爬蟲—分析Ajax爬取今日頭條圖片

  以今日頭條爲例分析Ajax請求抓取網頁數據。本次抓取今日頭條的街拍關鍵字對應的圖片,並保存到本地web

一,分析

  打開今日頭條主頁,在搜索框中輸入街拍二字,打開開發者工具,發現瀏覽器顯示的數據不在其源碼裏面。這樣能夠出初步判斷這些內容是由json

Ajax加載,而後使用JavaScript渲染出來的。api

        

  切換到XHR過濾選項卡,查看其Ajax請求。點擊其中一條進去,進入data展開,發現其中一個title字段對應的值正好是頁面中的某條數據的標題。再查看其餘數據,正好也是一一對應的,這說明這些數據確實是由Ajax加載的。瀏覽器

         

  

  本次的目的是抓取其中的圖片內容,data中每一個元素就是一篇文章,元素中的image_list字段包含了該文章的圖片內容。它是一個列表形式,包含了全部的圖片列表。咱們只須要將列表中的url字段下載下來就行了,每篇文章都建立一個文件夾,文件夾名稱即文章標題。app

          

  在使用Python爬取以前還須要分析一下URL的規律。切換到Headers選項卡,查看Headers信息。能夠看到,這是一個GET請求,請求的參數有aid,app_name,offset,format,keyword,autoload,count,en_qc,cur_tab,from,pd,timestamp。繼續往下滑動,多加載一些數據,找出其中的規律。工具

        

  通過觀察,能夠發現變化的參數只有offset,timestamp。第一次請求的offset的值爲0,第二次爲20,第三次爲40,key推斷出這個offset就是偏移量,count爲每次請求的數據量,而timestamp爲時間戳。這樣一來,咱們就可使用offset參數控制分頁了,經過模擬Ajax請求獲取數據,最後將數據解析後下載便可。url

二,爬取

  剛纔已經分析完了整個Ajax請求,接下來就是使用代碼來實現這個過程。spa

# _*_ coding=utf-8 _*_

import requests
import time
import os
from hashlib import md5
from urllib.parse import urlencode
from multiprocessing.pool import Pool


def get_data(offset):
    """
    構造URL,發送請求
    :param offset:
    :return:
    """
    timestamp = int(time.time())
    params = {
        'aid': '24',
        'app_name': 'web_search',
        'offset': offset,
        'format': 'json',
        'autoload': 'true',
        'count': '20',
        'en_qc': '1',
        'cur_tab': '1',
        'from': 'search_tab',
        'pd': 'synthesis',
        'timestamp': timestamp
    }

    base_url = 'https://www.toutiao.com/api/search/content/?keyword=%E8%A1%97%E6%8B%8D'
    url = base_url + urlencode(params)
    try:
        res = requests.get(url)
        if res.status_code == 200:
            return res.json()
    except requests.ConnectionError:
        return '555...'


def get_img(data):
    """
    提取每一張圖片鏈接,與標題一併返回,構造生成器
    :param data:
    :return:
    """
    if data.get('data'):
        page_data = data.get('data')
        for item in page_data:
            # cell_type字段不存在的這類文章不爬取,它沒有title,和image_list字段,會出錯
            if item.get('cell_type') is not None:
                continue
            title = item.get('title').replace(' |', ' ')    # 去掉某些可能致使文件名錯誤而不能建立文件的特殊符號,根據具體狀況而定
            imgs = item.get('image_list')
            for img in imgs:
                yield {
                    'title': title,
                    'img': img.get('url')
                }


def save(item):
    """
    根據title建立文件夾,將圖片以二進制形式寫入,
    圖片名稱使用其內容的md5值,能夠去除重複的圖片
    :param item:
    :return:
    """
    img_path = 'img' + '/' + item.get('title')
    if not os.path.exists(img_path):
        os.makedirs(img_path)
    try:
        res = requests.get(item.get('img'))
        if res.status_code == 200:
            file_path = img_path + '/' + '{name}.{suffix}'.format(
                name=md5(res.content).hexdigest(),
                suffix='jpg')
            if not os.path.exists(file_path):
                with open(file_path, 'wb') as f:
                    f.write(res.content)
                print('Successful')
            else:
                print('Already Download')
    except requests.ConnectionError:
        print('Failed to save images')


def main(offset):
    data = get_data(offset)
    for item in get_img(data):
        print(item)
        save(item)


START = 0
END = 10
if __name__ == "__main__":
    pool = Pool()
    offsets = ([n * 20 for n in range(START, END + 1)])
    pool.map(main, offsets)
    pool.close()
    pool.join()

  這裏定義了起始頁START和結束頁END,能夠自定義設置。而後利用多進程的進程池,調用map()方法實現多進程下載。運行以後發現圖片都報存下來了。3d

                                        

相關文章
相關標籤/搜索