分析Ajax爬取今日頭條街拍美圖

崔慶才丨靜覓發表於2018-04-04

本節中,我們以今日頭條為例來嘗試通過分析Ajax請求來抓取網頁資料的方法。這次要抓取的目標是今日頭條的街拍美圖,抓取完成之後,將每組圖片分資料夾下載到本地並儲存下來。

1. 準備工作

在本節開始之前,請確保已經安裝好requests庫。如果沒有安裝,可以自行查閱 。

2. 抓取分析

在抓取之前,首先要分析抓取的邏輯。開啟今日頭條的首頁http://www.toutiao.com/,如下圖所示。

分析Ajax爬取今日頭條街拍美圖

右上角有一個搜尋入口,這裡嘗試抓取街拍美圖,所以輸入“街拍”二字搜尋一下,結果如下圖所示。

分析Ajax爬取今日頭條街拍美圖

這時開啟開發者工具,檢視所有的網路請求。首先,開啟第一個網路請求,這個請求的URL就是當前的連結http://www.toutiao.com/search/?keyword=街拍,開啟Preview選項卡檢視Response Body。如果頁面中的內容是根據第一個請求得到的結果渲染出來的,那麼第一個請求的原始碼中必然會包含頁面結果中的文字。為了驗證,我們可以嘗試搜尋一下搜尋結果的標題,比如“路人”二字,如下圖所示。

分析Ajax爬取今日頭條街拍美圖

我們發現,網頁原始碼中並沒有包含這兩個字,搜尋匹配結果數目為0。因此,可以初步判斷這些內容是由Ajax載入,然後用JavaScript渲染出來的。接下來,我們可以切換到XHR過濾選項卡,檢視一下有沒有Ajax請求。

不出所料,此處出現了一個比較常規的Ajax請求,看看它的結果是否包含了頁面中的相關資料。

點選data欄位展開,發現這裡有許多條資料。點選第一條展開,可以發現有一個title欄位,它的值正好就是頁面中第一條資料的標題。再檢查一下其他資料,也正好是一一對應的,如下圖所示。

分析Ajax爬取今日頭條街拍美圖

這就確定了這些資料確實是由Ajax載入的。

我們的目的是要抓取其中的美圖,這裡一組圖就對應前面data欄位中的一條資料。每條資料還有一個image_detail欄位,它是列表形式,這其中就包含了組圖的所有圖片列表,如下圖所示。

分析Ajax爬取今日頭條街拍美圖

因此,我們只需要將列表中的url欄位提取出來並下載下來就好了。每一組圖都建立一個資料夾,資料夾的名稱就為組圖的標題。

接下來,就可以直接用Python來模擬這個Ajax請求,然後提取出相關美圖連結並下載。但是在這之前,我們還需要分析一下URL的規律。

切換回Headers選項卡,觀察一下它的請求URL和Headers資訊,如下圖所示。

分析Ajax爬取今日頭條街拍美圖

可以看到,這是一個GET請求,請求URL的引數有offsetformatkeywordautoloadcountcur_tab。我們需要找出這些引數的規律,因為這樣才可以方便地用程式構造出來。

接下來,可以滑動頁面,多載入一些新結果。在載入的同時可以發現,Network中又出現了許多Ajax請求,如下圖所示。

分析Ajax爬取今日頭條街拍美圖

這裡觀察一下後續連結的引數,發現變化的引數只有offset,其他引數都沒有變化,而且第二次請求的offset值為20,第三次為40,第四次為60,所以可以發現規律,這個offset值就是偏移量,進而可以推斷出count引數就是一次性獲取的資料條數。因此,我們可以用offset引數來控制資料分頁。這樣一來,我們就可以通過介面批量獲取資料了,然後將資料解析,將圖片下載下來即可。

3. 實戰演練

我們剛才已經分析了一下Ajax請求的邏輯,下面就用程式來實現美圖下載吧。

首先,實現方法get_page()來載入單個Ajax請求的結果。其中唯一變化的引數就是offset,所以我們將它當作引數傳遞,實現如下:

import requests
from urllib.parse import urlencode

def get_page(offset):
    params = {
        'offset': offset,
        'format': 'json',
        'keyword': '街拍',
        'autoload': 'true',
        'count': '20',
        'cur_tab': '1',
    }
    url = 'http://www.toutiao.com/search_content/?' + urlencode(params)
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.json()
    except requests.ConnectionError:
        return None複製程式碼

這裡我們用urlencode()方法構造請求的GET引數,然後用requests請求這個連結,如果返回狀態碼為200,則呼叫responsejson()方法將結果轉為JSON格式,然後返回。

接下來,再實現一個解析方法:提取每條資料的image_detail欄位中的每一張圖片連結,將圖片連結和圖片所屬的標題一併返回,此時可以構造一個生成器。實現程式碼如下:

def get_images(json):
    if json.get('data'):
        for item in json.get('data'):
            title = item.get('title')
            images = item.get('image_detail')
            for image in images:
                yield {
                    'image': image.get('url'),
                    'title': title
                }複製程式碼

接下來,實現一個儲存圖片的方法save_image(),其中item就是前面get_images()方法返回的一個字典。在該方法中,首先根據itemtitle來建立資料夾,然後請求這個圖片連結,獲取圖片的二進位制資料,以二進位制的形式寫入檔案。圖片的名稱可以使用其內容的MD5值,這樣可以去除重複。相關程式碼如下:

import os
from hashlib import md5

def save_image(item):
    if not os.path.exists(item.get('title')):
        os.mkdir(item.get('title'))
    try:
        response = requests.get(item.get('image'))
        if response.status_code == 200:
            file_path = '{0}/{1}.{2}'.format(item.get('title'), md5(response.content).hexdigest(), 'jpg')
            if not os.path.exists(file_path):
                with open(file_path, 'wb') as f:
                    f.write(response.content)
            else:
                print('Already Downloaded', file_path)
    except requests.ConnectionError:
        print('Failed to Save Image')複製程式碼

最後,只需要構造一個offset陣列,遍歷offset,提取圖片連結,並將其下載即可:

from multiprocessing.pool import Pool

def main(offset):
    json = get_page(offset)
    for item in get_images(json):
        print(item)
        save_image(item)

GROUP_START = 1
GROUP_END = 20

if __name__ == '__main__':
    pool = Pool()
    groups = ([x * 20 for x in range(GROUP_START, GROUP_END + 1)])
    pool.map(main, groups)
    pool.close()
    pool.join()複製程式碼

這裡定義了分頁的起始頁數和終止頁數,分別為GROUP_STARTGROUP_END,還利用了多執行緒的執行緒池,呼叫其map()方法實現多執行緒下載。

這樣整個程式就完成了,執行之後可以發現街拍美圖都分資料夾儲存下來了,如下圖所示。

分析Ajax爬取今日頭條街拍美圖

最後,我們給出本節的程式碼地址:https://github.com/Python3WebSpider/Jiepai。

通過本節,我們瞭解了Ajax分析的流程、Ajax分頁的模擬以及圖片的下載過程。

本節的內容需要熟練掌握,在後面的實戰中我們還會用到很多次這樣的分析和抓取。


本資源首發於崔慶才的個人部落格靜覓: Python3網路爬蟲開發實戰教程 | 靜覓

如想了解更多爬蟲資訊,請關注我的個人微信公眾號:進擊的Coder

weixin.qq.com/r/5zsjOyvEZ… (二維碼自動識別)


相關文章