【Python】Python3網絡爬蟲實戰-36、分析Ajax爬取今日頭條街拍美圖

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

1. 準備工作

在本節開始之前請確保已經安裝好了 Requests 庫,如沒有安裝可以參考第一章的安裝說明。

2. 抓取分析

在抓取之前我們首先要分析一下抓取的邏輯,首先打開今日頭條的首頁:http://www.toutiao.com/,如圖 6-15 所示:

圖 6-15 首頁內容
在右上角有一個搜索入口,在這裏我們嘗試抓取街拍美圖,所以輸入“街拍”二字搜索一下,結果圖 6-16 所示:

圖 6-16 搜索結果
這樣我們就跳轉到了搜索結果頁面。Python資源分享qun 784758214 ,內有安裝包,PDF,學習視頻,這裏是Python學習者的聚集地,零基礎,進階,都歡迎

這時打開開發者工具,查看一下所有網絡請求,我們首先打開第一個網絡請求,這個請求的 URL 就是當前的鏈接:http://www.toutiao.com/search...,打開 Preview 選項卡查看 Response Body,如果頁面中的內容是直接請求直接加載出來的,那麼這第一個請求的源代碼中必然包含了頁面結果中的文字,爲了驗證,我們可以嘗試嘗試搜索一下搜索結果的標題,比如“路人”二字,如圖 6-17 所示:

圖 6-17 搜索結果
然而發現網頁源代碼中並沒有包含這兩個字,搜索匹配結果數目爲 0。
所以我們就可以初步判斷出這些內容是由 Ajax 加載然後用JavaScript 渲染出來的,所以接下來我們可以切換到 XHR過濾選項卡查看一下有沒有 Ajax 請求。

不出所料,此處出現了一個比較常規的 Ajax 請求,觀察一下它的結果是否包含了頁面中的相關數據。
點擊 data 字段展開,發現這裏有許多條數據,我們點擊第一條繼續展開,可以發現有一個 title 字段,它的值正好就是頁面中的第一條數據的標題,再檢查一下其他的數據也正好是一一對應的,如圖 6-18 所示:


圖 6-18 對比結果
那這就確定了這些數據確實是由 Ajax 加載的。
我們的目的是要抓取其中的美圖,這裏一組圖就對應上文中的 data 字段中的一條數據,每條數據還有一個image_detail 字段,它是一個列表形式,這其中就包含了組圖的所有圖片列表,如圖 6-19 所示:


圖 6-19 圖片列表信息
所以我們只需要將列表中的 url 字段提取出來並下載下來就好了,每一組圖都建立一個文件夾,文件夾的名稱就命名爲組圖的標題。
接下來我們就可以直接用 Python 來模擬這個 Ajax 請求,然後提取出相關美圖鏈接並下載即可。但是在這之前我們還需要分析一下 URL 的規律。
切換回 Headers 選項卡,我們觀察一下它的請求 URL 和 Headers 信息,如圖 6-20 所示:


圖 6-20 請求信息
可以看到這是一個 GET 請求,請求 URL 的參數有 offset、format、keyword、autoload、count、cur_tab,我們需要找出這些參數的規律才方便用程序構造出來。
接下來我們可以滑動頁面,多加載一些新的結果,在加載的同時可以發現 Network 中又出現了許多 Ajax 請求,如圖 6-21 所示:

evernotecid://D603D29C-DFBA-4C04-85E9-CCA3C33763F6/appyinxiangcom/23852268/ENResource/p193

圖 6-21 Ajax 請求
在這裏觀察一下後續鏈接的參數,可以發現變化的參數只有offset,其他的都沒有變化,而且第二次請求的 offset 值爲 20,第三次爲 40,第四次爲 60,所以可以發現規律,這個 offset 值就是偏移量,而進而可以推斷出 count 參數就是一次性獲取的數據條數,所以我們可以用 offset 參數來控制數據分頁,這樣一來,我們就可以通過接口批量獲取數據了,然後將數據解析,將圖片下載下來就大功告成了。

3. 實戰演練

我們剛纔已經分析了一下 Ajax 請求的邏輯,下面我們就用程序來實現美圖下載吧。
首先我們實現一個方法用於加載單個 Ajax 請求的結果,叫做 get_page(),其中唯一變化的參數就是 offset,所以我們將 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,則調用 response 的 json() 方法將結果轉爲 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
                }
Python資源分享qun 784758214 ,內有安裝包,PDF,學習視頻,這裏是Python學習者的聚集地,零基礎,進階,都歡迎

接下來我們實現一個保存圖片的方法,item 就是剛纔get_images() 方法返回的一個字典,在方法中我們首先根據 item 的 title 來創建文件夾,然後請求這個圖片鏈接,獲取圖片的二進制數據,以二進制的形式寫入文件,圖片的名稱可以使用其內容的 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()
Python資源分享qun 784758214 ,內有安裝包,PDF,學習視頻,這裏是Python學習者的聚集地,零基礎,進階,都歡迎

在這裏定義了分頁的起始和終止頁數,分別爲 GROUP_START 和 GROUP_END,還利用了多線程的線程池,調用其 map() 方法實現多線程下載。
這樣整個程序都就完成了,運行之後可以發現街拍美圖都分文件夾保存下來了,如圖 6-22 所示:

圖 6-22 保存結果

4. 本節代碼

本節代碼地址:https://github.com/oldmarkfac...

5. 結語

以上便是抓取今日頭條街拍美圖的過程,通過本節我們可以瞭解 Ajax 分析的流程、Ajax 分頁的模擬以及圖片的下載過程。
本節的內容需要熟練掌握,在後面的實戰中我們還會用到很多次這樣的分析和抓取。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章