卢明俊 卢守东
摘要:互联网中的许多网站均已采用了Ajax技术,相应的Ajax页面中的数据是通过异步加载的方式予以呈现的,并未包含在页面源代码中,因此难以对其进行采集。针对此问题,介绍一种基于数据接口的Ajax页面数据采集技术,并通过具体的实例说明其编程模式。
关键词:数据采集;Ajax;数据接口;Python
中图分类号:TP311 文献标识码:A
文章编号:1009-3044(2024)07-0046-03
开放科学(资源服务)标识码(OSID)
0 引言
如今已进入大数据时代,对于各类大量数据的需求日趋紧迫,网络爬虫的应用也日益广泛。众所周知,网络爬虫的主要功能就是按照一定的规则从互联网中有关网站的相关页面中自动采集所需要的数据[1],其设计要点在于获取并解析页面的源代码。显然,这就要求页面源代码中必须包含相应的数据。然而,目前许多网站均已采用了Ajax技术,相应的Ajax页面中的数据是通过异步加载的方式予以呈现的,并未包含在页面源代码中,从而为其数据的采集带来了极大的困难。在此,将以Windows 7+Python 3.8.18为开发环境,介绍一种基于数据接口的Ajax页面数据采集技术,供大家参考。
1 Ajax简介
Ajax即异步JavaScript和XML(Asynchronous JavaScript and XML) ,是一种创建交互式网页应用的开发技术,可在不刷新整个页面的情况下实现页面的局部更新。Ajax是HTML/XHTML、CSS、DOM、XML、XMLHttpRequest与JavaScript等多种相关技术的融合,其关键之处在于使用XMLHttpRequest对象发送异步请求,待服务器返回响应后再自动进行后续处理(如更新页面等)[2]。
XMLHttpRequest对象具有open()、setRequestHeader()、send()等方法。其中,open()方法用于以GET或POST方式创建请求,setRequestHeader()方法用于设置标头,send()方法用于发送请求(对于POST方式的请求,必要时还可指定相应的参数)。
XMLHttpRequest对象具有readyState、status、responseText、responseXML、onreadystatechange、onload等属性。其中,readyState属性用于获知对象的状态(其中4表示请求已完成),status属性用于获知返回的HTTP状态码(其中200表示请求成功),responseText与responseXML属性用于获取服务器的响应(前者为字符串形式,后者为XML形式),onreadystatechange属性用于指定对象状态改变时所调用的函数,onload属性用于指定响应已就绪时所调用的函数。
对于Ajax页面来说,一旦发生某个事件,便通过页面中的JavaScript脚本创建XMLHttpRequest对象,并利用该对象发送相应的异步请求,同时在服务器返回响应后再通过该对象指定的函数进行处理,最终将有关数据更新至当前页面中。
除了原生的JavaScript方式外,Ajax功能还可能通过jQuery、DWR等有关框架实现,其基本原理是一样[3]。
2 Ajax页面数据采集的基本方案
基于Ajax技术的特点,Ajax页面数据采集的基本方案如下:
1) 对Ajax页面进行分析,确定与所要采集的数据相对应的数据接口,即相应异步请求的请求方法(GET或POST) 、URL、参数及其规律,以及服务器返回的响应数据的格式。
2) 按照数据接口的规范,构造并发送相应的请求,获取返回的响应数据。
3) 对返回的响应数据进行解析,最终获取所要采集的具体数据。
3 Ajax页面数据采集的关键技术
3.1 Ajax页面数据接口的分析
目前,Chrome、360等浏览器均带有相应的开发者或开发人员工具,可用于对Ajax页面的数据接口进行分析。以Chrome浏览器为例,先打开其開发者工具,单击Network选项卡及其中的Fetch/XHR选项卡,然后再打开相应的Ajax页面并进行有关操作,即可查看到相应的异步请求(其Type为xhr) 。单击某个异步请求,即可通过右侧的Headers选项卡查看其详细信息,包括URL、请求方法(Request Method) 、请求标头(Request Headers) 与响应标头(Response Headers) 等。此外,通过Payload选项卡可查看到有关的参数,通过Preview选项卡可查看到经过解析的响应内容,通过Response选项卡可查看到真实的响应数据。
3.2 请求的发送与响应的获取
在Python中,为发送HTTP请求并获取其响应,可使用urllib或requests模块[4]。其中,urllib为内置模块,无须安装即可直接使用。而requests为第三方模块,须另行安装(可使用“pip install requests”命令)。其实,二者的功能与用法基本一致,但后者更加便捷。
以requests模块为例,如须发送GET请求或POST请求,可调用其get()或post()方法,基本格式为:
requests.get(url, params, args)
requests.post(url, data, json, args)
其中,URL用于指定请求的URL,params与data用于指定需要传递的有关参数,JSON用于指定需要传递的JSON数据,args则为headers、cookies、verify等其他参数。对于GET请求,也可将有关参数按照规定的格式直接附加在URL之后。
调用get()或post()方法发送请求后,将返回一个响应对象。通过访问该对象的text或content属性,即可获取相应的响应内容(前者为字符串形式,主要用于获取文本、网页源代码类的数据;后者为二进制形式,主要用于获取图片、音频视频类的数据)。此外,必要时可访问该对象的status_code属性以获取返回的HTTP状态码。例如:
import requests
url="http://www.baidu.com"
r=requests.get(url)
print(r.status_code)
print(r.text)
在此,调用requests.get()方法发送了一个URL为http://www.baidu.com的GET请求,并通过返回的响应对象r的status_code与text属性获取相应的HTTP状态码与响应内容,然后加以输出。
3.3 响应数据的解析
对于获取到的响应数据,应根据其类型与特点,利用相应的解析库(如re、lxml、BeautifulSoup、JSON等)或技术进行处理,从中解析出所要采集的最终数据。
目前,对于Ajax请求来说,其返回的响应多为JSON数据。对于JSON数据,可直接利用JSON库进行解析。JSON库是Python的内置库,无须额外安装[5]。通过调用JSON库的loads()方法,即可将JSON字符串转换为一个字典类型的Python对象。该方法的基本格式为:
json.loads(s)
其中,参数s用于指定需要转换的JSON字符串。例如:
import json
person='{"name":"小明","sex":"男","age":18}'
pdict=json.loads(person)
print(pdict["name"],pdict["sex"],pdict["age"])
在此,person变量的值为包含一个人员姓名、性别与年龄信息的JSON字符串,通过调用json.loads()方法将其转换为字典pdict后,即可方便地获取并输出该人员的有关信息。
其实,requests模块内置有一个JSON解码器,也可用于将请求返回的JSON数据转换为Python字典。为此,只须直接调用请求返回的响应对象的json()方法即可。
4 Ajax页面数据采集的具体实例
央视网微博主页(https://www.weibo.com/u/3266943013) 是一个典型的Ajax页面,如图1所示。在浏览器中打开该主页,并使用开发者工具进行分析,可知其中的微博数据是通过URL为https://www.weibo.com/ajax/statuses/mymblog的一系列异步GET请求以分页的方式(每页10条)返回的,其参数共有4个,分别为uid、feature、page与since_id。其中,uid与feature是固定不变的,分别为3266943013(央视网微博的id) 与0;而page与since_id则是会按规律改变的,前者表示页码(1、2、3等),后者也用于实现分页(对于第1页,则无须指定或让其值为空)。这些请求返回的响应均为JSON字符串,其data键的值即为相应的具体数据。其中,since_id键的值即为下一页请求中since_id参数的值,list键的值则为微博数组。在该数组中,一个元素即为一条微博的数据。其中,created_at、reposts_count、comments_count、attitudes_count与text_raw键的值分为微博的发布时间、转发数、评论数、点赞数与标题文本。此外,isLongText键的值为true或false,用于表示标题文本是否为长文本;mblogid键的值则为微博的一个唯一id值。
若微博的标题文本为长文本(即内容较多),则在浏览器中只会显示其前面的部分内容,同时在末尾显示一个“展开”链接。单击此链接,方可显示完整的标题文本。实际上,在单击“展开”链接时,将触发一个URL为https://www.weibo.com/ajax/statuses/longtext的異步GET请求,其参数为id,参数值则为相应微博的mblogid值。该请求返回的响应亦为JSON字符串,其data键的值即为相应的具体数据。其中,longTextContent键的值即为微博的完整标题文本。
根据以上分析结果,即可编程实现央视网微博数据的采集,代码如下:
import requests
import json
import time
useragent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/ 109.0.0.0 Safari/537.36"
cookie="......" #在此应指定真正的cookie值
headers={"user-agent":useragent,"cookie":cookie}
url="https://www.weibo.com/ajax/statuses/mymblog"
uid="3266943013"
params={"uid":uid,"feature":0}
since_id=""
for i in range(5):
page=i+1
print("第"+str(page)+"页")
params["page"]=page
if page>=1:
params["since_id"]=since_id
r=requests.get(url,params=params,headers=headers)
j=json.loads(r.text)
d=j["data"]
since_id=d["since_id"]
list=d["list"]
for l in list:
created_at=l["created_at"]
reposts_count=l["reposts_count"]
comments_count=l["comments_count"]
attitudes_count=l["attitudes_count"]
title_text=l["text_raw"]
isLongText=l["isLongText"]
mblogid=l["mblogid"]
if isLongText:
url0="https://www.weibo.com/ajax/statuses/longtext"
params0={"id":mblogid}
r0=requests.get(url0, params=params0,headers=headers)
j0=json.loads(r0.text)
d0=j0["data"]
title_text=d0["longTextContent"]
with open("央視网微博.txt","a",encoding="utf8") as f:
s=created_at+"||"+str(reposts_count)+"||"+str(comments_count)
s+="||"+str(attitudes_count)+"||"+title_text+"\n"
f.write(s)
time.sleep(5)
print("OK!")
运行该程序,所采集到的共5页的央视网微博的有关数据将以“||”为分隔添加到文本文件“央视网微博.txt”中,如图2所示。
5 结束语
Ajax页面的实现方式千差万别,且其源代码中并没有通过异步请求返回并经JavaScript动态渲染至页面中的数据。因此,相对于常规页面来说,Ajax页面数据的采集无疑是较为困难的。不过,基于Ajax的基本原理,只要分析清楚并与所要采集的数据相对应的数据接口,即可通过模拟发送有关请求并获取其响应数据的方式,最终采集到所需要的具体数据。
参考文献:
[1] 卢守东,卢明俊.基于Selenium的网站自动登录技术[J].电脑知识与技术,2023,19(34):48-51.
[2] 卢守东.Java EE应用开发案例教程[M].北京:清华大学出版社,2017.
[3] 卢守东.jQuery程序设计实例教程[M].北京:清华大学出版社,2021.
[4] 夏敏捷.Python爬虫超详细实战攻略[M].北京:清华大学出版社,2021.
[5] Python | import json模块详解[EB/OL].[2023-01-26].https://www.cnblogs.com/zhangxuegold/p/17497618.html.
【通联编辑:谢媛媛】