之前已經爬過今日頭條街拍的美圖,今天再次完善一下代碼,并詳解爬取過程及遇到的坑。廢話不多說,抓緊上車啦。
分析頁面
分析索引頁
我們打開今日頭條官網,在在搜尋框輸入「街拍」

首頁内容
然後點選确定,跳轉到街拍的詳情頁。
街拍
這裡可以看到上方有四個框,分别是 綜合、視訊、圖集、使用者。
兩種方式
看到這裡,就有兩種不同的抓取方式。
- 抓取綜合下方的圖集,這個方式雖然可以抓取到圖檔,但是抓到的圖檔隻有四張或一張,也就是看到的顯示在标題下方的圖檔。而且擷取的圖檔還不是高清的,還要替換每張圖檔的位址格式。
-
抓取圖集下的圖檔,這種方式可以抓到所有一個标題下的圖檔,但是一頁顯示的圖檔抓取不到。
我看網上大多是第一種方式,這次為了練習,我們選取第二種方式。
我們點選圖集,打開開發者工具「按F12」,不斷下拉頁面,頁面位址沒有變化,内容不斷加載出來,這一看就是 Ajax 加載的頁面。
詳解python爬取今日頭條街拍美圖 詳情
這裡面也有一些坑
- 如果你點選圖集,打開開發者工具,重新整理一下,你會發現,你的頁面在綜合這一欄。
-
你會發現你找到的上圖的參數跟我的不一樣。
這裡你可以重新整理之後點選圖集,然後向下拖動幾個,讓頁面多加載一些。
其中「cur_tab:3」這個參數中的數字對應圖集,這下你明白了吧。
到這裡就好辦了,我們點選 Preview
可以看到 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 格式的,我們看一下這裡的内容
詳情頁json
這裡擷取用BeautifulSoup 擷取 title 很友善,直接去第一個 title 就好了,關鍵就在這個image的提取。
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 方法開啟線程就可以了。
總結
到這裡整個抓取過程就結束了。總體下來代碼量要比平時抓取的要大,知識點也有很多。在這個過程中,即使找着代碼敲也會發現不少的問題。抓取的過程就是不斷調試的過程。
點個贊再走呗。