博客 / 詳情

返回

爬蟲:越滑越多的動態網頁列表流數據(通過 Ajax 獲取微博個性化推薦內容)

在瀏覽社交媒體時,我們所看的內容彷彿是無窮無盡的。

我們常常滑動到頁面底端,以為沒有內容了,卻發現新的內容又一下子刷新出來。內容越滑越多,這種數據被稱作列表流數據。

有趣的是,當頁面不斷為我們提供新的內容時,網頁卻還是原來的網頁——URL 並沒有改變。這是怎麼回事?

列表流數據示例:微博熱門內容

1 Ajax

在同一個頁面中,網頁是如何源源不斷的展現新內容的呢?

如果打開瀏覽器的開發者模式,當我們滑動到頁面底端時,我們可以在 “網絡” 選項卡中觀測到一些新生成的 xhr 類型條目。這類條目中包含的就是 Ajax 請求。根據崔慶才老師的介紹:

Ajax,全稱為 Asynchronous JavaScript and XML,即異步的 JavaScript 和 XML。它不是一門編程語言,而是利用 JavaScript 在保證頁面不被刷新、頁面鏈接不改變的情況下與服務器交換數據並更新部分網頁的技術。

對於傳統的網頁,如果想更新其內容,那麼必須要刷新整個頁面,但有了 Ajax,便可以在頁面不被全部刷新的情況下更新其內容。在這個過程中,頁面實際上是在後台與服務器進行了數據交互,獲取到數據之後,再利用 JavaScript 改變網頁,這樣網頁內容就會更新了。

開發者模式下的 xhr 條目

簡單來説,當我們向下滑動到頁面底端,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 請求

xhr 標頭

除了標頭以外,我們還可以觀察到該條 Ajax 請求的預覽,這是一個 JSON 文件。

xhr 預覽

可以看到,該條請求中包含 10 條內容,如果深入觀察一條內容內部,我們可以找到這條內容對應的微博信息,包括微博 id、發佈用户、微博文字內容、微博圖片內容、發佈該條微博的 IP 地址信息等。

xhr 預覽-內部

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 數據爬取(崔慶才)
注:轉載請註明出處。
user avatar aloudata 頭像 qutianhang 頭像 hjava 頭像 u_16099302 頭像
4 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.