天天看點

詳解python爬取今日頭條街拍美圖

之前已經爬過今日頭條街拍的美圖,今天再次完善一下代碼,并詳解爬取過程及遇到的坑。廢話不多說,抓緊上車啦。

分析頁面

分析索引頁

我們打開今日頭條官網,在在搜尋框輸入「街拍」

詳解python爬取今日頭條街拍美圖

首頁内容

然後點選确定,跳轉到街拍的詳情頁。

詳解python爬取今日頭條街拍美圖

街拍

這裡可以看到上方有四個框,分别是 綜合、視訊、圖集、使用者。

兩種方式

看到這裡,就有兩種不同的抓取方式。

  • 抓取綜合下方的圖集,這個方式雖然可以抓取到圖檔,但是抓到的圖檔隻有四張或一張,也就是看到的顯示在标題下方的圖檔。而且擷取的圖檔還不是高清的,還要替換每張圖檔的位址格式。
  • 抓取圖集下的圖檔,這種方式可以抓到所有一個标題下的圖檔,但是一頁顯示的圖檔抓取不到。

    我看網上大多是第一種方式,這次為了練習,我們選取第二種方式。

    我們點選圖集,打開開發者工具「按F12」,不斷下拉頁面,頁面位址沒有變化,内容不斷加載出來,這一看就是 Ajax 加載的頁面。

    詳解python爬取今日頭條街拍美圖

    詳情

    這裡面也有一些坑

  • 如果你點選圖集,打開開發者工具,重新整理一下,你會發現,你的頁面在綜合這一欄。
  • 你會發現你找到的上圖的參數跟我的不一樣。

    這裡你可以重新整理之後點選圖集,然後向下拖動幾個,讓頁面多加載一些。

    其中「cur_tab:3」這個參數中的數字對應圖集,這下你明白了吧。

    到這裡就好辦了,我們點選 Preview

    詳解python爬取今日頭條街拍美圖
    可以看到 data 下方有 article_url 當然還有 image_url ,你點選 image_url 你會發現隻有四個 url ,複制連結在浏覽器上打開你發現 TMD 還不是大圖,還是縮略圖,是以我們不用它,我們擷取 article_url ,擷取之後再次請求不就完了嗎。雖說麻煩,但是我們思路清晰,頭腦發熱,四肢簡單。哎不對,跑題了。

分析詳情頁

這裡我們來看看詳情頁的内容

詳解python爬取今日頭條街拍美圖

詳情頁分析

這裡我們随便點開一個組圖的 url 來分析,我們可以看到傳回的資料是一大堆 html ,這裡有必要說一下,我們在擷取頁面内容的時候,一般浏覽器會傳回給我們的是 response 裡的内容。但是我們大多數爬取資料,用 xpath 、BeautifulSoup 擷取資料,看的是 Elements 裡的内容。這裡一定要看看 response 裡的内容和 Element 裡的是否相同。

這裡的圖檔位址還真是不好找,具體怎麼找呢,點開圖檔的位址,複制下連結,在HTML裡「Ctrl + F」就發現了。是在紅色框裡面的。

上代碼

分析完了就開始上代碼爬取

看看需要引入那些庫

import requests
import re,json,os
from urllib.parse import urlencode
from bs4 import BeautifulSoup
from requests.exceptions import RequestException
#引入子產品config中所有變量
from config import *
import pymongo
from hashlib import md5
from multiprocessing import Pool
           

這裡引用的庫有點多,所有本文的幹貨也是滿滿滴。

擷取索引頁資料

def get_page_index(offset,keyword):
    # 擷取頁面的HTML
    data = {
        'offset': offset,
        'format': 'json',
        'keyword': keyword,
        'autoload': 'true',
        'count': '20',
        'cur_tab': 3,
        'from':'gallery'
    }
    try:
        url = 'https://www.toutiao.com/search_content/?' + urlencode(data)
        response = requests.get(url)
        if response.status_code == 200:
            return response.text
        return None
    except RequestException:
        print('請求失敗')
        return None

           

擷取索引頁的内容,這裡的「offset」是頁面的規律、「keyword」是關鍵字,我們這篇文章是街拍。通過構造參數,拼接 url 。傳回頁面的 text。

解析索引頁

def parse_page_index(html):
    # 擷取所有詳情頁的url
    data = json.loads(html)  #頁面是json格式的,裝換成字元串格式
    # data.keys()傳回所有鍵名
    if data and 'data' in data.keys():
        for item in data.get('data'):
            yield item.get('article_url')
           

這個函數的主要作用就是提取所有的 article_url 。代碼裡面已經詳細的說明了内容。

解析詳情頁的url

def get_page_detial(url):
    #請求詳情頁的url
    try:
        headers = {
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 '
            '(KHTML, like Gecko) Chrome/53.0.2785.104 Safari/537.36 Core/1.53.4882.400 QQBrowser/9.7.13039.400'}
        response = requests.get(url,headers=headers)
        if response.status_code == 200:
            return response.text
        return None
    except RequestException:
        print('請求詳情頁失敗')
        return None

           

這裡不加 headers 是擷取不到資料的。

解析詳情頁

def parse_page_detial(html):
    #擷取詳情頁的标題和圖檔位址url
    soup = BeautifulSoup(html, 'lxml')
    title = soup.select('title')[0].get_text()

    #利用正則提取圖檔位址
    pattern = re.compile('.*?gallery: JSON.parse\("(.*?)\"\)', re.S)
    result = re.search(pattern,html)
    if result:
        data = json.loads(result.group(1).replace('\\', ''))
        if data and 'sub_images' in data.keys():
            sub_images = data.get('sub_images')
            #提取圖檔
            images = [item.get('url') for item in sub_images]
            #儲存圖檔到本地
            for image in images:download_image(image)
            return {'title':title,
                    'image':images}
           

這裡面有些東西要說一下了。

首先,這裡擷取的頁面内容是 json 格式的,我們看一下這裡的内容

詳解python爬取今日頭條街拍美圖

詳情頁json

這裡擷取用BeautifulSoup 擷取 title 很友善,直接去第一個 title 就好了,關鍵就在這個image的提取。

詳解python爬取今日頭條街拍美圖

image.png

這裡是在紅色框裡的,這裡涉及到了正則的用法,代碼裡用到了反斜杠,這裡是轉義比對,要不然正則會比對不到想要的資料。

還有在源代碼中出現了好多反斜杠,不去除掉還是沒辦法比對。

這些坑跨過之後就一帆風順了。

儲存到MongoDB

'''配置檔案'''

#連結位址
MONGO_URL = 'localhost'

#資料庫名稱
MONGO_DB = 'jiepai'

#表名稱
MONGO_TABLE = 'jiepai'

KEY_WORD = '街拍'
           

這是一些配置檔案,注意,這裡的配置檔案是在另一個python檔案中寫的,是以說開頭引入的庫中有一個注釋。

#引入子產品config中所有變量
from config import *
import pymongo

#聲明MongoDB對象
client = pymongo.MongoClient(MONGO_URL)
db = client[MONGO_DB]

def save_to_mongo(result):
    if db[MONGO_TABLE].insert(result):
        print('存儲到MongoDB成功')

           

這裡插入到MongoDB。

儲存到本地

def save_image(result):
    file_path = '{0}/{1}{2}'.format(os.getcwd(),md5(result).hexdigest(),'jpg')
    if not os.path.exists(file_path):
        with open(file_path,'wb') as f:
            f.write(result)

           

這裡用了 hashlib 庫的 md5 這個方法,目的是為了防止圖檔的重複,這個方法會根據圖檔的内容生成唯一的字元串,用來去重最好不過了。

這裡說儲存圖檔,沒有下載下傳圖檔,怎麼儲存,是以還要先下載下傳圖檔。

def download_image(url):
    try:
        print('正在下載下傳',url)
        r = requests.get(url)
        if r.status_code == 200:
            save_image(r.content)
        return False
    except RequestException:
        print('請求圖檔出錯')
        return False
           

細心的夥伴們已經發現,我們在解析詳情頁的時候插入的這個下載下傳圖檔的函數。

開啟多線程抓取

def main(offset):
    # 調用函數
    html = get_page_index(offset,KEY_WORD)
    for url in parse_page_index(html):
        html = get_page_detial(url)
        if html:
            result = parse_page_detial(html)
            save_to_mongo(result)

if __name__ == '__main__':
    pool = Pool()
    group = [x * 20 for x in range(1,21)]
    pool.map(main,group)
    pool.close()
    main()
           

這裡聲明一個線程池,調用 map 方法開啟線程就可以了。

總結

到這裡整個抓取過程就結束了。總體下來代碼量要比平時抓取的要大,知識點也有很多。在這個過程中,即使找着代碼敲也會發現不少的問題。抓取的過程就是不斷調試的過程。

點個贊再走呗。