文中代碼只是部分講解,完整的代碼放在文章末尾地址。
先實際感受一下我們要抓取的福利是什麼?點擊 今日頭條,在搜索欄輸入街拍 兩個字,點開任意一篇文章,裡面的圖片即是我們要抓取的內容。
可以看到搜索結果默認返回了 20 篇文章,當頁面滾動到底部時頭條通過 ajax 加載更多文章,瀏覽器按下 F12 打開調試工具(我的是 Chrome),點擊 Network 選項,嘗試加載更多的文章,可以看到相關的 http 請求:
可以看到請求的 URL(Request URL)為:http://www.toutiao.com/search_content/, 其請求參數為:
很容易猜測 offset 表示偏移量,即已經請求的文章數;format 為返回格式,這裡返回的是 json 格式的數據;keyword 是我們的搜索關鍵字;autoload 應該是自動加載的指示標誌,無關緊要;count 為請求的新文章數量;_ 應該是請求發起時的時間戳。將請求的 URL 和這些查詢參數拼接即組成完整的 Request URL,例如這次的 Request URL 是: http://www.toutiao.com/search_content/?offset=20&format=json&keyword=%E8%A1%97%E6%8B%8D&autoload=true&count=20&_=1480675595492。
先讓我們來看看這個請求為我們返回了什麼樣的數據。
import json
from pprint import pprint
from urllib import request
url = "http://www.toutiao.com/search_content/?offset=20&format=json&keyword=%E8%A1%97%E6%8B%8D&autoload=true&count=20&_=1480675595492"
with request.urlopen(url) as res:
d = json.loads(res.read().decode())
print(d)
這裡我們首先通過 request.urlopen(url) 向這個 url 發送請求,返回的數據保存在 res 中,res 是一個 HttpResponse 對象,通過調用其 read 方法獲取實際返回的內容,由於 read 方法返回的是 Python 的 bytes 類型的字符串,通過調用其 decode 方法將其編碼成 string 類型字符串,默認為 UTF-8 編碼。由於數據以 json 格式返回,因此通過 json.load 方法將其轉為 Python 的字典形式。
打印出這個字典,可以看到字典中有一個鍵 ‘data’ 對應著一個由字典組成的列表的值,分析可知這個值就是返回的全部文章的數據列表,稍微修改一下代碼,來看看 ‘data’ 對應的值是什麼樣的:
with request.urlopen(url) as res:
d = json.loads(res.read().decode())
d = d.get('data')
pprint(d)
這裡使用了 pprint 讓字典打印的出來的值更加的格式化,便於分析。可以看到這是一個由字典組成的列表,列表的每一個項代表一篇文章,包含了文章的全部基本數據,例如標題,文章的 URL 等。於是我們可以通過如下的方式來獲取我們本次請求的全部文章的 URL 列表:
urls = [article.get('article_url') for article in d if article.get('article_url')]
這裡使用了列表推導式,循環文章列表,通過 get('article_url') 獲取到文章的 URL,加上 if 判斷條件是為了防止因為數據缺失而得到空的文章 URL。我們將通過不斷請求這些文章的 URL,讀取其內容,並把圖片提取出來保存到我們的硬盤裡。
先來處理一篇文章,看看我們如何把文章裡的全部圖片提取出來。
隨便點開一個文章鏈接,按 F12 查看網頁源代碼,可以看到文章的主體部分位於一個 id="article-main"
的 div 裡。這個 div 下有 h1 標籤表示文章標題,另外一系列 img 標籤,其 src 屬性即保存著圖片所在的鏈接,於是我們通過訪問這些鏈接把圖片下載下來,看看具體怎麼做:
url = "http://www.toutiao.com/a6351879148420235522/"
with request.urlopen(url) as res:
soup = BeautifulSoup(res.read().decode(errors='ignore'), 'html.parser')
article_main = soup.find('div', id='article-main')
photo_list = [photo.get('src') for photo in article_main.find_all('img') if photo.get('src')]
print(photo_list)
# 輸出:
['http://p9.pstatp.com/large/111200020f54729cd558', 'http://p3.pstatp.com/large/11100005d3e8b9e69a88', 'http://p3.pstatp.com/large/106b00058387c12351c7', ...]
這裡我們請求文章的 URL,將返回的內容(html)傳遞給 BeautifulSoup 為我們做解析。通過 find 方法找到 article-main 對應的 div 塊,在該 div 塊下繼續使用 find_all 方法搜尋全部的 img 標籤,並提取其 src 屬性對應的值,於是我們便獲得了該文章下全部圖片的 URL 列表。 現在要做的就是繼續請求這些圖片的 URL,並把返回的圖片數據保存到硬盤裡。以一張圖片示例:
photo_url = "http://p9.pstatp.com/large/111200020f54729cd558"
photo_name = photo_url.rsplit('/', 1)[-1] + '.jpg'
with request.urlopen(photo_url) as res, open(photo_name, 'wb') as f:
f.write(res.read())
此時就可以在當前目錄下看到我們保存下來的圖片了。這裡我們使用了 URL 最後一段的數字做為圖片的文件名,並將其保存為 jpg 的格式。
基本步驟就是這麼多了,整理下爬取流程:
- 指定查詢參數,向 http://www.toutiao.com/search_content/ 提交我們的查詢請求。
- 從返回的數據(JSON 格式)中解析出全部文章的 URL,分別向這些文章發送請求。
- 從返回的數據(HTML 格式)提取出文章的標題和全部圖片鏈接。
- 再分別向這些圖片鏈接發送請求,將返回的圖片輸入保存到本地。
- 修改查詢參數,以使服務器返回新的文章數據,繼續第一步。
運行此代碼後一共爬取了大概 1000 多張圖片:
完整項目地址:https://github.com/zmrenwu/toutiao.git
閱讀更多 Python樂園 的文章