天天看點

Python3網絡爬蟲開發實戰分析Ajax爬取今日頭條街拍美圖

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

1. 準備工作

很多人學習python,不知道從何學起。

很多人學習python,掌握了基本文法過後,不知道在哪裡尋找案例上手。

很多已經做案例的人,卻不知道如何去學習更加高深的知識。

那麼針對這三類人,我給大家提供一個好的學習平台,免費領取視訊教程,電子書籍,以及課程的源代碼!

QQ群:101677771

在本節開始之前,請確定已經安裝好requests庫。如果沒有安裝,可以參考第1章。

2. 抓取分析

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

Python3網絡爬蟲開發實戰分析Ajax爬取今日頭條街拍美圖

圖6-15 首頁内容

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

Python3網絡爬蟲開發實戰分析Ajax爬取今日頭條街拍美圖

圖6-16 搜尋結果

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

Python3網絡爬蟲開發實戰分析Ajax爬取今日頭條街拍美圖

圖6-17 搜尋結果

我們發現,網頁源代碼中并沒有包含這兩個字,搜尋比對結果數目為0。是以,可以初步判斷這些内容是由Ajax加載,然後用JavaScript渲染出來的。接下來,我們可以切換到XHR過濾頁籤,檢視一下有沒有Ajax請求。

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

點選

data

字段展開,發現這裡有許多條資料。點選第一條展開,可以發現有一個

title

字段,它的值正好就是頁面中第一條資料的标題。再檢查一下其他資料,也正好是一一對應的,如圖6-18所示。

Python3網絡爬蟲開發實戰分析Ajax爬取今日頭條街拍美圖

圖6-18 對比結果

這就确定了這些資料确實是由Ajax加載的。

我們的目的是要抓取其中的美圖,這裡一組圖就對應前面

data

字段中的一條資料。每條資料還有一個

image_detail

字段,它是清單形式,這其中就包含了組圖的所有圖檔清單,如圖6-19所示。

Python3網絡爬蟲開發實戰分析Ajax爬取今日頭條街拍美圖

圖6-19 圖檔清單資訊

是以,我們隻需要将清單中的

url

字段提取出來并下載下傳下來就好了。每一組圖都建立一個檔案夾,檔案夾的名稱就為組圖的标題。

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

切換回Headers頁籤,觀察一下它的請求URL和Headers資訊,如圖6-20所示。

Python3網絡爬蟲開發實戰分析Ajax爬取今日頭條街拍美圖

圖6-20 請求資訊

可以看到,這是一個GET請求,請求URL的參數有

offset

format

keyword

autoload

count

cur_tab

。我們需要找出這些參數的規律,因為這樣才可以友善地用程式構造出來。

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

Python3網絡爬蟲開發實戰分析Ajax爬取今日頭條街拍美圖

圖6-21 Ajax請求

這裡觀察一下後續連結的參數,發現變化的參數隻有

offset

,其他參數都沒有變化,而且第二次請求的

offset

值為20,第三次為40,第四次為60,是以可以發現規律,這個

offset

值就是偏移量,進而可以推斷出

count

參數就是一次性擷取的資料條數。是以,我們可以用

offset

參數來控制資料分頁。這樣一來,我們就可以通過接口批量擷取資料了,然後将資料解析,将圖檔下載下傳下來即可。

3. 實戰演練

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

首先,實作方法

get_page()

來加載單個Ajax請求的結果。其中唯一變化的參數就是

offset

,是以我們将它當作參數傳遞,實作如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

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

字段中的每一張圖檔連結,将圖檔連結和圖檔所屬的标題一并傳回,此時可以構造一個生成器。實作代碼如下:

1

2

3

4

5

6

7

8

9

10

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()

方法傳回的一個字典。在該方法中,首先根據

item

title

來建立檔案夾,然後請求這個圖檔連結,擷取圖檔的二進制資料,以二進制的形式寫入檔案。圖檔的名稱可以使用其内容的MD5值,這樣可以去除重複。相關代碼如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

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

,提取圖檔連結,并将其下載下傳即可:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

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_START

GROUP_END

,還利用了多線程的線程池,調用其

map()

方法實作多線程下載下傳。

這樣整個程式就完成了,運作之後可以發現街拍美圖都分檔案夾儲存下來了,如圖6-22所示。

Python3網絡爬蟲開發實戰分析Ajax爬取今日頭條街拍美圖

圖6-22 儲存結果

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

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

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