盧明俊 盧守東
摘要:互聯(lián)網(wǎng)中的許多網(wǎng)站均已采用了Ajax技術(shù),相應(yīng)的Ajax頁面中的數(shù)據(jù)是通過異步加載的方式予以呈現(xiàn)的,并未包含在頁面源代碼中,因此難以對其進(jìn)行采集。針對此問題,介紹一種基于數(shù)據(jù)接口的Ajax頁面數(shù)據(jù)采集技術(shù),并通過具體的實(shí)例說明其編程模式。
關(guān)鍵詞:數(shù)據(jù)采集;Ajax;數(shù)據(jù)接口;Python
中圖分類號:TP311? ? ? 文獻(xiàn)標(biāo)識碼:A
文章編號:1009-3044(2024)07-0046-03
開放科學(xué)(資源服務(wù))標(biāo)識碼(OSID)
0 引言
如今已進(jìn)入大數(shù)據(jù)時(shí)代,對于各類大量數(shù)據(jù)的需求日趨緊迫,網(wǎng)絡(luò)爬蟲的應(yīng)用也日益廣泛。眾所周知,網(wǎng)絡(luò)爬蟲的主要功能就是按照一定的規(guī)則從互聯(lián)網(wǎng)中有關(guān)網(wǎng)站的相關(guān)頁面中自動(dòng)采集所需要的數(shù)據(jù)[1],其設(shè)計(jì)要點(diǎn)在于獲取并解析頁面的源代碼。顯然,這就要求頁面源代碼中必須包含相應(yīng)的數(shù)據(jù)。然而,目前許多網(wǎng)站均已采用了Ajax技術(shù),相應(yīng)的Ajax頁面中的數(shù)據(jù)是通過異步加載的方式予以呈現(xiàn)的,并未包含在頁面源代碼中,從而為其數(shù)據(jù)的采集帶來了極大的困難。在此,將以Windows 7+Python 3.8.18為開發(fā)環(huán)境,介紹一種基于數(shù)據(jù)接口的Ajax頁面數(shù)據(jù)采集技術(shù),供大家參考。
1 Ajax簡介
Ajax即異步JavaScript和XML(Asynchronous JavaScript and XML) ,是一種創(chuàng)建交互式網(wǎng)頁應(yīng)用的開發(fā)技術(shù),可在不刷新整個(gè)頁面的情況下實(shí)現(xiàn)頁面的局部更新。Ajax是HTML/XHTML、CSS、DOM、XML、XMLHttpRequest與JavaScript等多種相關(guān)技術(shù)的融合,其關(guān)鍵之處在于使用XMLHttpRequest對象發(fā)送異步請求,待服務(wù)器返回響應(yīng)后再自動(dòng)進(jìn)行后續(xù)處理(如更新頁面等)[2]。
XMLHttpRequest對象具有open()、setRequestHeader()、send()等方法。其中,open()方法用于以GET或POST方式創(chuàng)建請求,setRequestHeader()方法用于設(shè)置標(biāo)頭,send()方法用于發(fā)送請求(對于POST方式的請求,必要時(shí)還可指定相應(yīng)的參數(shù))。
XMLHttpRequest對象具有readyState、status、responseText、responseXML、onreadystatechange、onload等屬性。其中,readyState屬性用于獲知對象的狀態(tài)(其中4表示請求已完成),status屬性用于獲知返回的HTTP狀態(tài)碼(其中200表示請求成功),responseText與responseXML屬性用于獲取服務(wù)器的響應(yīng)(前者為字符串形式,后者為XML形式),onreadystatechange屬性用于指定對象狀態(tài)改變時(shí)所調(diào)用的函數(shù),onload屬性用于指定響應(yīng)已就緒時(shí)所調(diào)用的函數(shù)。
對于Ajax頁面來說,一旦發(fā)生某個(gè)事件,便通過頁面中的JavaScript腳本創(chuàng)建XMLHttpRequest對象,并利用該對象發(fā)送相應(yīng)的異步請求,同時(shí)在服務(wù)器返回響應(yīng)后再通過該對象指定的函數(shù)進(jìn)行處理,最終將有關(guān)數(shù)據(jù)更新至當(dāng)前頁面中。
除了原生的JavaScript方式外,Ajax功能還可能通過jQuery、DWR等有關(guān)框架實(shí)現(xiàn),其基本原理是一樣[3]。
2 Ajax頁面數(shù)據(jù)采集的基本方案
基于Ajax技術(shù)的特點(diǎn),Ajax頁面數(shù)據(jù)采集的基本方案如下:
1) 對Ajax頁面進(jìn)行分析,確定與所要采集的數(shù)據(jù)相對應(yīng)的數(shù)據(jù)接口,即相應(yīng)異步請求的請求方法(GET或POST) 、URL、參數(shù)及其規(guī)律,以及服務(wù)器返回的響應(yīng)數(shù)據(jù)的格式。
2) 按照數(shù)據(jù)接口的規(guī)范,構(gòu)造并發(fā)送相應(yīng)的請求,獲取返回的響應(yīng)數(shù)據(jù)。
3) 對返回的響應(yīng)數(shù)據(jù)進(jìn)行解析,最終獲取所要采集的具體數(shù)據(jù)。
3 Ajax頁面數(shù)據(jù)采集的關(guān)鍵技術(shù)
3.1 Ajax頁面數(shù)據(jù)接口的分析
目前,Chrome、360等瀏覽器均帶有相應(yīng)的開發(fā)者或開發(fā)人員工具,可用于對Ajax頁面的數(shù)據(jù)接口進(jìn)行分析。以Chrome瀏覽器為例,先打開其開發(fā)者工具,單擊Network選項(xiàng)卡及其中的Fetch/XHR選項(xiàng)卡,然后再打開相應(yīng)的Ajax頁面并進(jìn)行有關(guān)操作,即可查看到相應(yīng)的異步請求(其Type為xhr) 。單擊某個(gè)異步請求,即可通過右側(cè)的Headers選項(xiàng)卡查看其詳細(xì)信息,包括URL、請求方法(Request Method) 、請求標(biāo)頭(Request Headers) 與響應(yīng)標(biāo)頭(Response Headers) 等。此外,通過Payload選項(xiàng)卡可查看到有關(guān)的參數(shù),通過Preview選項(xiàng)卡可查看到經(jīng)過解析的響應(yīng)內(nèi)容,通過Response選項(xiàng)卡可查看到真實(shí)的響應(yīng)數(shù)據(jù)。
3.2 請求的發(fā)送與響應(yīng)的獲取
在Python中,為發(fā)送HTTP請求并獲取其響應(yīng),可使用urllib或requests模塊[4]。其中,urllib為內(nèi)置模塊,無須安裝即可直接使用。而requests為第三方模塊,須另行安裝(可使用“pip install requests”命令)。其實(shí),二者的功能與用法基本一致,但后者更加便捷。
以requests模塊為例,如須發(fā)送GET請求或POST請求,可調(diào)用其get()或post()方法,基本格式為:
requests.get(url, params, args)
requests.post(url, data, json, args)
其中,URL用于指定請求的URL,params與data用于指定需要傳遞的有關(guān)參數(shù),JSON用于指定需要傳遞的JSON數(shù)據(jù),args則為headers、cookies、verify等其他參數(shù)。對于GET請求,也可將有關(guān)參數(shù)按照規(guī)定的格式直接附加在URL之后。
調(diào)用get()或post()方法發(fā)送請求后,將返回一個(gè)響應(yīng)對象。通過訪問該對象的text或content屬性,即可獲取相應(yīng)的響應(yīng)內(nèi)容(前者為字符串形式,主要用于獲取文本、網(wǎng)頁源代碼類的數(shù)據(jù);后者為二進(jìn)制形式,主要用于獲取圖片、音頻視頻類的數(shù)據(jù))。此外,必要時(shí)可訪問該對象的status_code屬性以獲取返回的HTTP狀態(tài)碼。例如:
import requests
url="http://www.baidu.com"
r=requests.get(url)
print(r.status_code)
print(r.text)
在此,調(diào)用requests.get()方法發(fā)送了一個(gè)URL為http://www.baidu.com的GET請求,并通過返回的響應(yīng)對象r的status_code與text屬性獲取相應(yīng)的HTTP狀態(tài)碼與響應(yīng)內(nèi)容,然后加以輸出。
3.3 響應(yīng)數(shù)據(jù)的解析
對于獲取到的響應(yīng)數(shù)據(jù),應(yīng)根據(jù)其類型與特點(diǎn),利用相應(yīng)的解析庫(如re、lxml、BeautifulSoup、JSON等)或技術(shù)進(jìn)行處理,從中解析出所要采集的最終數(shù)據(jù)。
目前,對于Ajax請求來說,其返回的響應(yīng)多為JSON數(shù)據(jù)。對于JSON數(shù)據(jù),可直接利用JSON庫進(jìn)行解析。JSON庫是Python的內(nèi)置庫,無須額外安裝[5]。通過調(diào)用JSON庫的loads()方法,即可將JSON字符串轉(zhuǎn)換為一個(gè)字典類型的Python對象。該方法的基本格式為:
json.loads(s)
其中,參數(shù)s用于指定需要轉(zhuǎn)換的JSON字符串。例如:
import json
person='{"name":"小明","sex":"男","age":18}'
pdict=json.loads(person)
print(pdict["name"],pdict["sex"],pdict["age"])
在此,person變量的值為包含一個(gè)人員姓名、性別與年齡信息的JSON字符串,通過調(diào)用json.loads()方法將其轉(zhuǎn)換為字典pdict后,即可方便地獲取并輸出該人員的有關(guān)信息。
其實(shí),requests模塊內(nèi)置有一個(gè)JSON解碼器,也可用于將請求返回的JSON數(shù)據(jù)轉(zhuǎn)換為Python字典。為此,只須直接調(diào)用請求返回的響應(yīng)對象的json()方法即可。
4 Ajax頁面數(shù)據(jù)采集的具體實(shí)例
央視網(wǎng)微博主頁(https://www.weibo.com/u/3266943013) 是一個(gè)典型的Ajax頁面,如圖1所示。在瀏覽器中打開該主頁,并使用開發(fā)者工具進(jìn)行分析,可知其中的微博數(shù)據(jù)是通過URL為https://www.weibo.com/ajax/statuses/mymblog的一系列異步GET請求以分頁的方式(每頁10條)返回的,其參數(shù)共有4個(gè),分別為uid、feature、page與since_id。其中,uid與feature是固定不變的,分別為3266943013(央視網(wǎng)微博的id) 與0;而page與since_id則是會(huì)按規(guī)律改變的,前者表示頁碼(1、2、3等),后者也用于實(shí)現(xiàn)分頁(對于第1頁,則無須指定或讓其值為空)。這些請求返回的響應(yīng)均為JSON字符串,其data鍵的值即為相應(yīng)的具體數(shù)據(jù)。其中,since_id鍵的值即為下一頁請求中since_id參數(shù)的值,list鍵的值則為微博數(shù)組。在該數(shù)組中,一個(gè)元素即為一條微博的數(shù)據(jù)。其中,created_at、reposts_count、comments_count、attitudes_count與text_raw鍵的值分為微博的發(fā)布時(shí)間、轉(zhuǎn)發(fā)數(shù)、評論數(shù)、點(diǎn)贊數(shù)與標(biāo)題文本。此外,isLongText鍵的值為true或false,用于表示標(biāo)題文本是否為長文本;mblogid鍵的值則為微博的一個(gè)唯一id值。
若微博的標(biāo)題文本為長文本(即內(nèi)容較多),則在瀏覽器中只會(huì)顯示其前面的部分內(nèi)容,同時(shí)在末尾顯示一個(gè)“展開”鏈接。單擊此鏈接,方可顯示完整的標(biāo)題文本。實(shí)際上,在單擊“展開”鏈接時(shí),將觸發(fā)一個(gè)URL為https://www.weibo.com/ajax/statuses/longtext的異步GET請求,其參數(shù)為id,參數(shù)值則為相應(yīng)微博的mblogid值。該請求返回的響應(yīng)亦為JSON字符串,其data鍵的值即為相應(yīng)的具體數(shù)據(jù)。其中,longTextContent鍵的值即為微博的完整標(biāo)題文本。
根據(jù)以上分析結(jié)果,即可編程實(shí)現(xiàn)央視網(wǎng)微博數(shù)據(jù)的采集,代碼如下:
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="......"? #在此應(yīng)指定真正的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("央視網(wǎng)微博.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!")
運(yùn)行該程序,所采集到的共5頁的央視網(wǎng)微博的有關(guān)數(shù)據(jù)將以“||”為分隔添加到文本文件“央視網(wǎng)微博.txt”中,如圖2所示。
5 結(jié)束語
Ajax頁面的實(shí)現(xiàn)方式千差萬別,且其源代碼中并沒有通過異步請求返回并經(jīng)JavaScript動(dòng)態(tài)渲染至頁面中的數(shù)據(jù)。因此,相對于常規(guī)頁面來說,Ajax頁面數(shù)據(jù)的采集無疑是較為困難的。不過,基于Ajax的基本原理,只要分析清楚并與所要采集的數(shù)據(jù)相對應(yīng)的數(shù)據(jù)接口,即可通過模擬發(fā)送有關(guān)請求并獲取其響應(yīng)數(shù)據(jù)的方式,最終采集到所需要的具體數(shù)據(jù)。
參考文獻(xiàn):
[1] 盧守東,盧明俊.基于Selenium的網(wǎng)站自動(dòng)登錄技術(shù)[J].電腦知識與技術(shù),2023,19(34):48-51.
[2] 盧守東.Java EE應(yīng)用開發(fā)案例教程[M].北京:清華大學(xué)出版社,2017.
[3] 盧守東.jQuery程序設(shè)計(jì)實(shí)例教程[M].北京:清華大學(xué)出版社,2021.
[4] 夏敏捷.Python爬蟲超詳細(xì)實(shí)戰(zhàn)攻略[M].北京:清華大學(xué)出版社,2021.
[5] Python | import json模塊詳解[EB/OL].[2023-01-26].https://www.cnblogs.com/zhangxuegold/p/17497618.html.
【通聯(lián)編輯:謝媛媛】