爬一个AJAX加载的网页
目前多数网站都不会将数据直接放在HTML里,而是采用异步加载的方式,原始页面不包含数据,只是一些样式,在页面加载完后,向服务器发送AJAX请求,从其他接口获取数据,处理后在页面上展示。
一些官方的网站数据,有不少是采用这种方式,比较典型的就是外汇交易中心里的一些数据,下面拿每日活跃债券统计这个功能做个例子。
如何看是不是AJAX请求

这种网站大家一般都见过,在左上角选择需要查询的日期,然后会显示出所需的数据,但是浏览器上地址栏中网址并没有变化。那在选择日期的时候到底发生了什么?可以通过开发者工具来进行分析。
Chorme
自带开发者工具,
Chorme
内核的Edge也包含此工具,感觉两个应该是一样的。在打开上面的页面后,按
F12
,打开开发者工具,就会看到图2的界面。
如果是
Chorme
可能是全英文的,Edge会有中文的显示。开发者工具里,最上面选择
网络
(官方的
Chorme
是
Network
),然后在下面筛选器的位置选择
XHR
,因为
AJAX
的请求类型就是
XHR
。目前开发者工具中是空的,因为在打开此工具后有任何操作,没有产生请求。
现在回到CFETS的网页上,换一个日期,就会发现开发者工具中多了一项
AtbDlyBltn
,类型为
xhr
,这就是点击查询后产生的
AJAX
请求。
单击这个请求后,查看详细信息,可以看到右边的
请求URL
为
http://www.chinamoney.com.cn/dqs/rest/dqs-u-bond/AtbDlyBltn
,请求方式为
POST
。在请求标头部分,
X-Requested-With: XMLHttpRequest
标记了这个请求就是
AJAX
。最下面的表单数据里有POST的具体数据:
lang: cn
和
searchDate: 2020-09-29
。再看右边的预览,这里是从请求获取的数据,其中
records
部分就是网页中显示的活跃债券的信息。(官方的
CHORME
是英文的,但顺序都是一样的,英文看一下也都看得明白。)
请求结果的提取
对这部分数据的请求是向
http://www.chinamoney.com.cn/dqs/rest/dqs-u-bond/AtbDlyBltn
POST日期和语言,
python
里的
requests
就可以完成模拟请求。代码如下。
前面就是一些基本的变量,
r = requests.post(BASE_URL, data=post_data)
发送请求的部分,取回的结果即在
r.text
中,是标准的
json
格式,我们所需要的成交数据就在
records
的部分,所以使用
python
中的
json
包,将其转为字典,字典中
records
的值为一个包含当日前十成交量债券数据的列表,列表中的每一个元素都是一个字典,对应网页中表格的一行。
如果要批量爬取数据,可以用
pandas
先生成一个包含所有日期的列表,然后逐日重复上面的爬取过程,而数据的存储比较简便的方式就是利用
pandas
存储到
Excel
里。具体代码如下。
加入
time.sleep(3)
的目的是为了防止访问过快被封IP,具体怎么个屏蔽规则我也不敢试,反正在用另一种方法爬这个页面的时候把公司的IP弄屏蔽了,同事也都看不了这个页面了,好在一天之后就恢复了。另一种方式最后会介绍。
另一个例子:爬中金所国债期货持仓数据
打开中金所的国债期货成交排名,一看这个页面就和CFETS的很像,打开网页源代码,搜索一下永安,果然也是没有,按照上面的操作看一下请求,果然也是
AJAX
的情况。
但是和CFETS不同的是,中金所的网站是请求方式是
GET
,而CFETS的是
POST
,还有就是在具体请求的URL里最后还包含了一个
id=
,后面会有一个数字,换了不同日期后,这个数字会改变,再换回原来的日期,还是会产生一个不同的数字,因此是一个与日期没有对应关系的随机数。但是不要紧,经过测试,随便写个数字应该就行。。。
具体的代码如下。
首先,和之前不一样的是
BASE_URL
,里面多了两个
{}
。我们再看两个请求的URL,下面两个图一个是请求2020年8月18日的T合约,一个是请求2020年9月10日的T合约,可以看到两个括号位置分别对应年份+月份和日期。将
BASE_URL
写成这种形式方便之后在批量请求时利用
format
来补充相关日期信息。
接下来就是
header
字典,这部分是请求头,与开发者工具中下图部分对应。
再下面就是请求,这里的用
requests.get
替代了之前的
requests.post
,因为中金所的网站的请求方式是
GET
。
BASE_URL.format(date[:6], date[6:])
就是把日期分成两个部分,分别填到
{}
的位置,形成一个与日期对应的URL,而第二个参数
headers=header
,是将请求头信息加入到请求中,其实在这个例子里并没有实际的作用,加不加都能获取数据,其他的网站可能会验证请求头,如果确实一些必要的信息,例如
User-Agent
、
Referer
、
Cookies
等,则会限制访问。在之后就是数据处理的过程,与请求数据无关,因为中金所返回的数据不是
json
而是
xml
,需要利用
xml
模块进行解析,然后转换成字典,写入
DataFrame
后保存。具体批量爬取的代码就不放了,思路和上面是一样的。这是爬取T合约的方法,要是爬取TF和TS,需要调整URL。
另一个被限制了的方法
这个方法是最开始不会上面方法时候用到的,简单粗暴,中金所似乎没有限制,但是外汇交易中心应该是限制了的,就是利用页面自带的导出功能。
中金所持仓数据下面有个
Excel
附件,链接地址是
http://www.cffex.com.cn/sj/ccpm/202009/30/T_1.csv
,链接也是一样的规律,日期分成了两个部分,用
requests.get
是可以下载下来的,然后将这些下载下来的
csv
文件用
pandas
等再处理一下,也可以得到数据。在
get
中是否需要
headers
,具体需要包含什么,我也没有具体试。而CFETS导出到excel后链接地址是
http://www.chinamoney.com.cn/dqs/rest/dqs-u-bond/AtbDlyBltnExcel?lang=cn&searchDate=2020-09-30
,地址的规律也都可以看出来,也可以下载下来,但是下了几个后,我就被屏蔽了,不知道是不是因为太快了,之前没有加入
sleep
。