爬取A股上市公司指定年份年报
本文作者:胡思航 中南财经政法大学统计与数学学院
本文编辑:董高飞
技术总编:金点
Stata and Python 数据分析
爬虫俱乐部将于2023年8月1日至9日在河南大学(河南开封)举行Stata编程技术和Python编程技术定制培训,同时在网络平台进行直播,提供线上学习的方式。线上线下培训均有专门的答疑团队。大家感兴趣请点击推文链接《爬虫俱乐部2023第一期编程训练营开始报名啦!》或点击文末阅读原文查看课程详情及报名方式!
按F12打开开发者工具,切换到网络选项卡。我们切换一下页面,发现多出了一个query请求,这就是我们需要的访问接口。
笔者定义了一个访问接口的函数如下:
#定义一个访问接口的函数
def get_report(page_num,date):
url = "http://www.cninfo.com.cn/new/hisAnnouncement/query"
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"Content-Length": "195",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Host": "www.cninfo.com.cn",
"Origin": "http://www.cninfo.com.cn",
"Proxy-Connection": "keep-alive",
"Referer": "http://www.cninfo.com.cn/new/commonUrl/pageOfSearch?url=disclosure/list/search&checkedCategory=category_ndbg_szsh",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.42",
"X-Requested-With": "XMLHttpRequest"
}
'''
参数信息
plate: sz;sh, 表示沪深两市
seDate:查询时间
'''
data = {
"pageNum": page_num,
"pageSize": 30,
"column": "szse",
"tabName": "fulltext",
"plate": "sz;sh",
"searchkey": "",
"secid": "",
"category": "category_ndbg_szsh",
"trade": "",
"seDate": date,
"sortName": "code",
"sortType": "asc",
"isHLtitle": "false"
}
response = requests.post(url, data=data, headers=headers)
return response
03数据获取这部分主要就是为了实现本文的主要目的:获取指定年份的上市公司报告链接,首先我们需要循环遍历整个所有页面,那么如何确定遍历次数呢?仔细研究返回的json文件我们发现文件最后会包含一个totalpapges的参数,即总页数。有了这个数字,我们便可以先请求一次获取总页数,从而设定好循环次数进行获取。以下给出循环访问获取数据函数的完整代码:def downlaod_report(date):
global counter
all_results = []
page_num = 1
response_test = get_report(page_num,date)
data_test = response_test.json()
total_pages = data_test["totalpages"]
max_retries = 3 #最大重试次数
retry_count = 0 #当前重试次数
while page_num <= total_pages:
response = None
# 重试机制
while retry_count <= max_retries:
# 发送请求
try:
# response = requests.post(url, data=data,headers=headers)
response = get_report(page_num,date)
response.raise_for_status()
break
except requests.exceptions.RequestException as e:
print(f"出现错误!: {e}")
print(f"5秒后重试...")
time.sleep(5)
retry_count += 1
if retry_count > max_retries:
print(f"{max_retries} 次重试后均失败. 跳过第 {page_num}页.")
page_num += 1
retry_count = 0
continue
else:
# 解析数据
try:
data = response.json()
# print(f"正在下载第 {page_num}/{total_pages} 页")
print(f"\r正在下载第 {counter}/{sum} 页",end='')
# 尝试解析公告数据,如果解析失败则重试
retry_count = 0
while True:
try:
if data["announcements"] is None:
raise Exception("公告数据为空")
else:
all_results.extend(data["announcements"])
break
except (TypeError, KeyError) as e:
print(f"解析公告数据失败: {e}")
print(f"5秒后重试...")
time.sleep(5)
retry_count += 1
if retry_count > max_retries:
raise Exception("达到最大重试次数,跳过此页")
continue
page_num += 1
counter +=1
except (ValueError, KeyError) as e:
print(f"解析响应数据失败: {e}")
print(f"5秒后重试...")
time.sleep(5)
retry_count += 1
if retry_count > max_retries:
raise Exception("达到最大重试次数,跳过此页")
continue
return all_results
04数据保存我们根据年报发布的分布特点,将每年的1月1日-4月30日作为时间范围。然后将其细化,1-4月为一组,4月内部分为5组,保证每组获取到的总页数均小于100,并将本列表作为函数参数进行遍历,将获取到的数据汇总。既然已经获取到了所有数据,我们不可能将所有数据直接保存为不易阅读的json格式,那么接下来就是将得到的数据进行解析。我们首先分析了json文件中的具体参数,选择解析如图所示的几个变量,包括股票代码,公司名称,报告名称,年报链接等内容,并将年报链接拼串补充完整。接着创建一个excel表格,按这些变量的顺序创建表格,并存入表格,注意在存入表格时,需要对内容进行筛选,如“英文版,摘要”这类年报并不是我们所需要的。以下是主函数的完整代码。def main(year):
# 计数器
global sum
date_count = f"{year}-01-01~{year}-04-30"
response = get_report(1,date_count)
data = response.json()
sum = data["totalpages"]
year = year+1
all_results = []
time_segments = [
f"{year}-01-01~{year}-04-01",
f"{year}-04-02~{year}-04-15",
f"{year}-04-16~{year}-04-22",
f"{year}-04-23~{year}-04-26",
f"{year}-04-27~{year}-04-28",
f"{year}-04-29~{year}-04-30"
]
for i in time_segments:
results = downlaod_report(i)
all_results.extend(results)
# 创建Excel文件并添加表头
workbook = openpyxl.Workbook()
worksheet = workbook.active
worksheet.title = "爬虫title"
worksheet.append(["公司代码", "公司简称", "标题", "年份", "年报链接"])
# 解析搜索结果并添加到Excel表格中
for item in all_results:
company_code = item["secCode"]
company_name = item["secName"]
title = item["announcementTitle"].strip()
# 剔除不需要的样式和特殊符号,并重新组合标题
title = re.sub(r"<.*?>", "", title)
title = title.replace(":", "")
title = f"《{title}》"
adjunct_url = item["adjunctUrl"]
year = re.search(r"\d{4}", title)
if year:
year = year.group()
else:
year = setYear
time = f"{year}"
announcement_url = f"http://static.cninfo.com.cn/{adjunct_url}"
# 检查标题是否包含排除关键词
exclude_flag = False
for keyword in exclude_keywords:
if keyword in title:
exclude_flag = True
break
# 如果标题不包含排除关键词,则将搜索结果添加到Excel表格中
if not exclude_flag:
worksheet.append([company_code, company_name, title, time, announcement_url])
workbook.save(f"年报链接_{setYear}.xlsx")
05开始运行到这一步基本就大功告成了!只需要设置相关参数,就可以选择下载特定年份的年报,或者用循环遍历下载指定范围的年报。if __name__ == '__main__':
# 全局变量
exclude_keywords = ['英文', '摘要','已取消','公告']
global counter
global sum
counter = 1 # 计数器
setYear = 2016 #设置下载年份
# for setYear in range(2004,2022):
main(setYear)
# print(f"{setYear}年年报下载完成")
06运行结果
在PyCharm上运行以上代码,结果如下:
重磅福利!为了更好地服务各位同学的研究,爬虫俱乐部将在小鹅通平台上持续提供金融研究所需要的各类指标,包括上市公司十大股东、股价崩盘、投资效率、融资约束、企业避税、分析师跟踪、净资产收益率、资产回报率、国际四大审计、托宾Q值、第一大股东持股比例、账面市值比、沪深A股上市公司研究常用控制变量等一系列深加工数据,基于各交易所信息披露的数据利用Stata在实现数据实时更新的同时还将不断上线更多的数据指标。我们以最前沿的数据处理技术、最好的服务质量、最大的诚意望能助力大家的研究工作!相关数据链接,请大家访问:(https://appbqiqpzi66527.h5.xiaoeknow.com/homepage/10)或扫描二维码:
对我们的推文累计打赏超过1000元,我们即可给您开具发票,发票类别为“咨询费”。用心做事,不负您的支持!
往期推文推荐机器学习——监督学习入门禁忌魔法解封,击穿专业壁垒:ChatGPT code interpreter暑期来啦~⼀起看⼀看近期的天⽓情况【命令重磅更新】在Stata中深入使用ChatGPT
爬虫俱乐部2023第一期编程训练营开始报名啦!
【爬虫基础】Scrapy爬虫框架迈向交互式编程,ChatGPT更新!一个简单又好玩的Python库——MyQR
replace命令的“加强版”!——如何使用ereplace,结合egen
XML 轻松读取:用 Python 发现数据宝藏
爬虫俱乐部重磅推出cnstata.com.cn
Markdown:让数学公式输入更方便!
处理日期的好手:pendulumWhat’ new ? 速通Stata 18
【爬虫实战】Python爬取美食菜谱揭秘网络中心人物,你会是其中之一吗?考研之后,文科生需以“do”躬“do”! 关于我们微信公众号“Stata and Python数据分析”分享实用的Stata、Python等软件的数据处理知识,欢迎转载、打赏。我们是由李春涛教授领导下的研究生及本科生组成的大数据处理和分析团队。
武汉字符串数据科技有限公司一直为广大用户提供数据采集和分析的服务工作,如果您有这方面的需求,请发邮件到statatraining@163.com,或者直接联系我们的数据中台总工程司海涛先生,电话:18203668525,wechat: super4ht。海涛先生曾长期在香港大学从事研究工作,现为知名985大学的博士生,爬虫俱乐部网络爬虫技术和正则表达式的课程负责人。
此外,欢迎大家踊跃投稿,介绍一些关于Stata和Python的数据处理和分析技巧。