查看原文
其他

实战演练-爬取深交所年报

爬虫俱乐部 Stata and Python数据分析 2022-03-15

本文作者:邓浩然(上海财经大学)

文字编辑:余术玲

技术总编:张   邯

重磅!!!为了大家能够更好的使用Stata软件,2019年11月8-11日,北京天演融智软件有限公司和武汉字符串数据科技有限公司(爬虫俱乐部)将在大连举办《Stata数据分析技术应用培训》。课程采用Stata公司在今年6月26日推出的新版Stata16软件进行教学,课程通过案例教学模式,旨在帮助大家在短期内掌握Stata的基本命令、编程、数据处理以及熟悉Stata核心的网络数据抓取技术,同时针对最新版Stata中的实用新功能也会做出详细介绍。目前正在火热招生中~


详细培训大纲及报名方式,请点击文末阅读原文呦~



序言

最近为了批量下载公司年报,减小工作量,决定写一段代码。

基本思路

1.获取深交所所有上市公司PDF的地址

2.通过访问PDF地址进行下载


代码分析

定义爬取网站的函数
访问深交所网站(http://www.szse.cn/disclosure/listed/fixed/index.html)从下图我们可以看到深交所已经把所有年报的地址都已经放到了一起,这样降低了工作量,只需要简单的翻页就能将所有的年报PDF地址爬取下来。

但问题在于点击网页后网页地址未发生变化,网页是ajex动态网站,需要通过审阅网页的方式获取需要访问的网页地址。

我们通过审查元素发现网页URL中有一个随机的尾部,只需要用Python中的random.random()函数放在URL尾部即可。同样我们也发现网页请求采取的是post请求上传payload表单参数(例如,requests.post(url,headers= headers,data = json.dumps(payload) ))。

进一步分析payload代码,点击两次翻页对比发现,payload中参数,{"seDate":["",""],"channelCode":["fixed_disc"],"bigCategoryId":["010301"],"pageSize":30,"pageNum":2},翻页后只有pageNum发生了变化,即页码数发生了变化,其他参数不变,所以我们便可以通过修改pageNum的方式实现对不同页码的访问。

综上,我们可以写下如下代码

 # 定义爬取函数def get_pdf_address(pageNum):    url = 'http://www.szse.cn/api/disc/announcement/annList?random=%s' % random.random()    headers = {'Accept': 'application/json, text/javascript, */*; q=0.01'    ,'Accept-Encoding': 'gzip, deflate'    ,'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'    ,'Content-Type': 'application/json'    ,'Host': 'www.szse.cn'    ,'Origin': 'http://www.szse.cn'    ,'Proxy-Connection': 'keep-alive'    ,'Referer': 'http://www.szse.cn/disclosure/listed/fixed/index.html'    ,'User-Agent': 'Mozilla/5.0(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)Chrome/74.0.3729.169 Safari/537.36'    ,'X-Request-Type': 'ajax'    ,'X-Requested-With': 'XMLHttpRequest'}    pagenum = int(pageNum)    payload = {"seDate":["",""],"channelCode":["fixed_disc"],"bigCategoryId":["010301"],"pageSize":30,"pageNum":pagenum}    response = requests.post(url,headers =headers,data = json.dumps(payload))    result = response.json() return result
解析返回的json代码

选取其中一个返回的json格式参数作为示例。如下所示:

返回的参数如下:{'annId': 1206427044, 'attachFormat':'PDF', 'attachPath':'/disc/disk02/finalpage/2019-07-05/dde0ce5e-e2c7-4c09-b6f4-a03ad9d593ee.PDF','attachSize': 2417, 'bigCategoryId': None, 'bigIndustryCode': None, 'bondType':None, 'channelCode': None, 'content': None, 'id':'12bc2f64-ec69-4c93-bc42-877e04a7dda5', 'publishTime': '2019-07-05 00:00:00','secCode': ['000020'], 'secName': ['深华发A'], 'smallCategoryId': None, 'title': '深华发A:2018年年度报告(更新后)'}]}。

当进入具体年报页面时,结合上述返回的信息,我们发现返回中的attachPath字符串"/disc/disk02/finalpage/2019-07-05/dde0ce5e-e2c7-4c09-b6f4-a03ad9d593ee.PDF"加上"http://disc.static.szse.cn/download/"前缀后便是PDF下载地址http://disc.static.szse.cn/download/disc/disk02/finalpage/2019-07-05/dde0ce5e-e2c7-4c09-b6f4-a03ad9d593ee.PDF。对返回代码进行解析后,便可直接将返回的代码的放入DataFrame中方便之后访问进行下载。

#创建一个DataFrame储存爬取信息pdf_infor= pd.DataFrame(columns =['secCode','secName','url','title','publishTime'])# 下载域名的前缀http://www.szse.cn/disclosure/listed/fixed/index.htmlcount = 0url_head ='http://disc.static.szse.cn/download/'for i in range(1,1557): print("爬取深交所年报下载地址第{}页".format(i)) result = get_pdf_address(i) num = len(result['data']) for each in range(num): # each = 1 pdf_infor.at[count,'secCode'] = result['data'][each]['secCode'][0] pdf_infor.at[count,'secName'] = result['data'][each]['secName'][0] pdf_infor.at[count,'url'] = url_head + result['data'][each]['attachPath'] pdf_infor.at[count,'title'] = result['data'][each]['title'] pdf_infor.at[count,'publishTime'] = result['data'][each]['publishTime'] count += 1 print('获取完成') time.sleep(random.uniform(1,2)) # 控制访问速度


全部代码如下

# -*-coding: utf-8 -*-import requestsimport timeimport pandas as pdimport randomimport osimport json# 定义爬取函数def get_pdf_address(pageNum): url = 'http://www.szse.cn/api/disc/announcement/annList?random=%s' % random.random() headers = {'Accept': 'application/json, text/javascript, */*; q=0.01' ,'Accept-Encoding': 'gzip, deflate' ,'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8' ,'Content-Type': 'application/json' ,'Host': 'www.szse.cn' ,'Origin': 'http://www.szse.cn' ,'Proxy-Connection': 'keep-alive' ,'Referer': 'http://www.szse.cn/disclosure/listed/fixed/index.html' ,'User-Agent': 'Mozilla/5.0(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)Chrome/74.0.3729.169 Safari/537.36' ,'X-Request-Type': 'ajax' ,'X-Requested-With': 'XMLHttpRequest'} pagenum = int(pageNum) payload = {"seDate":["",""],"channelCode":["fixed_disc"],"bigCategoryId":["010301"],"pageSize":30,"pageNum":pagenum} response = requests.post(url,headers =headers,data = json.dumps(payload)) result = response.json() return result #创建一个DataFrame储存爬取信息pdf_infor= pd.DataFrame(columns =['secCode','secName','url','title','publishTime'])# 下载域名的前缀http://www.szse.cn/disclosure/listed/fixed/index.htmlcount = 0url_head ='http://disc.static.szse.cn/download/'for i in range(1,1557): print("爬取深交所年报下载地址第{}页".format(i)) result = get_pdf_address(i) num = len(result['data']) for each in range(num): # each = 1 pdf_infor.at[count,'secCode'] = result['data'][each]['secCode'][0] pdf_infor.at[count,'secName'] = result['data'][each]['secName'][0] pdf_infor.at[count,'url'] = url_head + result['data'][each]['attachPath'] pdf_infor.at[count,'title'] = result['data'][each]['title'] pdf_infor.at[count,'publishTime'] = result['data'][each]['publishTime'] count += 1 print('获取完成') time.sleep(random.uniform(1,2)) # 控制访问速度 # 提取title中字符串获取年份pdf_infor['Year'] = pdf_infor['title'].str.extract('([0-9]{4})')file_path= "G:\\年报\\"# 这里放在了G盘年报文件夹headers ={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'}for each in range(pdf_infor.shape[0]): Stkcd = pdf_infor.at[each,'secCode'] firm_name = pdf_infor.at[each,'secName'].replace("*","") Year = pdf_infor.at[each,'Year'] print("开始下载{},股票代码{}的{}年报".format(firm_name,Stkcd,Year)) file_name = "{}{}{}.pdf".format(Stkcd,firm_name,Year) file_full_name = os.path.join(file_path,file_name) # print(file_full_name) pdf_url = pdf_infor.at[each,'url'] rs = requests.get(pdf_url,headers= headers,stream=True) with open(file_full_name, "wb") as fp: for chunk in rs.iter_content(chunk_size=10240): if chunk: fp.write(chunk) time.sleep(random.uniform(1,2)) # 控制访问速度 print("===================下载完成==========================")


 


对我们的推文累计打赏超过1000元,我们即可给您开具发票,发票类别为“咨询费”。用心做事,不负您的支持!
往期推文推荐
       重建“通天塔” —— Unicode编码与解码
输出分组描述性统计表的利器——report
字典常用操作小结
XPath Helper助XPath爬虫一臂之力
查找变量?用“codebook”!
distinct命令用法一览
Stata数据分析技术应用培训
玩转Python之“手把手”教你爬数据(一)
玩转Python之“手把手”教你爬数据(二
labelsof和labelbook介绍
Statalist上的“火云邪神”
爬虫实战程序的函数封装
Zipfile(二)
利用collapse命令转化原始数据
Stata中的数值型
爬虫实战——聚募网股权众筹信息爬取
duplicates drop之前,我们要做什么?
类型内置函数-type() isinstance()
数据含义记不住?—— label“大神”来帮忙

关于我们

微信公众号“Stata and Python数据分析”分享实用的stata、python等软件的数据处理知识,欢迎转载、打赏。我们是由李春涛教授领导下的研究生及本科生组成的大数据处理和分析团队。

此外,欢迎大家踊跃投稿,介绍一些关于stata和python的数据处理和分析技巧。
投稿邮箱:statatraining@163.com
投稿要求:
1)必须原创,禁止抄袭;
2)必须准确,详细,有例子,有截图;
注意事项:
1)所有投稿都会经过本公众号运营团队成员的审核,审核通过才可录用,一经录用,会在该推文里为作者署名,并有赏金分成。
2)邮件请注明投稿,邮件名称为“投稿+推文名称”。
3)应广大读者要求,现开通有偿问答服务,如果大家遇到有关数据处理、分析等问题,可以在公众号中提出,只需支付少量赏金,我们会在后期的推文里给予解答。

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存