实战演练-爬取深交所年报
本文作者:邓浩然(上海财经大学)
文字编辑:余术玲
技术总编:张 邯
重磅!!!为了大家能够更好的使用Stata软件,2019年11月8-11日,北京天演融智软件有限公司和武汉字符串数据科技有限公司(爬虫俱乐部)将在大连举办《Stata数据分析技术应用培训》。课程采用Stata公司在今年6月26日推出的最新版Stata16软件进行教学,课程通过案例教学模式,旨在帮助大家在短期内掌握Stata的基本命令、编程、数据处理以及熟悉Stata核心的网络数据抓取技术,同时针对最新版Stata中的实用新功能也会做出详细介绍。目前正在火热招生中~
详细培训大纲及报名方式,请点击文末阅读原文呦~
序言
最近为了批量下载公司年报,减小工作量,决定写一段代码。
1.获取深交所所有上市公司PDF的地址
2.通过访问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格式参数作为示例。如下所示:
返回的参数如下:{'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.html
count = 0
url_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 requests
import time
import pandas as pd
import random
import os
import 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.html
count = 0
url_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("===================下载完成==========================")
关于我们
微信公众号“Stata and Python数据分析”分享实用的stata、python等软件的数据处理知识,欢迎转载、打赏。我们是由李春涛教授领导下的研究生及本科生组成的大数据处理和分析团队。
1)必须原创,禁止抄袭;
2)必须准确,详细,有例子,有截图;
注意事项:
1)所有投稿都会经过本公众号运营团队成员的审核,审核通过才可录用,一经录用,会在该推文里为作者署名,并有赏金分成。
2)邮件请注明投稿,邮件名称为“投稿+推文名称”。
3)应广大读者要求,现开通有偿问答服务,如果大家遇到有关数据处理、分析等问题,可以在公众号中提出,只需支付少量赏金,我们会在后期的推文里给予解答。