在瀏覽社交媒體時,我們所看的內容彷彿是無窮無盡的。
我們常常滑動到頁面底端,以為沒有內容了,卻發現新的內容又一下子重新整理出來。內容越滑越多,這種資料被稱作列表流資料。
有趣的是,當頁面不斷為我們提供新的內容時,網頁卻還是原來的網頁——URL 並沒有改變。這是怎麼回事?
1 Ajax
在同一個頁面中,網頁是如何源源不斷的展現新內容的呢?
如果開啟瀏覽器的開發者模式,當我們滑動到頁面底端時,我們可以在 “網路” 選項卡中觀測到一些新生成的 xhr 型別條目。這類條目中包含的就是 Ajax 請求。根據崔慶才老師的介紹:
Ajax,全稱為 Asynchronous JavaScript and XML,即非同步的 JavaScript 和 XML。它不是一門程式語言,而是利用 JavaScript 在保證頁面不被重新整理、頁面連結不改變的情況下與伺服器交換資料並更新部分網頁的技術。
對於傳統的網頁,如果想更新其內容,那麼必須要重新整理整個頁面,但有了 Ajax,便可以在頁面不被全部重新整理的情況下更新其內容。在這個過程中,頁面實際上是在後臺與伺服器進行了資料互動,獲取到資料之後,再利用 JavaScript 改變網頁,這樣網頁內容就會更新了。
簡單來說,當我們向下滑動到頁面底端,JavaScript 會向網頁後臺的伺服器傳送一個請求,告訴伺服器我們想要更多的內容。伺服器返回相應的響應內容,JavaScript 又對響應內容(不論是 HTML 格式,還是 JSON 格式)進行了解析,並渲染成為我們看到的新內容。
2 列表流資料的爬取:以微博為例
瞭解了列表流資料是如何源源不斷產生的後,我們就有了獲取這種資料的思路:模擬 JavaScript 向網頁伺服器傳送 Ajax 請求,並解析獲取到的響應資料。
下面來演示爬取過程——以爬取微博個性化推薦內容為例。
2.1 觀察 Ajax 請求
我們找到上文提到的 xhr 檔案,點選它,我們可以看到檔案包含的標頭資訊。其中相關的資訊包括:
- 請求 URL:如果你觀察多條 xhr 條目的話,你會發現它們的 URL 幾乎完全一樣,唯一不同之處在於 max_id 的值,從 1 開始,每從底部重新整理一次,新的 xhr 條目的 max_id 的值就會增加 1。
- 請求方法
- 狀態程式碼
- 請求標頭中的 cookie
- 請求標頭中的 user-agent
- 請求標頭中的 x-requested-with,它的值為 XMLHttpRequest,這標記了該條請求是 Ajax 請求
除了標頭以外,我們還可以觀察到該條 Ajax 請求的預覽,這是一個 JSON 檔案。
可以看到,該條請求中包含 10 條內容,如果深入觀察一條內容內部,我們可以找到這條內容對應的微博資訊,包括微博 id、釋出使用者、微博文字內容、微博圖片內容、釋出該條微博的 IP 地址資訊等。
2.2 爬取 Ajax 請求獲取的 JSON 資料
現在我們可以嘗試爬取上述資料了。首先匯入我們所需的包,並定義一些後續用得到的變數:
from urllib.parse import urlencode
import requests
from pyquery import PyQuery as pq
base_url = 'https://weibo.com/ajax/feed/hottimeline?' # 本專案要爬取的 url 都是以此為開頭的
headers = {
'User-Agent': '你的 User-Agent', # 點選你要爬取的 xhr 檔案,在標頭中可以找到相關資訊。
'X-Requested-With': 'XMLHttpRequest',
'Cookie': '你的 Cookie'
}
我們來構建一個函式,用於返回我們要傳送模擬請求的 URL:
def get_hottimeline_url(max_id):
'''
返回一個 xhr 型別 "hottimeline (熱榜時間流)" 的 url。
:param max_id: 每個 hottimeline url 中特殊的 max_id (1, 2, 3...)
'''
params = {
'refresh': '2',
'group_id': '你 URL 中的 group_id',
'containerid': '你 URL 中的 group_id containerid',
'extparam': 'discover%7Cnew_feed',
'max_id': max_id, # URL 中只有該值是變化的
'count': '10'
}
url = base_url + urlencode(params, safe='%') # 此處指明 safe 引數,使 "%" 不被轉義為 "%25",不然會拼接成錯誤的 URL
return url
接下來,我們像獲取靜態頁面的資料一樣,透過 request 傳送請求,獲取 JSON 資料:
def get_response_json(url):
'''
返回一個 url 的 json 檔案。
'''
response = requests.get(url, headers=headers)
json = response.json()
return json
2.3 解析 JSON 資料
獲取到 JSON 資料後,我們需要對資料進行解析,獲取我們所需的資訊。一個 URL 能返回 10 條微博內容,我們希望能迴圈得到每一條微博內容的資料,存為一個字典。再將這個字典,存入微博內容組成的列表中。
在獲取一條微博的文字內容時,我們需要注意,當這條微博的文字內容過長時,文字段會被摺疊。如果我們想看到完整的內容,需要在瀏覽器介面點選“展開”按鈕,這使得我們無法在現有的 JSON 資料中獲得完整的文字資料。但是,當我們點選展開時,可以看到開發者模式的網路選項卡中,又多出了名為 “longtext?id=xxxxxxxxxx” 的xhr條目,我們可以透過該條目的 URL 獲取到完整的長文字資料。
def parse_hottimeline(list_recommendation, json):
'''
解析一條 hottimeline 的 json 檔案,並將包含的 10 條熱榜推薦內容追加到內容列表中。
'''
for item in json.get('statuses'):
weibo = {} # 建立一個臨時的用於儲存一條微博資訊的字典
# user information
user_info = item.get('user')
weibo['user_id'] = user_info.get('id') # 釋出者 id
weibo['user_name'] = user_info.get('screen_name') # 釋出者暱稱
# weibo information
weibo['id'] = item.get('id') # 微博 id
weibo['isLongText'] = item.get('isLongText') # 該變數為 True 時,這個微博的文字為長文字(文字段會被摺疊)
weibo['mblogid'] = item.get('mblogid') # 可以透過該變數,索引到存有長文字的 JSON 檔案的 URL
if weibo['isLongText'] is True:
url = "https://weibo.com/ajax/statuses/longtext?id=" + weibo['mblogid']
json_longtext = get_response_json(url)
weibo['text'] = json_longtext.get('data').get('longTextContent')
else:
weibo['text'] = pq(item.get('text')).text()
weibo['pic_num'] = item.get('pic_num') # 該條微博包含的圖片數
weibo['pic'] = [] # 用於儲存該條微博圖片的 url
if weibo['pic_num'] > 0:
pic_dict = item.get('pic_infos')
for pic in pic_dict:
pic_url = pic_dict[pic]['original']['url']
weibo['pic'].append(pic_url)
else:
pass
weibo['attitudes'] = item.get('attitudes_count') # 點贊數
weibo['comments'] = item.get('comments_count') # 評論數
weibo['reposts'] = item.get('reposts_count') # 轉發數
region = item.get('region_name') # 釋出時的 IP 地址
if region is None:
weibo['region'] = region
else: weibo['region'] = region.strip('釋出於 ')
print(weibo)
list_recommendation.append(weibo) # 將解析出的一條微博資料,加入一個列表中
2.4 儲存資料
我們已經實現了頁面底部重新整理資料的 URL 獲取、模擬請求、解析資料的功能。最後,我們將新建一個列表,將每條微博資訊儲存進去。主函式的程式碼如下:
if __name__ == '__main__':
list_recommendation = []
for max_id in range(1, 11): # 模擬爬取 10 次重新整理結果,最終能獲取到 100 條個性化推薦熱門微博資料
hottimeline_url = get_hottimeline_url(max_id)
print('hottimeline_url = ', hottimeline_url)
response_json = get_response_json(hottimeline_url)
parse_hottimeline(list_recommendation, response_json)
參考
注:轉載請註明出處。