前段时间介绍了selenium模拟爬虫,应对很多需要动态加载或鼠标点击的网页都非常有效,本期介绍一个js动态加载的网页爬虫。正好笔者也很久没有写爬虫了,当是练练手。目标定为新浪财经,获取上证指数成分股近3年的利润表数据,爬取完成后保存到本地csv中。
目录
先导入一些必要模块:
import pandas as pd
import requests
import re
1. 获取所有公司列表
打开新浪的行情中心,选择相关指数即可以查看所有成分股:行情中心_新浪财经。如图,每页如果显示80条,一共有25页,利用循环遍历爬取是没跑了。
新浪的难点在于翻页是通过js请求完成的,也就是说无论点击哪一页,浏览器上显示的网址都是一样的。笔者通过Telerik fiddler工具抓取到翻页时的网址如下:
url = "https://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php/Market_Center.getHQNodeDataSimple?page=1&num=80&sort=symbol&asc=1&node=zhishu_000001&_s_r_a=page"
当然,键盘F12在网络里一条条找访问记录也是可以找到这个网址的:
观察该网址不难发现,"page="的字段就是翻页的参数。"num="的字段就是每页显示多少条记录的参数。
下面改写一下,加入翻页循环。利用正则表达式匹配公司代码最后存进表格:
stock_list = []
for i in range(1, 26):
url = "https://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php/Market_Center.getHQNodeDataSimple?page="+str(i)+"&num=80&sort=symbol&asc=1&node=zhishu_000001&_s_r_a=page"
response = requests.get(url).text
symbol = re.findall('"symbol":"(.*?)","name"',response,re.S)
stock_list.extend(symbol)
print(stock_list[:5])
print("共获取:", len(stock_list))
# ['sh600000', 'sh600004', 'sh600006', 'sh600007', 'sh600008']
# 共获取: 2000
共获取到2000家公司代码。
2. 爬取单个公司历史财务数据
一样先观察网址,笔者找到一个较为容易的网址结构:http://money.finance.sina.com.cn/corp/go.php/vFD_ProfitStatement/stockid/600585/ctrl/2022/displaytype/4.phtml
新浪这个网址不是通过js请求的,可以直接在浏览器上复制粘贴,只要改改公司代码和年份即可,内容属于可见即可爬的表格:
url = "http://money.finance.sina.com.cn/corp/go.php/vFD_ProfitStatement/stockid/600000/" \
"ctrl/2022/displaytype/4.phtml"
df = pd.read_html(url, attrs = {"id": "ProfitStatementNewTable0"}, header=[1], index_col=0)[0]
df.dropna(axis=1, how="all", inplace=True)
print(df)
2022-09-30 2022-06-30 2022-03-31
报表日期
NaN NaN NaN NaN
一、营业收入 14368000.00 9864400.00 5000200.00
利息净收入 10159200.00 6868100.00 3450200.00
其中:利息收入 22503100.00 15027800.00 7533200.00
减:利息支出 12343900.00 8159700.00 4083000.00
...
但这样只能请求到三期的财报,如果要更多期就需要循环遍历了,不过也很简单:
url = "http://money.finance.sina.com.cn/corp/go.php/vFD_ProfitStatement/stockid/600000/ctrl/2022/displaytype/4.phtml"
df = pd.read_html(url, attrs = {"id": "ProfitStatementNewTable0"}, header=[1], index_col=0)[0]
df.dropna(axis=1, how="all", inplace=True)
for i in range(1, 4):
url = "http://money.finance.sina.com.cn/corp/go.php/vFD_ProfitStatement/stockid/600000/ctrl/"+str(2022-i)+"/displaytype/4.phtml"
df_1 = pd.read_html(url, attrs = {"id": "ProfitStatementNewTable0"}, header=[1], index_col=0)[0]
df_1.dropna(axis=1, how="all", inplace=True)
df = pd.concat([df, df_1], axis=1)
df = df.dropna(how="all")
print(df)
报表日期 2022-09-30 2022-06-30 2022-03-31 2021-12-31 2021-09-30 2021-06-30 2021-03-31 2020-12-31 2020-09-30 2020-06-30 2020-03-31 2019-12-31 2019-09-30 2019-06-30 2019-03-31
一、营业收入 14368000.00 9864400.00 5000200.00 19098200.00 14348400.00 9736500.00 4952200.00 19638400.00 14873100.00 10140700.00 5542400.00 19068800.00 14638600.00 9759900.00 5008400.00
利息净收入 10159200.00 6868100.00 3450200.00 13595800.00 10138900.00 6766200.00 3367200.00 13858100.00 9145600.00 6187500.00 3209600.00 12885000.00 9782200.00 6426400.00 3144600.00
3. 批量爬取历史数据
将上面的代码改写成函数传参,遍历所有的公司代码即可。封装好后加入一些进度显示,时间计算的代码,一个精致的利润表数据获取程序就完成了:
def craw_list():
stock_list = []
for i in range(1, 26):
url = "https://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php/Market_Center.getHQNodeDataSimple?page="+str(i)+"&num=80&sort=symbol&asc=1&node=zhishu_000001&_s_r_a=page"
response = requests.get(url).text
symbol = re.findall('"symbol":"(.*?)","name"',response,re.S)
stock_list.extend(symbol)
return stock_list
def craw_mod(code, year):
url = "http://money.finance.sina.com.cn/corp/go.php/vFD_ProfitStatement/stockid/"+code+"/ctrl/2022/displaytype/4.phtml"
df = pd.read_html(url, attrs = {"id": "ProfitStatementNewTable0"}, header=[1], index_col=0)[0]
df.dropna(axis=1, how="all", inplace=True)
for i in range(1, year):
url = "http://money.finance.sina.com.cn/corp/go.php/vFD_ProfitStatement/stockid/"+code+"/ctrl/"+str(2022-i)+"/displaytype/4.phtml"
df_1 = pd.read_html(url, attrs = {"id": "ProfitStatementNewTable0"}, header=[1], index_col=0)[0]
df_1.dropna(axis=1, how="all", inplace=True)
df = pd.concat([df, df_1], axis=1)
df.dropna(how="all")
df.to_csv(code + ".csv") # 存储路径
def main():
stock_list = craw_list()
for code in stock_list:
craw_mod(code[2:], 3) # code前两个字符为"sh",要去掉
print("爬取进度{}%\r".format('%.2f'% (100*(stock_list.index(code) + 1)/len(stock_list))), end = "")
if __name__=="__main__":
import datetime
start = datetime.datetime.now()
main()
end = datetime.datetime.now()
print("程序耗时:", end - start)
当然,用多线程或者多进程辅助加速也是可以的,这里就不进行展示了。
在刚刚的网址中找到资产负债表、现金流量表的网址链接就可以进一步把其它两张表也爬下来,都是一样的操作,换换网址而已,笔者也不进行更多展示了。
最后都保存到了本地: