在瀏覽社交媒體時,我們所看的內容彷彿是無窮無盡的。
我們常常滑動到頁面底端,以為沒有內容了,卻發現新的內容又一下子刷新出來。內容越滑越多,這種數據被稱作列表流數據。
有趣的是,當頁面不斷為我們提供新的內容時,網頁卻還是原來的網頁——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)
參考
- Python3 網絡爬蟲開發實戰-6 Ajax 數據爬取(崔慶才)
注:轉載請註明出處。