全棧 - 9 實戰 爬取豆瓣電影資料

張巨集倫發表於2017-03-03

這是全棧資料工程師養成攻略系列教程的第九期:9 實戰 爬取豆瓣電影資料。

掌握了爬蟲的基本原理和程式碼實現,現在讓我們通過實戰專案鞏固一下。

確定目標

在寫爬蟲之前應當想清楚:我需要哪方面的資料?需要包含哪些欄位?這些資料需要以何種形式呈現?

很多網站往往都是大家爬取的物件,例如提供住房資訊的鏈家網,提供書評和影評資訊的豆瓣網,以及提供餐飲生活娛樂資訊的大眾點評網。當然,這些網站之所以能很容易地被我們爬取,也是因為它們採取了內容開放的運營態度,沒有進行過多的反爬處理。

有一個挺有意思的名詞叫做“三月爬蟲”,即在三月份左右頻繁出現的大量小規模爬蟲。這些爬蟲從何而來?因為在每年的這個時候,學生們都要做課設專案了,沒有資料怎麼辦?嗯,靠爬!

其實爬蟲和反爬蟲之間就好比矛與盾的關係,我們可以花更多的心思、時間和成本去爬取資料,資料運營方同樣可以花更多的技術、金錢和人力以保護資料。識別程式碼請求並禁止,我們可以偽裝成瀏覽器;對IP頻繁請求採取限制,我們可以使用IP代理池;要求登入並輸入複雜的驗證碼,我們同樣可以模擬登入以及想出相應的解決辦法。總而言之,沒有一定能爬到的資料,也沒有一定爬不到的資料,無非是攻守雙方的博弈,看誰下的功夫更深、投入成本更多。

當然,之前介紹的都是最基礎的爬取方法,所針對的也是採取開放運營態度,或者暫未採取防爬措施的網站。方法雖簡單,但依舊足以爬取相當多的網站,至於爬蟲的進一步深入研究,則需要花費更多時間去學習,這也是為什麼有專業的爬蟲工程師這一方向了。

在這次的專案實戰中,我們需要獲取豆瓣電影上的電影資料,數量自然是越多越好,每條資料應當包含電影名稱、導演、演員、型別、片長、語言、上映時間、上映地區、評分等資訊,這樣在獲取資料並儲存之後便可進行後續分析和展示。

通用思路

寫爬蟲時往往會遵循以下通用思路:首先得找到一個彙總頁,以鏈家網為例,可以是首頁或搜尋頁。在彙總頁中是一條條房源,以列表形式依次排列,可能一頁會安排幾十條房源,看完之後可以通過翻頁功能跳轉至下一頁,從而進行對全部房源的瀏覽;彙總頁中的每一個連結都對應一條房源的詳情頁,點進去即可檢視房源的詳細資訊。這些詳情頁都是用同樣的模版渲染出來的,只不過渲染時使用了不同的資料,因此十分便於批量獲取,只要對詳情頁的頁面結構進行分析和提取即可。

當然,以上所提的二層結構是最理想的情況,實際問題中未必能找到這樣一個能直接涵蓋並通往全部詳情頁的彙總頁,因此三層、四層乃至更復雜的結構也完全可能出現。例如從鏈家的首頁開始,先下鑽到城市,再深入到地區,接著按戶型進行分類,最後才能找到所對應的房源。其實我們會發現,每一層結構都對應著最終詳情頁的一個欄位,例如房源的城市、地區、戶型等資訊,多層結構無非只是對二層結構按照若干個欄位進行了逐層聚合,所以只要理清楚網站資料的整理結構,接下來的程式碼工作都是類似的。

尋找連結

回到這次的專案實戰上,我們首先訪問一下豆瓣電影的首頁,movie.douban.com/,探探路子,看看該如何去尋找之前提及的彙總頁和詳情頁。

我們首先看到豆瓣提供了一個正在熱映模組,展示當前正在上映的一些電影。由於我們希望儘可能多地獲取電影資料,當然包括歷史電影,所以這塊忽略不計。

接下來的頁面中有一個按電影分類進行篩選的模組,如下圖所示,可以根據熱門、最新、經典、可播放等標籤顯示相應的電影。按熱度排序、按時間排序和按評價排序意義不大,希望我們希望獲得儘可能多的電影資料全集,對排序則不關心。再往下有個載入更多的按鈕,我們會發現每次點選之後,頁面上就會出現更多對應類別的電影,說明網頁相應地又向服務端請求了更多資料。

全棧 - 9 實戰 爬取豆瓣電影資料

所以爬取的基本思路有了,首先獲得全部的類別標籤,然後針對每個標籤不斷地請求相應的電影資料,最後從每部電影的詳情頁獲取所需的欄位。讓我們用Chrome開發者工具來找出應當請求哪些連結,開啟開發者工具之後重新整理豆瓣電影首頁,我們會發現在NetworkXHR中有這麼個連結,movie.douban.com/j/search_ta…,從名字上來看似乎是返回電影標籤,在瀏覽器中訪問果不其然,得到了以下內容:

{"tags":["熱門","最新","經典","可播放","豆瓣高分","冷門佳片","華語","歐美","韓國","日本","動作","喜劇","愛情","科幻","懸疑","恐怖","動畫"]}複製程式碼

說明這是一個GET型別的API,返回一個json格式的字串,如果在Python中載入成字典之後,則包含一個鍵tags,對應的值是一個列表,裡面的每一項都是一個電影標籤。

我們還順便發現了另一個GET類API,movie.douban.com/j/search_su…,可以根據提供的標籤、排序方法、每頁數量、每頁開始編號等引數返回相應的電影資料,這裡是按推薦程度排名,從0號開始,返回熱門標籤下的20條電影資料。在瀏覽器中訪問以上鍊接,得到的也是一個json格式字串,同樣轉成Python字典再處理即可。如果點選載入更多按鈕,會發現網頁會繼續請求這個API,不同的只是page_start不斷增加,通過改變開始編號即可請求到新的資料。

設計下程式碼實現的過程:針對每個標籤,使用以上第二個API不斷請求資料,如果請求結果中包含資料,則將page_start增加20再繼續,直到返回結果為空,說明這一標籤下的電影資料已經全部拿到。

程式碼實現

我們已經掌握瞭如何用Python發起GET和POST請求,所以接下來的工作就是寫程式碼實現。

# 載入庫
import urllib
import urllib2
import json
from bs4 import BeautifulSoup

# 獲取所有標籤
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']

# 定義一個列表儲存電影的基本資訊
movies = []
# 處理每個tag
for tag in tags:
    start = 0
    # 不斷請求,直到返回結果為空
    while 1:
        # 拼接需要請求的連結,包括標籤和開始編號
        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)

        # 先在瀏覽器中訪問一下API,觀察返回json的結構
        # 然後在Python中取出需要的值 
        result = result['subjects']

        # 返回結果為空,說明已經沒有資料了
        # 完成一個標籤的處理,退出迴圈
        if len(result) == 0:
            break

        # 將每一條資料都加入movies
        for item in result:
            movies.append(item)

        # 使用迴圈記得修改條件
        # 這裡需要修改start
        start += 20

# 看看一共獲取了多少電影
print len(movies)複製程式碼

以上程式碼執行完畢之後,列表movies中即包含了全部的電影資料,其中的每一項都是一個字典,包含rate評分、title電影標題、url詳情頁連結、playable是否可播放、cover封面圖片連結、id電影的豆瓣id、is_new是否為新電影等欄位。

除了以上欄位,我們還希望獲取每部電影的更多資訊,因此需要進一步爬取各部電影所對應的詳情頁。以下是《瘋狂動物城》的豆瓣詳情頁,導演、編劇、主演、型別、語言、片長、簡介等,都是值得進一步爬取的欄位。

全棧 - 9 實戰 爬取豆瓣電影資料

由於電影詳情頁的url型別屬於Html,即訪問後返回經瀏覽器渲染的網頁內容,所以需要更加複雜的處理方法。BeautifulSoup包提供瞭解析html文字、查詢和選擇html元素、提取元素內容和屬性等功能,但要求一些html和css語法基礎。這裡僅以詳情頁中的電影簡介為例,展示下如何使用BeautifulSoup解析html文字,其他完整內容等後續章節介紹了相關基礎後再回過頭講解。

import time

# 請求每部電影的詳情頁面
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)
    # 提取電影簡介
    # 捕捉異常,有的電影詳情頁中並沒有簡介
    try:
        description = html.find_all("span", attrs={"property": "v:summary"})[0].get_text()
    except Exception, e:
        # 沒有提取到簡介,則簡介為空
        movies[x]['description'] = ''
    else:
        # 將新獲取的欄位填入movies
        movies[x]['description'] = description
    finally:
        pass

    # 適當休息,避免請求頻發被禁止,報403 Forbidden錯誤
    time.sleep(0.5)複製程式碼

最後,可以將獲取的電影資料寫入txt檔案,以便後續使用。

fw = open('douban_movies.txt', 'w')
# 寫入一行表頭,用於說明每個欄位的意義
fw.write('title^rate^url^cover^id^description\n')
for item in movies:
    # 用^作為分隔符
    # 主要是為了避免中文裡可能包含逗號發生衝突
    fw.write(item['title'] + '^' + item['rate'] + '^' + item['url'] + '^' + item['cover'] + '^' + item['id'] + '^' + item['description'] + '\n')
fw.close()複製程式碼

其他內容

這次的實戰其實是我一個Github專案的一部分,github.com/Honlan/data…。這個專案以豆瓣電影為例,展示瞭如何進行資料獲取、清洗、儲存、分析和視覺化,和爬蟲相關的程式碼都在spider資料夾下。所以如果希望瞭解更多,以及對BeautifulSoup用法感興趣,可以進一步研究。

另外,以上程式碼最終獲取了五千部左右的電影資料,明顯少於豆瓣電影的總量,說明之前所使用的API能獲取的資料量十分有限。那應該怎麼辦呢?在豆瓣電影的網站上找一找,會發現這麼個連結,movie.douban.com/tag/,點進去之後簡直是到了一個嶄新的世界。在這個頁面中提供了各種分類標籤,每個標籤下的電影數量也十分驚人,全部爬取一遍能收穫的資料量必然相當可觀。所以只要按照之前所講的通用思路,類似地寫程式碼爬取即可。

全棧 - 9 實戰 爬取豆瓣電影資料

視訊連結:

如果覺得文章不錯,不妨點一下左下方的喜歡~

相關文章