Scrapy爬蟲框架教程(二)-- 爬取豆瓣電影TOP250

前言

經過上一篇教程我們已經大致瞭解了Scrapy的基本情況,並寫了一個簡單的小demo。這次我會以爬取豆瓣電影TOP250爲例進一步爲大家講解一個完整爬蟲的流程。

工具和環境

  1. 語言:python 2.7
  2. IDE: Pycharm
  3. 瀏覽器:Chrome
  4. 爬蟲框架:Scrapy 1.2.1

教程正文

觀察頁面結構

首先我們打開豆瓣電影TOP250的頁面

 

通過觀察頁面決定讓我們的爬蟲獲取每一部電影的排名、電影名稱、評分和評分的人數。

聲明Item

什麼是Items呢?官方文檔Items定義如下:

Items
爬取的主要目標就是從非結構性的數據源提取結構性數據,例如網頁。 Scrapy spider可以以python的dict來返回提取的數據.雖然dict很方便,並且用起來也熟悉,但是其缺少結構性,容易打錯字段的名字或者返回不一致的數據,尤其在具有多個spider的大項目中。
爲了定義常用的輸出數據,Scrapy提供了 Item 類。 Item 對象是種簡單的容器,保存了爬取到得數據。 其提供了 類似於詞典(dictionary-like) 的API以及用於聲明可用字段的簡單語法。
許多Scrapy組件使用了Item提供的額外信息: exporter根據Item聲明的字段來導出數據、 序列化可以通過Item字段的元數據(metadata)來定義、 trackref 追蹤Item實例來幫助尋找內存泄露 (see 使用 trackref 調試內存泄露) 等等。

Item使用簡單的class定義語法以及Field對象來聲明。我們打開scrapyspider目錄下的items.py文件寫入下列代碼聲明Item:

import scrapy


class DoubanMovieItem(scrapy.Item):
    # 排名
    ranking = scrapy.Field()
    # 電影名稱
    movie_name = scrapy.Field()
    # 評分
    score = scrapy.Field()
    # 評論人數
    score_num = scrapy.Field()

爬蟲程序

在scrapyspider/spiders目錄下創建douban_spider.py文件,並寫入初步的代碼:

from scrapy.spiders import Spider
from scrapyspider.items import DoubanMovieItem


class DoubanMovieTop250Spider(Spider):
    name = 'douban_movie_top250'
    start_urls = ['https://movie.douban.com/top250']
    
    def parse(self, response):
        item = DoubanMovieItem()

這個一個基本的scrapy的spider的model,首先我們要導入Scrapy.spiders中的Spider類,以及scrapyspider.items中我們剛剛定義好的DoubanMovieItem。 接着創建我們自己的爬蟲類DoubanMovieTop250Spider並繼承Spider類,scrapy.spiders中有很多不同的爬蟲類可供我們繼承,一般情況下使用Spider類就可以滿足要求。(其他爬蟲類的使用可以去參考官方文檔)。

Spider
class scrapy.spider.Spider
Spider是最簡單的spider。每個其他的spider必須繼承自該類(包括Scrapy自帶的其他spider以及您自己編寫的spider)。 Spider並沒有提供什麼特殊的功能。 其僅僅請求給定的 start_urls/start_requests ,並根據返回的結果(resulting responses)調用spider的 parse 方法。
name 定義spider名字的字符串(string)。spider的名字定義了Scrapy如何定位(並初始化)spider,所以其必須是唯一的。 不過您可以生成多個相同的spider實例(instance),這沒有任何限制。 name是spider最重要的屬性,而且是必須的。
如果該spider爬取單個網站(single domain),一個常見的做法是以該網站(domain)(加或不加 後綴 )來命名spider。 例如,如果spider爬取 mywebsite.com ,該spider通常會被命名爲 mywebsite 。
allowed_domains 可選。包含了spider允許爬取的域名(domain)列表(list)。 當 OffsiteMiddleware 啓用時, 域名不在列表中的URL不會被跟進。
start_urls URL列表。當沒有制定特定的URL時,spider將從該列表中開始進行爬取。 因此,第一個被獲取到的頁面的URL將是該列表之一。 後續的URL將會從獲取到的數據中提取。
start_requests() 該方法必須返回一個可迭代對象(iterable)。該對象包含了spider用於爬取的第一個Request。
當spider啓動爬取並且未制定URL時,該方法被調用。 當指定了URL時,make_requests_from_url() 將被調用來創建Request對象。 該方法僅僅會被Scrapy調用一次,因此您可以將其實現爲生成器。
該方法的默認實現是使用 start_urls 的url生成Request。
如果您想要修改最初爬取某個網站的Request對象,您可以重寫(override)該方法。 例如,如果您需要在啓動時以POST登錄某個網站,你可以這麼寫:
def start_requests(self):
    return [scrapy.FormRequest("http://www.example.com/login",
                               formdata={'user': 'john', 'pass': 'secret'},
                               callback=self.logged_in)]

def logged_in(self, response):
    # here you would extract links to follow and return Requests for
    # each of them, with another callback
    pass
make_requests_from_url(url) 該方法接受一個URL並返回用於爬取的 Request 對象。 該方法在初始化request時被 start_requests() 調用,也被用於轉化url爲request。
默認未被複寫(overridden)的情況下,該方法返回的Request對象中, parse() 作爲回調函數,dont_filter參數也被設置爲開啓。 (詳情參見 Request).
parse(response) 當response沒有指定回調函數時,該方法是Scrapy處理下載的response的默認方法。
parse 負責處理response並返回處理的數據以及(/或)跟進的URL。 Spider 對其他的Request的回調函數也有相同的要求。
該方法及其他的Request回調函數必須返回一個包含 Request 及(或) Item 的可迭代的對象。
參數: response (Response) – 用於分析的response
log(message[, level, component]) 使用 scrapy.log.msg() 方法記錄(log)message。 log中自動帶上該spider的 name 屬性。 更多數據請參見 Logging 。
closed(reason) 當spider關閉時,該函數被調用。 該方法提供了一個替代調用signals.connect()來監聽 spider_closed 信號的快捷方式。

提取網頁信息

我們使用xpath語法來提取我們所需的信息。 不熟悉xpath語法的可以在W3School網站學習一下,很快就能上手。 首先我們在chrome瀏覽器裏進入豆瓣電影TOP250頁面並按F12打開開發者工具。

 

點擊工具欄左上角的類鼠標符號圖標或者Ctrl + Shift + c在頁面中點擊我們想要的元素即可在工具欄中看到它在網頁HTML源碼中所處的位置。 一般抓取時會以先抓大再抓小的原則來抓取。通過觀察我們看到該頁面所有影片的信息都位於一個class屬性爲grid_view的ol標籤內的li標籤內。

<ol class="grid_view">
        <li>
            <div class="item">
                <div class="pic">
                    <em class="">1</em>
                    <a href="https://movie.douban.com/subject/1292052/">
                        <img alt="肖申克的救贖" src="https://img3.doubanio.com/view/movie_poster_cover/ipst/public/p480747492.jpg" class="">
                    </a>
                </div>
                <div class="info">
                    <div class="hd">
                        <a href="https://movie.douban.com/subject/1292052/" class="">
                            <span class="title">肖申克的救贖</span>
                                    <span class="title">&nbsp;/&nbsp;The Shawshank Redemption</span>
                                <span class="other">&nbsp;/&nbsp;月黑高飛(港)  /  刺激1995(臺)</span>
                        </a>


                            <span class="playable">[可播放]</span>
                    </div>
                    <div class="bd">
                        <p class="">
                            導演: 弗蘭克·德拉邦特 Frank Darabont&nbsp;&nbsp;&nbsp;主演: 蒂姆·羅賓斯 Tim Robbins /...<br>
                            1994&nbsp;/&nbsp;美國&nbsp;/&nbsp;犯罪 劇情
                        </p>

                        
                        <div class="star">
                                <span class="rating5-t"></span>
                                <span class="rating_num" property="v:average">9.6</span>
                                <span property="v:best" content="10.0"></span>
                                <span>766719人評價</span>
                        </div>

                            <p class="quote">
                                <span class="inq">希望讓人自由。</span>
                            </p>
                    </div>
                </div>
            </div>
        </li>
        ...
        ...
        ...
</ol>

因此我們根據以上原則對所需信息進行抓取

from scrapy.spiders import Spider
from scrapyspider.items import DoubanMovieItem


class DoubanMovieTop250Spider(Spider):
    name = 'douban_movie_top250'
    start_urls = ['https://movie.douban.com/top250']
    
    def parse(self, response):
        item = DoubanMovieItem()
        movies = response.xpath('//ol[@class="grid_view"]/li')
        for movie in movies:
            item['ranking'] = movie.xpath(
                './/div[@class="pic"]/em/text()').extract()[0]
            item['movie_name'] = movie.xpath(
                './/div[@class="hd"]/a/span[1]/text()').extract()[0]
            item['score'] = movie.xpath(
                './/div[@class="star"]/span[@class="rating_num"]/text()'
            ).extract()[0]
            item['score_num'] = movie.xpath(
                './/div[@class="star"]/span/text()').re(ur'(\d+)人評價')[0]
            yield item

對於Scrapy提取頁面信息的內容詳情可以參照官方文檔的相應章節

運行爬蟲

在項目文件夾內打開cmd運行下列命令:

scrapy crawl douban_movie_top250 -o douban.csv

注意此處的douban_movie_top250即爲我們剛剛寫的爬蟲的name, 而-o douban.csv是scrapy提供的將item輸出爲csv格式的快捷方式

試着運行一下爬蟲怎麼什麼也沒輸出呢?!!!

 

辛辛苦苦到了這裏難道要失敗了嗎?!!! 不要急我們看下一控制檯輸出的信息,原來是403錯誤了。這是因爲豆瓣對爬蟲設了一個小小的門檻,我們只需要更改一下發送請求時的請求頭user-agent即可。

from scrapy import Request
from scrapy.spiders import Spider
from scrapyspider.items import DoubanMovieItem


class DoubanMovieTop250Spider(Spider):
    name = 'douban_movie_top250'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36',
    }

    def start_requests(self):
        url = 'https://movie.douban.com/top250'
        yield Request(url, headers=self.headers)

    def parse(self, response):
        item = DoubanMovieItem()
        movies = response.xpath('//ol[@class="grid_view"]/li')
        for movie in movies:
            item['ranking'] = movie.xpath(
                './/div[@class="pic"]/em/text()').extract()[0]
            item['movie_name'] = movie.xpath(
                './/div[@class="hd"]/a/span[1]/text()').extract()[0]
            item['score'] = movie.xpath(
                './/div[@class="star"]/span[@class="rating_num"]/text()'
            ).extract()[0]
            item['score_num'] = movie.xpath(
                './/div[@class="star"]/span/text()').re(ur'(\d+)人評價')[0]
            yield item

更改後的代碼是不是覺得有些地方不太一樣了?start_urls怎麼不見了?start_requests函數又是幹什麼的?還記得剛纔對Spider類的介紹嗎?先回過頭複習一下上面關於start_urls和start_requests函數的介紹。簡單的說就是使用start_requests函數我們對初始URL的處理就有了更多的權利,比如這次給初始URL增加請求頭user_agent。

再次運行爬蟲,我們想要的信息都被下載到douban.scv文件夾裏了。直接用WPS打開即可查看信息。

 

自動翻頁

先別急着高興,你難道沒有發現一個問題嗎?這樣的話我們還是隻能爬到當前頁的25個電影的內容。怎麼樣才能把剩下的也一起爬下來呢? 實現自動翻頁一般有兩種方法:

  1. 在頁面中找到下一頁的地址;
  2. 自己根據URL的變化規律構造所有頁面地址。

一般情況下我們使用第一種方法,第二種方法適用於頁面的下一頁地址爲JS加載的情況。今天我們只說第一種方法。 首先利用Chrome瀏覽器的開發者工具找到下一頁的地址

然後在解析該頁面時獲取下一頁的地址並將地址交給調度器(Scheduler)

from scrapy import Request
from scrapy.spiders import Spider
from scrapyspider.items import DoubanMovieItem


class DoubanMovieTop250Spider(Spider):
    name = 'douban_movie_top250'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36',
    }

    def start_requests(self):
        url = 'https://movie.douban.com/top250'
        yield Request(url, headers=self.headers)

    def parse(self, response):
        item = DoubanMovieItem()
        movies = response.xpath('//ol[@class="grid_view"]/li')
        for movie in movies:
            item['ranking'] = movie.xpath(
                './/div[@class="pic"]/em/text()').extract()[0]
            item['movie_name'] = movie.xpath(
                './/div[@class="hd"]/a/span[1]/text()').extract()[0]
            item['score'] = movie.xpath(
                './/div[@class="star"]/span[@class="rating_num"]/text()'
            ).extract()[0]
            item['score_num'] = movie.xpath(
                './/div[@class="star"]/span/text()').re(ur'(\d+)人評價')[0]
            yield item

        next_url = response.xpath('//span[@class="next"]/a/@href').extract()
        if next_url:
            next_url = 'https://movie.douban.com/top250' + next_url[0]
            yield Request(next_url, headers=self.headers)

最後再運行一下爬蟲,打開douban.csv。是不是發現所有的影片信息都獲取到了,250個一個不多一個不少。

 

最後,利用WPS的篩選功能你可以篩選任意符合你要求的影片。(Ps:外來的和尚有時候不一定好唸經。記得要用WPS打開這個CVS文件,用EXCEL打開會因爲有中文而顯示不正常。)

結尾

從寫這個Scrapy爬蟲框架教程以來,我越來越覺得自己學會的東西再輸出出去沒有想象的那麼簡單,往往寫了幾個小時的教程最後發現還是沒有想表達的東西表達完美。如果有什麼說的不好的地方歡迎大家指正。聞道有先後,術業有專攻。大家互相學習: )

源碼地址:Wooden-Robot/scrapy-tutorial

 

補充:

可以通過創建main文件來實現VSCODE的debug效果,不用每次都通過指令執行

# -*- coding=utf8 -*-
from scrapy import cmdline

# TODO 執行爬蟲指令
cmdline.execute("scrapy crawl sample_spider".split())  # 執行sample_spider爬蟲
cmdline.execute("scrapy crawl sample_spider -o sample.json".split())  # 執行爬蟲並在當前目錄下生成一個sample.json文件(該文件是爬蟲結果)

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章