10行代码爬取全国所有A股/港股/新三板上市公司信息

picture.image

数据科学俱乐部

中国数据科学家社区

picture.image

作者 : 苏克,零基础、转行python爬虫与数据分析

博客:https://www.makcyun.top

摘要: 我们平常在浏览网页中会遇到一些表格型的数据信息,除了表格本身体现的内容以外,可能还想透过表格背后再挖掘些有意思或者有价值的信息。这时,可用python爬虫来实现。本文采用pandas库中的read_html方法来快速准确地抓取网页中的表格数据。

本文知识点:

  • Table型表格抓取
  • DataFrame.read_html函数使用
  • MySQL数据库存储
  • Navicat数据库的使用
  1. table型表格

我们在网页上会经常看到这样一些表格,比如:
QS2018世界大学排名:

picture.image

财富世界500强企业排名:

picture.image

IMDB世界电影票房排行榜:

picture.image

中国A股上市公司信息:

picture.image

它们除了都是表格以外,还一个共同点就是当点击右键-定位时,可以看到它们都是table类型的表格。

picture.image

picture.image

picture.image

picture.image

从中可以看到table类型的表格网页结构大致如下:


        
 1<table class="..." id="...">  
 2    <thead>  
 3    <tr>  
 4    <th>...</th>  
 5    </tr>  
 6    </thead>  
 7    <tbody>  
 8        <tr>  
 9            <td>...</td>  
10        </tr>  
11        <tr>...</tr>  
12        <tr>...</tr>  
13        <tr>...</tr>  
14        <tr>...</tr>  
15        ...  
16        <tr>...</tr>  
17        <tr>...</tr>  
18        <tr>...</tr>  
19        <tr>...</tr>          
20    </tbody>  
21</table>  

    

先来简单解释一下上文出现的几种标签含义:


        
1<table>    : 定义表格  
2<thead>    : 定义表格的页眉  
3<tbody>    : 定义表格的主体  
4<tr>    : 定义表格的行  
5<th>    : 定义表格的表头  
6<td>    : 定义表格单元  

    

这样的表格数据,就可以利用pandas模块里的read_html函数方便快捷地抓取下来。下面我们就来操作一下。

  1. 快速抓取

下面以中国上市公司信息这个网页中的表格为例,感受一下read_html函数的强大之处。


        
1import pandas as pd  
2import csv  
3  
4for i in range(1,178):  # 爬取全部177页数据  
5    url = 'http://s.askci.com/stock/a/?reportTime=2017-12-31&pageNum=%s' % (str(i))  
6    tb = pd.read_html(url)[3] #经观察发现所需表格是网页中第4个表格,故为[3]  
7    tb.to_csv(r'1.csv', mode='a', encoding='utf\_8\_sig', header=1, index=0)  
8    print('第'+str(i)+'页抓取完成')  

    

picture.image

只需不到十行代码,1分钟左右就可以将全部178页共3535家A股上市公司的信息干净整齐地抓取下来。比采用正则表达式、xpath这类常规方法要省心省力地多。如果采取人工一页页地复制粘贴到excel中,就得操作到猴年马月去了。
上述代码除了能爬上市公司表格以外,其他几个网页的表格都可以爬,只需做简单的修改即可。因此,可作为一个简单通用的代码模板。但是,为了让代码更健壮更通用一些,接下来,以爬取177页的A股上市公司信息为目标,讲解一下详细的代码实现步骤。

  1. 详细代码实现

3.1. read_html函数

先来了解一下read_html 函数的api:


        
 1pandas.read_html(io, match='.+', flavor=None, header=None, index_col=None, skiprows=None, attrs=None, parse_dates=False, tupleize_cols=None, thousands=', ', encoding=None, decimal='.', converters=None, na_values=None, keep_default_na=True, displayed_only=True)  
 2  
 3常用的参数:  
 4io:可以是url、html文本、本地文件等;  
 5flavor:解析器;  
 6header:标题行;  
 7skiprows:跳过的行;  
 8attrs:属性,比如 attrs = {'id': 'table'};  
 9parse_dates:解析日期  
10  
11注意:返回的结果是**DataFrame**组成的**list**

参考:

1 http://pandas.pydata.org/pandas-docs/stable/io.html#io-read-html
2 http://pandas.pydata.org/pandas-docs/stable/generated/pandas.read\_html.html

3.2. 分析网页url

首先,观察一下中商情报网第1页和第2页的网址:


        
1http://s.askci.com/stock/a/?reportTime=2017-12-31&pageNum=1#QueryCondition  
2http://s.askci.com/stock/a/?reportTime=2017-12-31&pageNum=2#QueryCondition  

    

可以发现,只有pageNum 的值随着翻页而变化,所以基本可以断定pageNum=1代表第1页,pageNum=10代表第10页,以此类推。这样比较容易用for循环构造爬取的网址。
试着把**#QueryCondition** 删除,看网页是否同样能够打开,经尝试发现网页依然能正常打开,因此在构造url时,可以使用这样的格式:
http://s.askci.com/stock/a/?reportTime=2017-12-31&pageNum=i
再注意一下其他参数:
a :表示A股,把a替换为h ,表示港股 ;把a替换为xsb ,则表示新三板 。那么,在网址分页for循环外部再加一个for循环,就可以爬取这三个股市的股票了。

3.3. 定义函数

将整个爬取分为网页提取、内容解析、数据存储等步骤,依次建立相应的函数。


        
 1# 网页提取函数  
 2def get\_one\_page(i):  
 3    try:  
 4        headers = {  
 5            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'  
 6        }  
 7        paras = {  
 8        'reportTime': '2017-12-31',     
 9        #可以改报告日期,比如2018-6-30获得的就是该季度的信息  
10        'pageNum': i   #页码  
11        }  
12        url = 'http://s.askci.com/stock/a/?' + urlencode(paras)  
13        response = requests.get(url,headers = headers)  
14        if response.status_code == 200:  
15            return response.text  
16        return None  
17    except RequestException:  
18        print('爬取失败')  
19  
20# beatutiful soup解析然后提取表格  
21def parse\_one\_page(html):  
22    soup = BeautifulSoup(html,'lxml')  
23    content = soup.select('#myTable04')[0] #[0]将返回的list改为bs4类型  
24    tbl = pd.read_html(content.prettify(),header = 0)[0]  
25    # prettify()优化代码,[0]从pd.read\_html返回的list中提取出DataFrame  
26  
27    tbl.rename(columns = {'序号':'serial\_number', '股票代码':'stock\_code', '股票简称':'stock\_abbre', '公司名称':'company\_name', '省份':'province', '城市':'city', '主营业务收入(201712)':'main\_bussiness\_income', '净利润(201712)':'net\_profit', '员工人数':'employees', '上市日期':'listing\_date', '招股书':'zhaogushu', '公司财报':'financial\_report', '行业分类':'industry\_classification', '产品类型':'industry\_type', '主营业务':'main\_business'},inplace = True)  
28  
29    print(tbl)  
30    # return tbl  
31    # rename将表格15列的中文名改为英文名,便于存储到mysql及后期进行数据分析  
32    # tbl = pd.DataFrame(tbl,dtype = 'object') #dtype可统一修改列格式为文本  
33  
34# 主函数  
35def main(page):  
36    for i in range(1,page):   # page表示提取页数  
37        html = get_one_page(i)  
38        parse_one_page(html)  
39  
40# 单进程  
41if __name__ == '\_\_main\_\_':      
42    main(178)   #共提取n页  

    

上面两个函数相比于快速抓取的方法代码要多一些,如果需要抓的表格很少或只需要抓一次,那么推荐快速抓取法。如果页数比较多,这种方法就更保险一些。解析函数用了BeautifulSoup和css选择器,这种方法定位提取表格所在的id为#myTable04 的table代码段,更为准确。

3.4. 存储到MySQL

接下来,我们可以将结果保存到本地csv文件,也可以保存到MySQL数据库中。这里为了练习一下MySQL,因此选择保存到MySQL中。

首先,需要先在数据库建立存放数据的表格,这里命名为listed_company 。代码如下:


        
 1import pymysql  
 2  
 3def generate\_mysql():  
 4    conn = pymysql.connect(  
 5        host='localhost',   # 本地服务器  
 6        user='root',  
 7        password='******',  # 你的数据库密码  
 8        port=3306,          # 默认端口  
 9        charset = 'utf8',  
10        db = 'wade')  
11    cursor = conn.cursor()  
12  
13    sql = 'CREATE TABLE IF NOT EXISTS listed\_company2 (serial\_number INT(30) NOT NULL,stock\_code INT(30) ,stock\_abbre VARCHAR(30) ,company\_name VARCHAR(30) ,province VARCHAR(30) ,city VARCHAR(30) ,main\_bussiness\_income VARCHAR(30) ,net\_profit VARCHAR(30) ,employees INT(30) ,listing\_date DATETIME(0) ,zhaogushu VARCHAR(30) ,financial\_report VARCHAR(30) , industry\_classification VARCHAR(255) ,industry\_type VARCHAR(255) ,main\_business VARCHAR(255) ,PRIMARY KEY (serial\_number))'  
14    # listed\_company是要在wade数据库中建立的表,用于存放数据  
15  
16    cursor.execute(sql)  
17    conn.close()  
18  
19generate_mysql()  

    

上述代码定义了generate_mysql()函数,用于在MySQL中wade数据库下生成一个listed_company的表。表格包含15个列字段。根据每列字段的属性,分别设置为INT整形(长度为30)、VARCHAR字符型(长度为30) 、DATETIME(0) 日期型等。
在Navicat中查看建立好之后的表格:

picture.image

picture.image

接下来就可以往这个表中写入数据,代码如下:


        
 1import pymysql  
 2from sqlalchemy import create_engine  
 3  
 4def write\_to\_sql(tbl, db = 'wade'):  
 5    engine = create_engine('mysql+pymysql://root:******@localhost:3306/{0}?charset=utf8'.format(db))  
 6    # db = 'wade'表示存储到wade这个数据库中,root后面的*是密码  
 7    try:  
 8        tbl.to_sql('listed\_company',con = engine,if_exists='append',index=False)  
 9        # 因为要循环网页不断数据库写入内容,所以if\_exists选择append,同时该表要有表头,parse\_one\_page()方法中df.rename已设置  
10    except Exception as e:  
11        print(e)  

    

以上就完成了单个页面的表格爬取和存储工作,接下来只要在main()函数进行for循环,就可以完成所有总共178页表格的爬取和存储,完整代码如下:


        
 1import requests  
 2import pandas as pd  
 3from bs4 import BeautifulSoup  
 4from lxml import etree  
 5import time  
 6import pymysql  
 7from sqlalchemy import create_engine  
 8from urllib.parse import urlencode  # 编码 URL 字符串  
 9  
10start_time = time.time()  #计算程序运行时间  
11  
12def get\_one\_page(i):  
13    try:  
14        headers = {  
15            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'  
16        }  
17        paras = {  
18        'reportTime': '2017-12-31',  
19        #可以改报告日期,比如2018-6-30获得的就是该季度的信息  
20        'pageNum': i   #页码  
21        }  
22        url = 'http://s.askci.com/stock/a/?' + urlencode(paras)  
23        response = requests.get(url,headers = headers)  
24        if response.status_code == 200:  
25            return response.text  
26        return None  
27    except RequestException:  
28        print('爬取失败')  
29  
30  
31def parse\_one\_page(html):  
32    soup = BeautifulSoup(html,'lxml')  
33    content = soup.select('#myTable04')[0] #[0]将返回的list改为bs4类型  
34    tbl = pd.read_html(content.prettify(),header = 0)[0]  
35    # prettify()优化代码,[0]从pd.read\_html返回的list中提取出DataFrame  
36    tbl.rename(columns = {'序号':'serial\_number', '股票代码':'stock\_code', '股票简称':'stock\_abbre', '公司名称':'company\_name', '省份':'province', '城市':'city', '主营业务收入(201712)':'main\_bussiness\_income', '净利润(201712)':'net\_profit', '员工人数':'employees', '上市日期':'listing\_date', '招股书':'zhaogushu', '公司财报':'financial\_report', '行业分类':'industry\_classification', '产品类型':'industry\_type', '主营业务':'main\_business'},inplace = True)  
37  
38    # print(tbl)  
39    return tbl  
40    # rename将中文名改为英文名,便于存储到mysql及后期进行数据分析  
41    # tbl = pd.DataFrame(tbl,dtype = 'object') #dtype可统一修改列格式为文本  
42  
43def generate\_mysql():  
44    conn = pymysql.connect(  
45        host='localhost',  
46        user='root',  
47        password='******',  
48        port=3306,  
49        charset = 'utf8',    
50        db = 'wade')  
51    cursor = conn.cursor()  
52  
53    sql = 'CREATE TABLE IF NOT EXISTS listed\_company (serial\_number INT(20) NOT NULL,stock\_code INT(20) ,stock\_abbre VARCHAR(20) ,company\_name VARCHAR(20) ,province VARCHAR(20) ,city VARCHAR(20) ,main\_bussiness\_income VARCHAR(20) ,net\_profit VARCHAR(20) ,employees INT(20) ,listing\_date DATETIME(0) ,zhaogushu VARCHAR(20) ,financial\_report VARCHAR(20) , industry\_classification VARCHAR(20) ,industry\_type VARCHAR(100) ,main\_business VARCHAR(200) ,PRIMARY KEY (serial\_number))'  
54    # listed\_company是要在wade数据库中建立的表,用于存放数据  
55  
56    cursor.execute(sql)  
57    conn.close()  
58  
59  
60def write\_to\_sql(tbl, db = 'wade'):  
61    engine = create_engine('mysql+pymysql://root:******@localhost:3306/{0}?charset=utf8'.format(db))  
62    try:  
63        # df = pd.read\_csv(df)  
64        tbl.to_sql('listed\_company2',con = engine,if_exists='append',index=False)  
65        # append表示在原有表基础上增加,但该表要有表头  
66    except Exception as e:  
67        print(e)  
68  
69  
70def main(page):  
71    generate_mysql()  
72    for i in range(1,page):    
73        html = get_one_page(i)  
74        tbl = parse_one_page(html)  
75        write_to_sql(tbl)  
76  
77# # 单进程  
78if __name__ == '\_\_main\_\_':      
79    main(178)  
80  
81    endtime = time.time()-start_time  
82    print('程序运行了%.2f秒' %endtime)  
83  
84  
85# 多进程  
86# from multiprocessing import Pool  
87# if \_\_name\_\_ == '\_\_main\_\_':  
88#     pool = Pool(4)  
89#     pool.map(main, [i for i in range(1,178)])  #共有178页  
90  
91#     endtime = time.time()-start\_time  
92#     print('程序运行了%.2f秒' %(time.time()-start\_time))  

    

最终,A股所有3535家企业的信息已经爬取到mysql中,如下图:

picture.image

除了A股,还可以顺便再把港股和新三板所有的上市公司也爬了。后期,将会对爬取的数据做一下简单的数据分析。

最后,需说明不是所有表格都可以用这种方法爬取,比如这个网站中的表格,表面是看起来是表格,但在html中不是前面的table格式,而是list列表格式。这种表格则不适用read_html爬取。得用其他的方法,比如selenium,以后再进行介绍。

picture.image

picture.image

Python中文社区作为一个去中心化的全球技术社区,以成为全球20万Python中文开发者的精神部落为愿景,目前覆盖各大主流媒体和协作平台,与阿里、腾讯、百度、微软、亚马逊、开源中国、CSDN等业界知名公司和技术社区建立了广泛的联系,拥有来自十多个国家和地区数万名登记会员,会员来自以公安部、工信部、清华大学、北京大学、北京邮电大学、中国人民银行、中科院、中金、华为、BAT、谷歌、微软等为代表的政府机关、科研单位、金融机构以及海内外知名公司,全平台近20万开发者关注。

Python中文社区公众号底部回复“内推”

获取一周内推技术职位清单

picture.image

点击下方 阅读原文 ,学习 网易云课堂 数据分析师微专业

0
0
0
0
评论
未登录
暂无评论