這是全棧數(shù)據(jù)工程師養(yǎng)成攻略系列教程的第九期:9 實(shí)戰(zhàn) 爬取豆瓣電影數(shù)據(jù)年局。
掌握了爬蟲(chóng)的基本原理和代碼實(shí)現(xiàn)殉摔,現(xiàn)在讓我們通過(guò)實(shí)戰(zhàn)項(xiàng)目鞏固一下凄贩。
確定目標(biāo)
在寫(xiě)爬蟲(chóng)之前應(yīng)當(dāng)想清楚:我需要哪方面的數(shù)據(jù)淑玫?需要包含哪些字段崎岂?這些數(shù)據(jù)需要以何種形式呈現(xiàn)捆毫?
很多網(wǎng)站往往都是大家爬取的對(duì)象,例如提供住房信息的鏈家網(wǎng)冲甘,提供書(shū)評(píng)和影評(píng)信息的豆瓣網(wǎng)绩卤,以及提供餐飲生活?yuàn)蕵?lè)信息的大眾點(diǎn)評(píng)網(wǎng)。當(dāng)然江醇,這些網(wǎng)站之所以能很容易地被我們爬取濒憋,也是因?yàn)樗鼈儾扇×藘?nèi)容開(kāi)放的運(yùn)營(yíng)態(tài)度,沒(méi)有進(jìn)行過(guò)多的反爬處理陶夜。
有一個(gè)挺有意思的名詞叫做“三月爬蟲(chóng)”凛驮,即在三月份左右頻繁出現(xiàn)的大量小規(guī)模爬蟲(chóng)。這些爬蟲(chóng)從何而來(lái)律适?因?yàn)樵诿磕甑倪@個(gè)時(shí)候辐烂,學(xué)生們都要做課設(shè)項(xiàng)目了,沒(méi)有數(shù)據(jù)怎么辦捂贿?嗯纠修,靠爬!
其實(shí)爬蟲(chóng)和反爬蟲(chóng)之間就好比矛與盾的關(guān)系厂僧,我們可以花更多的心思扣草、時(shí)間和成本去爬取數(shù)據(jù),數(shù)據(jù)運(yùn)營(yíng)方同樣可以花更多的技術(shù)、金錢(qián)和人力以保護(hù)數(shù)據(jù)辰妙。識(shí)別代碼請(qǐng)求并禁止鹰祸,我們可以偽裝成瀏覽器;對(duì)IP頻繁請(qǐng)求采取限制密浑,我們可以使用IP代理池蛙婴;要求登錄并輸入復(fù)雜的驗(yàn)證碼,我們同樣可以模擬登錄以及想出相應(yīng)的解決辦法尔破〗滞迹總而言之,沒(méi)有一定能爬到的數(shù)據(jù)懒构,也沒(méi)有一定爬不到的數(shù)據(jù)餐济,無(wú)非是攻守雙方的博弈,看誰(shuí)下的功夫更深胆剧、投入成本更多絮姆。
當(dāng)然,之前介紹的都是最基礎(chǔ)的爬取方法秩霍,所針對(duì)的也是采取開(kāi)放運(yùn)營(yíng)態(tài)度篙悯,或者暫未采取防爬措施的網(wǎng)站。方法雖簡(jiǎn)單前域,但依舊足以爬取相當(dāng)多的網(wǎng)站辕近,至于爬蟲(chóng)的進(jìn)一步深入研究韵吨,則需要花費(fèi)更多時(shí)間去學(xué)習(xí)匿垄,這也是為什么有專(zhuān)業(yè)的爬蟲(chóng)工程師這一方向了。
在這次的項(xiàng)目實(shí)戰(zhàn)中归粉,我們需要獲取豆瓣電影上的電影數(shù)據(jù)椿疗,數(shù)量自然是越多越好,每條數(shù)據(jù)應(yīng)當(dāng)包含電影名稱糠悼、導(dǎo)演届榄、演員、類(lèi)型倔喂、片長(zhǎng)铝条、語(yǔ)言、上映時(shí)間席噩、上映地區(qū)班缰、評(píng)分等信息,這樣在獲取數(shù)據(jù)并存儲(chǔ)之后便可進(jìn)行后續(xù)分析和展示悼枢。
通用思路
寫(xiě)爬蟲(chóng)時(shí)往往會(huì)遵循以下通用思路:首先得找到一個(gè)匯總頁(yè)埠忘,以鏈家網(wǎng)為例,可以是首頁(yè)或搜索頁(yè)。在匯總頁(yè)中是一條條房源莹妒,以列表形式依次排列名船,可能一頁(yè)會(huì)安排幾十條房源,看完之后可以通過(guò)翻頁(yè)功能跳轉(zhuǎn)至下一頁(yè)旨怠,從而進(jìn)行對(duì)全部房源的瀏覽渠驼;匯總頁(yè)中的每一個(gè)鏈接都對(duì)應(yīng)一條房源的詳情頁(yè),點(diǎn)進(jìn)去即可查看房源的詳細(xì)信息鉴腻。這些詳情頁(yè)都是用同樣的模版渲染出來(lái)的渴邦,只不過(guò)渲染時(shí)使用了不同的數(shù)據(jù),因此十分便于批量獲取拘哨,只要對(duì)詳情頁(yè)的頁(yè)面結(jié)構(gòu)進(jìn)行分析和提取即可谋梭。
當(dāng)然,以上所提的二層結(jié)構(gòu)是最理想的情況倦青,實(shí)際問(wèn)題中未必能找到這樣一個(gè)能直接涵蓋并通往全部詳情頁(yè)的匯總頁(yè)瓮床,因此三層、四層乃至更復(fù)雜的結(jié)構(gòu)也完全可能出現(xiàn)产镐。例如從鏈家的首頁(yè)開(kāi)始隘庄,先下鉆到城市,再深入到地區(qū)癣亚,接著按戶型進(jìn)行分類(lèi)丑掺,最后才能找到所對(duì)應(yīng)的房源。其實(shí)我們會(huì)發(fā)現(xiàn)述雾,每一層結(jié)構(gòu)都對(duì)應(yīng)著最終詳情頁(yè)的一個(gè)字段街州,例如房源的城市、地區(qū)玻孟、戶型等信息唆缴,多層結(jié)構(gòu)無(wú)非只是對(duì)二層結(jié)構(gòu)按照若干個(gè)字段進(jìn)行了逐層聚合,所以只要理清楚網(wǎng)站數(shù)據(jù)的整理結(jié)構(gòu)黍翎,接下來(lái)的代碼工作都是類(lèi)似的面徽。
尋找鏈接
回到這次的項(xiàng)目實(shí)戰(zhàn)上,我們首先訪問(wèn)一下豆瓣電影的首頁(yè)匣掸,https://movie.douban.com/趟紊,探探路子,看看該如何去尋找之前提及的匯總頁(yè)和詳情頁(yè)碰酝。
我們首先看到豆瓣提供了一個(gè)正在熱映
模塊霎匈,展示當(dāng)前正在上映的一些電影。由于我們希望盡可能多地獲取電影數(shù)據(jù)砰粹,當(dāng)然包括歷史電影唧躲,所以這塊忽略不計(jì)造挽。
接下來(lái)的頁(yè)面中有一個(gè)按電影分類(lèi)進(jìn)行篩選的模塊,如下圖所示弄痹,可以根據(jù)熱門(mén)饭入、最新、經(jīng)典肛真、可播放等標(biāo)簽顯示相應(yīng)的電影谐丢。按熱度排序、按時(shí)間排序和按評(píng)價(jià)排序意義不大蚓让,希望我們希望獲得盡可能多的電影數(shù)據(jù)全集乾忱,對(duì)排序則不關(guān)心。再往下有個(gè)加載更多
的按鈕历极,我們會(huì)發(fā)現(xiàn)每次點(diǎn)擊之后窄瘟,頁(yè)面上就會(huì)出現(xiàn)更多對(duì)應(yīng)類(lèi)別的電影,說(shuō)明網(wǎng)頁(yè)相應(yīng)地又向服務(wù)端請(qǐng)求了更多數(shù)據(jù)趟卸。
所以爬取的基本思路有了蹄葱,首先獲得全部的類(lèi)別標(biāo)簽,然后針對(duì)每個(gè)標(biāo)簽不斷地請(qǐng)求相應(yīng)的電影數(shù)據(jù)锄列,最后從每部電影的詳情頁(yè)獲取所需的字段图云。讓我們用Chrome開(kāi)發(fā)者工具來(lái)找出應(yīng)當(dāng)請(qǐng)求哪些鏈接,打開(kāi)開(kāi)發(fā)者工具之后刷新豆瓣電影首頁(yè)邻邮,我們會(huì)發(fā)現(xiàn)在Network
的XHR
中有這么個(gè)鏈接竣况,https://movie.douban.com/j/search_tags?type=movie,從名字上來(lái)看似乎是返回電影標(biāo)簽筒严,在瀏覽器中訪問(wèn)果不其然丹泉,得到了以下內(nèi)容:
{"tags":["熱門(mén)","最新","經(jīng)典","可播放","豆瓣高分","冷門(mén)佳片","華語(yǔ)","歐美","韓國(guó)","日本","動(dòng)作","喜劇","愛(ài)情","科幻","懸疑","恐怖","動(dòng)畫(huà)"]}
說(shuō)明這是一個(gè)GET類(lèi)型的API,返回一個(gè)json格式的字符串萝风,如果在Python中加載成字典之后嘀掸,則包含一個(gè)鍵tags紫岩,對(duì)應(yīng)的值是一個(gè)列表规惰,里面的每一項(xiàng)都是一個(gè)電影標(biāo)簽。
我們還順便發(fā)現(xiàn)了另一個(gè)GET類(lèi)API泉蝌,https://movie.douban.com/j/search_subjects?type=movie&tag=熱門(mén)&sort=recommend&page_limit=20&page_start=0歇万,可以根據(jù)提供的標(biāo)簽、排序方法勋陪、每頁(yè)數(shù)量贪磺、每頁(yè)開(kāi)始編號(hào)等參數(shù)返回相應(yīng)的電影數(shù)據(jù),這里是按推薦程度排名诅愚,從0號(hào)開(kāi)始寒锚,返回?zé)衢T(mén)標(biāo)簽下的20條電影數(shù)據(jù)。在瀏覽器中訪問(wèn)以上鏈接,得到的也是一個(gè)json格式字符串刹前,同樣轉(zhuǎn)成Python字典再處理即可泳赋。如果點(diǎn)擊加載更多
按鈕,會(huì)發(fā)現(xiàn)網(wǎng)頁(yè)會(huì)繼續(xù)請(qǐng)求這個(gè)API喇喉,不同的只是page_start
不斷增加祖今,通過(guò)改變開(kāi)始編號(hào)即可請(qǐng)求到新的數(shù)據(jù)。
設(shè)計(jì)下代碼實(shí)現(xiàn)的過(guò)程:針對(duì)每個(gè)標(biāo)簽拣技,使用以上第二個(gè)API不斷請(qǐng)求數(shù)據(jù)千诬,如果請(qǐng)求結(jié)果中包含數(shù)據(jù),則將page_start
增加20再繼續(xù)膏斤,直到返回結(jié)果為空徐绑,說(shuō)明這一標(biāo)簽下的電影數(shù)據(jù)已經(jīng)全部拿到。
代碼實(shí)現(xiàn)
我們已經(jīng)掌握了如何用Python發(fā)起GET和POST請(qǐng)求莫辨,所以接下來(lái)的工作就是寫(xiě)代碼實(shí)現(xiàn)泵三。
# 加載庫(kù)
import urllib
import urllib2
import json
from bs4 import BeautifulSoup
# 獲取所有標(biāo)簽
tags = []
url = 'https://movie.douban.com/j/search_tags?type=movie'
request = urllib2.Request(url=url)
response = urllib2.urlopen(request, timeout=20)
result = response.read()
# 加載json為字典
result = json.loads(result)
tags = result['tags']
# 定義一個(gè)列表存儲(chǔ)電影的基本信息
movies = []
# 處理每個(gè)tag
for tag in tags:
start = 0
# 不斷請(qǐng)求,直到返回結(jié)果為空
while 1:
# 拼接需要請(qǐng)求的鏈接衔掸,包括標(biāo)簽和開(kāi)始編號(hào)
url = 'https://movie.douban.com/j/search_subjects?type=movie&tag=' + tag + '&sort=recommend&page_limit=20&page_start=' + str(start)
print url
request = urllib2.Request(url=url)
response = urllib2.urlopen(request, timeout=20)
result = response.read()
result = json.loads(result)
# 先在瀏覽器中訪問(wèn)一下API烫幕,觀察返回json的結(jié)構(gòu)
# 然后在Python中取出需要的值
result = result['subjects']
# 返回結(jié)果為空,說(shuō)明已經(jīng)沒(méi)有數(shù)據(jù)了
# 完成一個(gè)標(biāo)簽的處理敞映,退出循環(huán)
if len(result) == 0:
break
# 將每一條數(shù)據(jù)都加入movies
for item in result:
movies.append(item)
# 使用循環(huán)記得修改條件
# 這里需要修改start
start += 20
# 看看一共獲取了多少電影
print len(movies)
以上代碼運(yùn)行完畢之后较曼,列表movies
中即包含了全部的電影數(shù)據(jù),其中的每一項(xiàng)都是一個(gè)字典振愿,包含rate
評(píng)分捷犹、title
電影標(biāo)題、url
詳情頁(yè)鏈接冕末、playable
是否可播放萍歉、cover
封面圖片鏈接、id
電影的豆瓣id档桃、is_new
是否為新電影等字段枪孩。
除了以上字段,我們還希望獲取每部電影的更多信息藻肄,因此需要進(jìn)一步爬取各部電影所對(duì)應(yīng)的詳情頁(yè)蔑舞。以下是《瘋狂動(dòng)物城》的豆瓣詳情頁(yè),導(dǎo)演嘹屯、編劇攻询、主演、類(lèi)型州弟、語(yǔ)言钧栖、片長(zhǎng)低零、簡(jiǎn)介等,都是值得進(jìn)一步爬取的字段拯杠。
由于電影詳情頁(yè)的url類(lèi)型屬于Html毁兆,即訪問(wèn)后返回經(jīng)瀏覽器渲染的網(wǎng)頁(yè)內(nèi)容,所以需要更加復(fù)雜的處理方法阴挣。BeautifulSoup包提供了解析html文本气堕、查找和選擇html元素、提取元素內(nèi)容和屬性等功能畔咧,但要求一些html和css語(yǔ)法基礎(chǔ)茎芭。這里僅以詳情頁(yè)中的電影簡(jiǎn)介為例,展示下如何使用BeautifulSoup解析html文本誓沸,其他完整內(nèi)容等后續(xù)章節(jié)介紹了相關(guān)基礎(chǔ)后再回過(guò)頭講解梅桩。
import time
# 請(qǐng)求每部電影的詳情頁(yè)面
for x in xrange(0, len(movies)):
url = movies[x]['url']
request = urllib2.Request(url=url)
response = urllib2.urlopen(request, timeout=20)
result = response.read()
# 使用BeautifulSoup解析html
html = BeautifulSoup(result)
# 提取電影簡(jiǎn)介
# 捕捉異常,有的電影詳情頁(yè)中并沒(méi)有簡(jiǎn)介
try:
description = html.find_all("span", attrs={"property": "v:summary"})[0].get_text()
except Exception, e:
# 沒(méi)有提取到簡(jiǎn)介拜隧,則簡(jiǎn)介為空
movies[x]['description'] = ''
else:
# 將新獲取的字段填入movies
movies[x]['description'] = description
finally:
pass
# 適當(dāng)休息宿百,避免請(qǐng)求頻發(fā)被禁止,報(bào)403 Forbidden錯(cuò)誤
time.sleep(0.5)
最后洪添,可以將獲取的電影數(shù)據(jù)寫(xiě)入txt文件垦页,以便后續(xù)使用。
fw = open('douban_movies.txt', 'w')
# 寫(xiě)入一行表頭干奢,用于說(shuō)明每個(gè)字段的意義
fw.write('title^rate^url^cover^id^description\n')
for item in movies:
# 用^作為分隔符
# 主要是為了避免中文里可能包含逗號(hào)發(fā)生沖突
fw.write(item['title'] + '^' + item['rate'] + '^' + item['url'] + '^' + item['cover'] + '^' + item['id'] + '^' + item['description'] + '\n')
fw.close()
其他內(nèi)容
這次的實(shí)戰(zhàn)其實(shí)是我一個(gè)Github項(xiàng)目的一部分痊焊,https://github.com/Honlan/data-visualize-chain。這個(gè)項(xiàng)目以豆瓣電影為例忿峻,展示了如何進(jìn)行數(shù)據(jù)獲取薄啥、清洗、存儲(chǔ)逛尚、分析和可視化垄惧,和爬蟲(chóng)相關(guān)的代碼都在spider
文件夾下。所以如果希望了解更多绰寞,以及對(duì)BeautifulSoup用法感興趣到逊,可以進(jìn)一步研究。
另外克握,以上代碼最終獲取了五千部左右的電影數(shù)據(jù)蕾管,明顯少于豆瓣電影的總量,說(shuō)明之前所使用的API能獲取的數(shù)據(jù)量十分有限菩暗。那應(yīng)該怎么辦呢?在豆瓣電影的網(wǎng)站上找一找旭蠕,會(huì)發(fā)現(xiàn)這么個(gè)鏈接停团,https://movie.douban.com/tag/旷坦,點(diǎn)進(jìn)去之后簡(jiǎn)直是到了一個(gè)嶄新的世界。在這個(gè)頁(yè)面中提供了各種分類(lèi)標(biāo)簽佑稠,每個(gè)標(biāo)簽下的電影數(shù)量也十分驚人秒梅,全部爬取一遍能收獲的數(shù)據(jù)量必然相當(dāng)可觀。所以只要按照之前所講的通用思路舌胶,類(lèi)似地寫(xiě)代碼爬取即可捆蜀。
視頻鏈接: