全棧 - 9 實戰 爬取豆瓣電影數據
這是全棧數據工程師養成攻略系列教程的第九期:9 實戰 爬取豆瓣電影數據。
掌握了爬蟲的基本原理和代碼實現,現在讓我們通過實戰項目鞏固一下。
確定目標
在寫爬蟲之前應當想清楚:我需要哪方面的數據?需要包含哪些欄位?這些數據需要以何種形式呈現?
很多網站往往都是大家爬取的對象,例如提供住房信息的鏈家網,提供書評和影評信息的豆瓣網,以及提供餐飲生活娛樂信息的大眾點評網。當然,這些網站之所以能很容易地被我們爬取,也是因為它們採取了內容開放的運營態度,沒有進行過多的反爬處理。
有一個挺有意思的名詞叫做「三月爬蟲」,即在三月份左右頻繁出現的大量小規模爬蟲。這些爬蟲從何而來?因為在每年的這個時候,學生們都要做課設項目了,沒有數據怎麼辦?嗯,靠爬!
其實爬蟲和反爬蟲之間就好比矛與盾的關係,我們可以花更多的心思、時間和成本去爬取數據,數據運營方同樣可以花更多的技術、金錢和人力以保護數據。識別代碼請求並禁止,我們可以偽裝成瀏覽器;對IP頻繁請求採取限制,我們可以使用IP代理池;要求登錄並輸入複雜的驗證碼,我們同樣可以模擬登錄以及想出相應的解決辦法。總而言之,沒有一定能爬到的數據,也沒有一定爬不到的數據,無非是攻守雙方的博弈,看誰下的功夫更深、投入成本更多。
當然,之前介紹的都是最基礎的爬取方法,所針對的也是採取開放運營態度,或者暫未採取防爬措施的網站。方法雖簡單,但依舊足以爬取相當多的網站,至於爬蟲的進一步深入研究,則需要花費更多時間去學習,這也是為什麼有專業的爬蟲工程師這一方向了。
在這次的項目實戰中,我們需要獲取豆瓣電影上的電影數據,數量自然是越多越好,每條數據應當包含電影名稱、導演、演員、類型、片長、語言、上映時間、上映地區、評分等信息,這樣在獲取數據並存儲之後便可進行後續分析和展示。
通用思路
寫爬蟲時往往會遵循以下通用思路:首先得找到一個匯總頁,以鏈家網為例,可以是首頁或搜索頁。在匯總頁中是一條條房源,以列表形式依次排列,可能一頁會安排幾十條房源,看完之後可以通過翻頁功能跳轉至下一頁,從而進行對全部房源的瀏覽;匯總頁中的每一個鏈接都對應一條房源的詳情頁,點進去即可查看房源的詳細信息。這些詳情頁都是用同樣的模版渲染出來的,只不過渲染時使用了不同的數據,因此十分便於批量獲取,只要對詳情頁的頁面結構進行分析和提取即可。
當然,以上所提的二層結構是最理想的情況,實際問題中未必能找到這樣一個能直接涵蓋並通往全部詳情頁的匯總頁,因此三層、四層乃至更複雜的結構也完全可能出現。例如從鏈家的首頁開始,先下鑽到城市,再深入到地區,接著按戶型進行分類,最後才能找到所對應的房源。其實我們會發現,每一層結構都對應著最終詳情頁的一個欄位,例如房源的城市、地區、戶型等信息,多層結構無非只是對二層結構按照若干個欄位進行了逐層聚合,所以只要理清楚網站數據的整理結構,接下來的代碼工作都是類似的。
尋找鏈接
回到這次的項目實戰上,我們首先訪問一下豆瓣電影的首頁,豆瓣電影,探探路子,看看該如何去尋找之前提及的匯總頁和詳情頁。
我們首先看到豆瓣提供了一個正在熱映模塊,展示當前正在上映的一些電影。由於我們希望儘可能多地獲取電影數據,當然包括歷史電影,所以這塊忽略不計。
接下來的頁面中有一個按電影分類進行篩選的模塊,如下圖所示,可以根據熱門、最新、經典、可播放等標籤顯示相應的電影。按熱度排序、按時間排序和按評價排序意義不大,希望我們希望獲得儘可能多的電影數據全集,對排序則不關心。再往下有個載入更多的按鈕,我們會發現每次點擊之後,頁面上就會出現更多對應類別的電影,說明網頁相應地又向服務端請求了更多數據。
所以爬取的基本思路有了,首先獲得全部的類別標籤,然後針對每個標籤不斷地請求相應的電影數據,最後從每部電影的詳情頁獲取所需的欄位。讓我們用Chrome開發者工具來找出應當請求哪些鏈接,打開開發者工具之後刷新豆瓣電影首頁,我們會發現在Network的XHR中有這麼個鏈接,https://movie.douban.com/j/search_tags?type=movie,從名字上來看似乎是返回電影標籤,在瀏覽器中訪問果不其然,得到了以下內容:
{"tags":["熱門","最新","經典","可播放","豆瓣高分","冷門佳片","華語","歐美","韓國","日本","動作","喜劇","愛情","科幻","懸疑","恐怖","動畫"]}n
說明這是一個GET類型的API,返回一個json格式的字元串,如果在Python中載入成字典之後,則包含一個鍵tags,對應的值是一個列表,裡面的每一項都是一個電影標籤。
我們還順便發現了另一個GET類API,https://movie.douban.com/j/search_subjects?type=movie&tag=熱門&sort=recommend&page_limit=20&page_start=0,可以根據提供的標籤、排序方法、每頁數量、每頁開始編號等參數返回相應的電影數據,這裡是按推薦程度排名,從0號開始,返回熱門標籤下的20條電影數據。在瀏覽器中訪問以上鏈接,得到的也是一個json格式字元串,同樣轉成Python字典再處理即可。如果點擊載入更多按鈕,會發現網頁會繼續請求這個API,不同的只是page_start不斷增加,通過改變開始編號即可請求到新的數據。
設計下代碼實現的過程:針對每個標籤,使用以上第二個API不斷請求數據,如果請求結果中包含數據,則將page_start增加20再繼續,直到返回結果為空,說明這一標籤下的電影數據已經全部拿到。
代碼實現
我們已經掌握了如何用Python發起GET和POST請求,所以接下來的工作就是寫代碼實現。
# 載入庫nimport urllibnimport urllib2nimport jsonnfrom bs4 import BeautifulSoupnn# 獲取所有標籤ntags = []nurl = https://movie.douban.com/j/search_tags?type=movienrequest = urllib2.Request(url=url)nresponse = urllib2.urlopen(request, timeout=20)nresult = response.read()n# 載入json為字典nresult = json.loads(result)ntags = result[tags]nn# 定義一個列表存儲電影的基本信息nmovies = []n# 處理每個tagnfor tag in tags:n start = 0n # 不斷請求,直到返回結果為空n while 1:n # 拼接需要請求的鏈接,包括標籤和開始編號n url = https://movie.douban.com/j/search_subjects?type=movie&tag= + tag + &sort=recommend&page_limit=20&page_start= + str(start)n print urln request = urllib2.Request(url=url)n response = urllib2.urlopen(request, timeout=20)n result = response.read()n result = json.loads(result)nn # 先在瀏覽器中訪問一下API,觀察返回json的結構n # 然後在Python中取出需要的值 n result = result[subjects]nn # 返回結果為空,說明已經沒有數據了n # 完成一個標籤的處理,退出循環n if len(result) == 0:n breaknn # 將每一條數據都加入moviesn for item in result:n movies.append(item)nn # 使用循環記得修改條件n # 這裡需要修改startn start += 20nn# 看看一共獲取了多少電影nprint len(movies)n
以上代碼運行完畢之後,列表movies中即包含了全部的電影數據,其中的每一項都是一個字典,包含rate評分、title電影標題、url詳情頁鏈接、playable是否可播放、cover封面圖片鏈接、id電影的豆瓣id、is_new是否為新電影等欄位。
除了以上欄位,我們還希望獲取每部電影的更多信息,因此需要進一步爬取各部電影所對應的詳情頁。以下是《瘋狂動物城》的豆瓣詳情頁,導演、編劇、主演、類型、語言、片長、簡介等,都是值得進一步爬取的欄位。
由於電影詳情頁的url類型屬於Html,即訪問後返回經瀏覽器渲染的網頁內容,所以需要更加複雜的處理方法。BeautifulSoup包提供了解析html文本、查找和選擇html元素、提取元素內容和屬性等功能,但要求一些html和css語法基礎。這裡僅以詳情頁中的電影簡介為例,展示下如何使用BeautifulSoup解析html文本,其他完整內容等後續章節介紹了相關基礎後再回過頭講解。
import timenn# 請求每部電影的詳情頁面nfor x in xrange(0, len(movies)):n url = movies[x][url]n request = urllib2.Request(url=url)n response = urllib2.urlopen(request, timeout=20)n result = response.read()nn # 使用BeautifulSoup解析htmln html = BeautifulSoup(result)n # 提取電影簡介n # 捕捉異常,有的電影詳情頁中並沒有簡介n try:n description = html.find_all("span", attrs={"property": "v:summary"})[0].get_text()n except Exception, e:n # 沒有提取到簡介,則簡介為空n movies[x][description] = n else:n # 將新獲取的欄位填入moviesn movies[x][description] = descriptionn finally:n passnn # 適當休息,避免請求頻發被禁止,報403 Forbidden錯誤n time.sleep(0.5)n
最後,可以將獲取的電影數據寫入txt文件,以便後續使用。
fw = open(douban_movies.txt, w)n# 寫入一行表頭,用於說明每個欄位的意義nfw.write(title^rate^url^cover^id^descriptionn)nfor item in movies:n # 用^作為分隔符n # 主要是為了避免中文裡可能包含逗號發生衝突n fw.write(item[title] + ^ + item[rate] + ^ + item[url] + ^ + item[cover] + ^ + item[id] + ^ + item[description] + n)nfw.close()n
其他內容
這次的實戰其實是我一個Github項目的一部分,Honlan/data-visualize-chain。這個項目以豆瓣電影為例,展示了如何進行數據獲取、清洗、存儲、分析和可視化,和爬蟲相關的代碼都在spider文件夾下。所以如果希望了解更多,以及對BeautifulSoup用法感興趣,可以進一步研究。
另外,以上代碼最終獲取了五千部左右的電影數據,明顯少於豆瓣電影的總量,說明之前所使用的API能獲取的數據量十分有限。那應該怎麼辦呢?在豆瓣電影的網站上找一找,會發現這麼個鏈接,豆瓣電影標籤,點進去之後簡直是到了一個嶄新的世界。在這個頁面中提供了各種分類標籤,每個標籤下的電影數量也十分驚人,全部爬取一遍能收穫的數據量必然相當可觀。所以只要按照之前所講的通用思路,類似地寫代碼爬取即可。
視頻鏈接:
- 思路分析
- 代碼實現
推薦閱讀:
※從零開始寫Python爬蟲 --- 2.4 爬蟲實踐:代理的爬取和驗證
※從零開始寫Python爬蟲 --- 3.1 Selenium模擬瀏覽器
※從零開始寫Python爬蟲 --- 爬蟲實踐:螺紋鋼數據&Cookies