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

标签: tuicool | 发表时间:2018-10-25 00:00 | 作者:
出处:http://itindex.net/relian

点击上方“程序人生”,选择“置顶公众号”

第一时间关注程序猿(媛)身边的故事

作者

高级农民工

已获原作者授权,如需转载,请联系原作者。

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

由于本文中含有一些超链接,微信中无法直接打开,所以建议复制链接到浏览器打开:

https://www.makcyun.top/web_scraping_withpython2.html

本文知识点:

  • Table型表格抓取

  • DataFrame.read_html函数使用

  • MySQL数据库存储

  • Navicat数据库的使用

1. table型表格

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

财富世界500强企业排名:

IMDB世界电影票房排行榜:

中国A股上市公司信息:

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

从中可以看到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函数方便快捷地抓取下来。下面我们就来操作一下。

2. 快速抓取

下面以中国上市公司信息这个网页中的表格为例,感受一下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)+'页抓取完成')

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

3. 详细代码实现

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中查看建立好之后的表格:

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

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中,如下图:

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

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

本文完。

- The End -

点击文末阅读全文,看『程序人生』其他精彩文章推荐

「若你有原创文章想与大家分享,欢迎投稿。」

加编辑微信ID,备注#投稿#:

程序 丨 druidlost  

小七 丨 duoshangshuang

推荐阅读:

print_r('点个赞吧');
var_dump('点个赞吧');
NSLog(@"点个赞吧!")
System.out.println("点个赞吧!");
console.log("点个赞吧!");
print("点个赞吧!");
printf("点个赞吧!\n");
cout << "点个赞吧!" << endl;
Console.WriteLine("点个赞吧!");
fmt.Println("点个赞吧!")
Response.Write("点个赞吧");
alert(’点个赞吧’)

相关 [代码 港股 新三板] 推荐:

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

- - IT瘾-tuicool
点击上方“程序人生”,选择“置顶公众号”. 第一时间关注程序猿(媛)身边的故事. 已获原作者授权,如需转载,请联系原作者. 摘要:我们平常在浏览网页中会遇到一些表格型的数据信息,除了表格本身体现的内容以外,可能还想透过表格背后再挖掘些有意思或者有价值的信息. 这时,可用python爬虫来实现. 本文采用pandas库中的read_html方法来快速准确地抓取网页中的表格数据.

代码重构

- - ITeye博客
随着程序的演化,我们有必要重新思考早先的决策,并重写部分代码. 代码需要演化;它不是静态的事物. 重写、重做和重新架构代码合起来,称为重构.    当你遇到绊脚石  ---  代码不在合适,你注意到有两样东西其实应该合并或是其他任何对你来说是"错误"的东西  -------- . 如果代码具备以下特征,你都应该考虑重构代码:.

代码小比较

- Tim - 斯巴达第二季
判断上百万个4k的buffer是否为全0,我最先想到的办法是:zero_buffer = malloc(4096);. /* 循环百万次读取buffer */.         /* 全0 */. 由于好奇,看看shell工具cp的代码,它的解决办法是:. /* 循环百万次读取buffer */.         /* 全0 */.

两行 JavaScript 代码

- MessyCS - Dreamer&#39;s Blog
最近看到了两行 JavaScript 代码,很受启发. 在 JavaScript 中,我们可以获取HTML元素的属性值,例如 element.id. 但是,因为 for 和 class 是 JavaScript 中的关键字,所以在 JavaScript 中这两个属性名称分别用 htmlFor 和 className 代替,于是在封装的时候需要先对这两个属性进行特殊判断.

Netty代码分析

- LightingMan - 淘宝JAVA中间件团队博客
Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序[官方定义],整体来看其包含了以下内容:1.提供了丰富的协议编解码支持,2.实现自有的buffer系统,减少复制所带来的消耗,3.整套channel的实现,4.基于事件的过程流转以及完整的网络事件响应与扩展,5.丰富的example.

python代码调试

- - 阿里古古
【转自: http://blog.csdn.net/luckeryin/article/details/4477233】. 本文讨论在没有方便的IDE工具可用的情况下,使用pdb调试python程序. 例如,有模拟税收计算的程序:. debug_demo函数计算4500的入账所需的税收. 在需要插入断点的地方,加入红色部分代码:如果_DEBUG值为True,则在该处开始调试(加入_DEBUG的原因是为了方便打开/关闭调试).

ios代码开源

- - CSDN博客移动开发推荐文章
本人从10年开始搞ios开发,从菜鸟到现在的入门,期间遇到了许多困难,也总结了一些东西,本着开源精神,希望大家共同成长的目的把这个工程开源出来.. 这个工程是从11年到13年之前完成的.主要是我平时用到的一些基础功能模块.其中有其他开源的代码和我自己写的一些.代码结构基本乱,12年以后的代码结构还可以,不是很乱,之前水平有限,如果不怎么样就别喷我了.

Oracle错误代码

- - 数据库 - ITeye博客
ORA-00001: 违反唯一约束条件 (.). ORA-00017: 请求会话以设置跟踪事件. ORA-00018: 超出最大会话数. ORA-00019: 超出最大会话许可数. ORA-00020: 超出最大进程数 (). ORA-00021: 会话附属于其它某些进程;无法转换会话. ORA-00022: 无效的会话 ID;访问被拒绝.

Java代码优化

- - ImportNew
2016年3月修改,结合自己的工作和平时学习的体验重新谈一下为什么要进行代码优化. 在修改之前,我的说法是这样的:. 就像鲸鱼吃虾米一样,也许吃一个两个虾米对于鲸鱼来说作用不大,但是吃的虾米多了,鲸鱼自然饱了. 代码优化一样,也许一个两个的优化,对于提升代码的运行效率意义不大,但是只要处处都能注意代码优化,总体来说对于提升代码的运行效率就很有用了.

用 pylint, 写好代码

- Nickcheng - 赖勇浩的编程私伙局
赖勇浩(http://laiyonghao.com). Pylint 是一个 Python 代码分析工具,它分析 Python 代码中的错误,查找不符合代码风格标准(Pylint 默认使用的代码风格是 PEP 8)和有潜在问题的代码. Pylint 是一个 Python 工具,除了平常代码分析工具的作用之外,它提供了更多的功能:如检查一行代码的长度,变量名是否符合命名标准,一个声明过的接口是否被真正实现等等.