本文开始正式介绍python抓取的代码编写,我将由浅入深地来介绍上市公司财务数据的抓取实现。上市公司列表的抓取相对会简单一些,本文会先对上市公司财务数据的相关页面进行分析,然后再详细讲解上市公司列表的抓取实现,若想直接看列表抓取的,可以直接跳到“第一点”的“第六步”开始阅读。对于有上市公司财务数据抓取需求的朋友,或者刚入门学习python抓取的同学均能提供参考价值。

先回顾一下本主题“Python抓取上市公司财务数据”的内容体系,分成9节来为‬大家讲解,从0到1来详细拆解程序开发流程和编码实现,这套体系内容适合给Python零基础的小白学者学习,同时也对熟悉python但对数据采集不熟练的量化专家提供一套Python抓取的最佳实践。其包含的主要章节如下:

1. Centos7搭建代码库和Python运行环境

2. Win10搭建Python开发环境

3. Python爬虫应用运行(Docker)镜像准备

4. 编码实现上市公司列表抓取

5. 编码实现上市公司简介和行业板块抓取

6. 编码实现上市公司企业财务摘要抓取

7. 编码实现上市公司历年财务数据抓取

8. 编码实现上市公司财务数据抓取结果入库(Mysql)

9. Python代码提交及部署运行

本文讲解的是其中的第4章节,主要包括:

1)上市公司财务数据页面的定位分析;

2)上市公司列表页的网络数据结构分析

3)编写列表抓取程序

4)列表抓取程序的运行

一、上市公司财务数据页面的定位分析

第一步:上市公司数据页面定位

打开新浪财经网站https://finance.sina.com.cn/,并在搜索框中搜索我们需要抓取的公司代码,以新华医疗(600587)为例,如下图:

python抓取财务数据(Python抓取上市公司财务数据)(1)

点击自动提示的搜索结果,我们就能进入对应上市公司的行情详情页https://finance.sina.com.cn/realstock/company/sh600587/nc.shtml,该页面左侧模块我们就能找到我们需要的数据页面链接,如下图:

python抓取财务数据(Python抓取上市公司财务数据)(2)

第二步:公司简介页面结构分析

打开公司简介页,地址如下:

http://vip.stock.finance.sina.com.cn/corp/go.php/vCI_CorpInfo/stockid/600587.phtml

通过url格式我们可以发现可以改变股票代码就能定位到不同上市公司的简介页,因此通配url如下(%s代表股票代码的字符串占位):

http://vip.stock.finance.sina.com.cn/corp/go.php/vCI_CorpInfo/stockid/%s.phtml

另外,我们可以看到公司简介信息以单表格的形式展现,如下:

python抓取财务数据(Python抓取上市公司财务数据)(3)

该表格的格式分为两列和四列两种,后续可根据奇偶列进行数据键值对关联处理。

第三步:所属行业页面结构分析

打开公司的所属行业页面,url地址如下:

https://vip.stock.finance.sina.com.cn/corp/go.php/vCI_CorpOtherInfo/stockid/600587/menu_num/2.phtml

通过url格式我们可以发现可以改变股票代码就能定位到不同上市公司的所属行业页面,因此通配url如下(%s代表股票代码的字符串占位):

https://vip.stock.finance.sina.com.cn/corp/go.php/vCI_CorpOtherInfo/stockid/%s/menu_num/2.phtml

我们可以看到公司的行业数据为表格中的一个字段值,如下:

python抓取财务数据(Python抓取上市公司财务数据)(4)

该数据的提取可以直接指定xpath路径提取文本即可。

第四步:财务摘要页面结构分析

打开公司财务摘要页面,地址如下:

http://vip.stock.finance.sina.com.cn/corp/go.php/vFD_FinanceSummary/stockid/600587/displaytype/4.phtml

通过url格式我们可以发现可以改变股票代码就能定位到不同上市公司的财务摘要页面,因此通配url如下(%s代表股票代码的字符串占位):

http://vip.stock.finance.sina.com.cn/corp/go.php/vFD_FinanceSummary/stockid/%s/displaytype/4.phtml

另外,我们可以看到公司财务摘要数据以多表格的形式展现,如下:

python抓取财务数据(Python抓取上市公司财务数据)(5)

该表格为固定两列,首行字体加粗代表日期,数据值存在有的带链接,有的不带。后续可直接提取两列的文本分别作为数据键和值,直接关联即可。

第五步:各类财务指标的历年数据结构分析

打开公司财务指标、资产负债表、利润表、现金流量表页面,地址分别如下:

http://vip.stock.finance.sina.com.cn/corp/go.php/vFD_FinancialGuideLine/stockid/600587/displaytype/4.phtml

http://vip.stock.finance.sina.com.cn/corp/go.php/vFD_BalanceSheet/stockid/600587/ctrl/part/displaytype/4.phtml

http://vip.stock.finance.sina.com.cn/corp/go.php/vFD_ProfitStatement/stockid/600587/ctrl/part/displaytype/4.phtml

http://vip.stock.finance.sina.com.cn/corp/go.php/vFD_CashFlow/stockid/600587/ctrl/part/displaytype/4.phtml

由于我们需要获取历年数据,我们需要进一步分析指标数据页面结构。从页面结构上来看,历年数据都在页面中有链接,如下图:

python抓取财务数据(Python抓取上市公司财务数据)(6)

python抓取财务数据(Python抓取上市公司财务数据)(7)

python抓取财务数据(Python抓取上市公司财务数据)(8)

python抓取财务数据(Python抓取上市公司财务数据)(9)

我们点击2021年的数据打开对应2021年的财务指标数据页,其URL分别如下:

https://money.finance.sina.com.cn/corp/go.php/vFD_FinancialGuideLine/stockid/600587/ctrl/2021/displaytype/4.phtml

http://money.finance.sina.com.cn/corp/go.php/vFD_BalanceSheet/stockid/600587/ctrl/2021/displaytype/4.phtml

http://money.finance.sina.com.cn/corp/go.php/vFD_ProfitStatement/stockid/600587/ctrl/2021/displaytype/4.phtml

http://money.finance.sina.com.cn/corp/go.php/vFD_CashFlow/stockid/600587/ctrl/2021/displaytype/4.phtml

通过url格式我们可以发现可以改变股票代码和年份就能定位到不同上市公司的对应年份的财务指标数据页面,因此通配url如下(%s代表股票代码字符串占位,%d代表对应年份的数值占位):

https://money.finance.sina.com.cn/corp/go.php/vFD_FinancialGuideLine/stockid/%s/ctrl/%d/displaytype/4.phtml

http://money.finance.sina.com.cn/corp/go.php/vFD_BalanceSheet/stockid/%s/ctrl/%d/displaytype/4.phtml

http://money.finance.sina.com.cn/corp/go.php/vFD_ProfitStatement/stockid/%s/ctrl/%d/displaytype/4.phtml

http://money.finance.sina.com.cn/corp/go.php/vFD_CashFlow/stockid/%s/ctrl/%d/displaytype/4.phtml

另外,我们看到各类财务指标数据均以多列表格的形式展现,如下图:

python抓取财务数据(Python抓取上市公司财务数据)(10)

并且表格均以报告日期为第一行,第一列均为数据指标名称,后续列则都为指标的值。另外,大部分指标数据都有一二级标题名称,也存在有的指标只有一级标题名称,如下图:

python抓取财务数据(Python抓取上市公司财务数据)(11)

后续可通过多级字典的格式来存储数据,第1级为日期、第2级为一级标题、第3级为二级标题(若有),最后的那一级为指标的值。

待抓取的上市公司财务数据分析完毕后。我们开始分析上市公司的列表数据。

第六步:定位上市公司列表

通过查找新浪财经网站,我们可以找到行情中心页面有根据行业分类来查询不同分类下的上市公司列表,如下图:

python抓取财务数据(Python抓取上市公司财务数据)(12)

我们可以选择沪市A股、深市A股、北交所三个主要市场的上市企业列表,打开其中一个列表(沪市A股)页面,列表为分页展示,如下图:

python抓取财务数据(Python抓取上市公司财务数据)(13)

二、上市公司列表页的网络数据结构分析

第一步:打开页面的浏览器检查工具

我们通过右键-点击“检查”(或快捷键F12),如下图:

python抓取财务数据(Python抓取上市公司财务数据)(14)

第二步:筛选XHR类型的网络请求列表

切换到“网络”TAB的“XHR”项,如下图:

python抓取财务数据(Python抓取上市公司财务数据)(15)

第三步:监测翻页操作的新增网络请求

我们点击下一页,并观察网络列表的新增请求,如下图:

python抓取财务数据(Python抓取上市公司财务数据)(16)

我们查看请求详情,发现是通过GET方法请求的分页数据,如下图:

python抓取财务数据(Python抓取上市公司财务数据)(17)

第四步:分析列表请求的url地址

列表的请求的url为:

https://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php/Market_Center.getHQNodeData?page=2&num=40&sort=symbol&asc=1&node=sh_a&symbol=&_s_r_a=page

我们可以通过多次翻页查看请求得出,该url的page参数为页码。并通过查看其他分类的请求可知,node参数为分类标识,其中沪市A股为sh_a,深市A故为sz_a,北交所为hs_bjs。

第五步:分析列表请求的响应数据

从其响应的数据我们知道响应数据的格式为json数组,将json数据格式化后,我们能看出code为股票代码,name为股票名称(经过了unioncode编码),如下图所示:

python抓取财务数据(Python抓取上市公司财务数据)(18)

三、编写列表抓取程序

第一步:打开scrapy_list.py模块文件

在项目scrapy-finance文件夹中,选择scrapy_list文件并选择右键-Edit with IDLE-Edit with IDLE 3.0(64-bit),如下图:

python抓取财务数据(Python抓取上市公司财务数据)(19)

点击打开后,就可以开始编写代码,如下图:

python抓取财务数据(Python抓取上市公司财务数据)(20)

第二步:编写页面抓取方法

根据列表页的url参数,创建抓取方法scrapy_page,参数包括页码、分类标识,并实现页面请求和解析逻辑,代码如下:

from urllib import request import ssl import json ssl._create_default_https_context = ssl._create_unverified_context def scrapy_page(self, page_no, market_sign): list_url = "https://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php/Market_Center.getHQNodeData?page=%d&num=40&sort=symbol&asc=1&node=%s&symbol=&_s_r_a=page" resp = request.urlopen(list_url%(page_no, market_sign)) print ("request url:" resp.geturl()) rt_code = resp.getcode() if rt_code==200: data_json = json.loads(resp.read().decode()) return [{'code': one.get('code'), 'name': one.get('name')} for one in data_json] else: print ("error return code:" rt_code) return []

以上代码说明如下:

1. from urllib import request为从urllib模块中加载request子模块,http请求我们使用request模块的urlopen方法;

2. import ssl和ssl._create_default_https_context = ssl._create_unverified_context是在https安全请求时,设置为做不认证的模式。不做此设置会导致https类型的请求会失败。

3. list_url%(page_no, market_sign)中的%是python中对字符串进行格式化的方式,可拼接一个或多个(多个需用括号括起来)变量,变量将替换掉字符串中的对应占位符,占位符类型包括以下几种:

(1)%d为整数占位,将整型和浮点型转为整型后填充;

(2)%f为浮点数占位,将整型和浮点型转为浮点型后填充,默认保留6位小数;

(3)%s为字符串占位,转化为对象的字符串表述(或者说取对象的__str__方法返回值)后填充;

(4)%x为十六进制占位,将十进制数转化为十六进制后填充;

(5)%o为八进制占位,将十进制数转化为八进制后填充;

4. request.urlopen()方法实现了请求URL地址,其参数可只传URL,也可指定参数的方式来调用,可传的参数有method=请求方法,url=请求URL,body=请求体数据,headers=请求头数据,请求结果将以http.client.HTTPResponse对象的形式返回

5. HTTPResponse类提供了geturl()获取请求的URL、getcode()获取响应码、read()读取响应的内容,getheaders()获取响应头等方法,用来获取响应的相关数据。

格式上需要注意以下两点:

1. import引入依赖和ssl全局设置的代码放在模块代码的顶部;

2. scrapy_page函数的实现放在ScrapyList类中,并注意统一增加一级缩进;

如下图:

python抓取财务数据(Python抓取上市公司财务数据)(21)

第三步:编写调用测试列表请求

在ScrapyList类的run方法中增加scrapy_page的调用以测试列表页的请求和解析,代码如下:

res_list = self.scrapy_page(1, 'sh_a') print ('res len=%d first=%s'%(len(res_list), str(res_list[0])))

如下图:

python抓取财务数据(Python抓取上市公司财务数据)(22)

第四步:保存并运行列表抓取模块

使用Ctrl S快捷键保存后,Ctrl F5运行模块,如下图所示:

python抓取财务数据(Python抓取上市公司财务数据)(23)

至此我们成功抓取到了一页上市企业数据,一页40条,并成功解析出了股票代码和股票名称。

第五步:编写循环抓取上市公司列表

基于刚刚的测试代码,我们增加while循环来实现循环抓取,并且在结果数量不足40条时结束循环。并且在抓取结束后等待1秒钟再继续抓下一页,避免请求太频繁而被限制请求。最后在请求异常时,进行异常捕获,避免因为某次报错而中断抓取循环,同时在出错时等待3秒钟后再重试请求。循环抓取的结果将存储在code_list数组中。代码如下:

code_list = [] # sh_a market market_sign = "sh_a" page_no = 1 while True: try: res_list = self.scrapy_page(page_no, market_sign) code_list.extend(res_list) if len(res_list) < 40: break page_no = 1 time.sleep(1) except HTTPError as err: print ('Http Error:', err) time.sleep(3)

另外,模块顶部代码还需:

1. 增加from time来导入时间模块以支持sleep方法;

2. 增加from urllib.error import HTTPError来导入HTTPError类。

如下图:

python抓取财务数据(Python抓取上市公司财务数据)(24)

第六步:编写将列表数据存储到文件

编写存储列表数据到文件的函数save_list_to_file,参数包含list_file(存储文件的路径)、res_list(待存储的json列表),代码如下:

# result write to file def save_list_to_file(self, list_file, res_list): try: fb = open(list_file, 'w') for item in res_list: fb.write(json.dumps(item) '\n') except IOError as err: print ('IO Error:', err) else: fb.close()

其中:

1. open()方法为读写文件的方法,第二个参数为文件操作模式,可以用w覆盖写入、r读取、a追加,方法的返回值为文件句柄。

2. json.dumps方法为将json对象导出成字符串,以便将字符串存储到文件中。写入的json字符串后缀增加'\n'换行字符,代表一个json存储为一行。

3. 最后我们捕获到IOError异常则打印出来,若正常处理则最后调用close()方法来关闭文件句柄。

我们将save_list_to_file方法的实现放在ScrapyList类中,并在run()方法中调用,并且将存储路径在__init__方法中初始化,如下图所示:

python抓取财务数据(Python抓取上市公司财务数据)(25)

至此,列表抓取代码编写完成,我们可以将#todo注释去掉。

四、列表抓取程序的运行

第一步:创建list_data结果存储文件夹

python抓取财务数据(Python抓取上市公司财务数据)(26)

命名为list_data,回车保存,如下图:

python抓取财务数据(Python抓取上市公司财务数据)(27)

第二步:本地运行scrapy_list.py模块

由于上市公司列表的抓取请求量并不大,我们可以直接运行测试一下列表抓取模块,如下图:

python抓取财务数据(Python抓取上市公司财务数据)(28)

python抓取财务数据(Python抓取上市公司财务数据)(29)

第三步:查看本地列表抓取结果

可以发现总共抓取了53页上市公司列表。我们查看抓取结果文件夹list_data,可知抓取结果成功写入到了code_list.data文件夹。如下图:

python抓取财务数据(Python抓取上市公司财务数据)(30)

第四步:编写增加多个分类的列表抓取

我们复制sz_a的循环抓取代码,修改market_sign变量的初始化值分别为'sz_a'、'hs_bjs',如下图所示:

python抓取财务数据(Python抓取上市公司财务数据)(31)

使用Ctrl S保存文件,至此上市公司的分类列表抓取代码编写完成。

五、结语

本节主要讲解了上市公司数据的页面结构以及上市公司列表数据的页面网络解析和python抓取代码,并在本地运行了单个分类的测试列表抓取,成功抓到到了53页列表数据。最后,我们按照循环的逻辑实现了沪市A股、深市A股以及北交所三个分类的上市公司列表抓取,为后面的公司详情及历年财务数据抓取做好了基础数据的准备。

下一节将介绍“上市公司简介和行业板块的抓取”,将会对python抓取技术做进一步的实操学习,希望通过详细的步骤讲解及逐层深入的分析,能让小白用户也能轻松拿起python来实现自己的抓取程序。并最终能独立完成整个财务数据的抓取,为后续的量化分析提供充足的数据基础。感兴趣的可以关注下我的后续动态,有任何疑问或建议欢迎在评论区留言。

,