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

個人博客地址:woodenrobot.me

前言

經過上一篇教程我們已經大致了解了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 scrapynnnclass DoubanMovieItem(scrapy.Item):n # 排名n ranking = scrapy.Field()n # 電影名稱n movie_name = scrapy.Field()n # 評分n score = scrapy.Field()n # 評論人數n score_num = scrapy.Field()n

爬蟲程序

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

from scrapy.spiders import Spidernfrom scrapyspider.items import DoubanMovieItemnnnclass DoubanMovieTop250Spider(Spider):n name = douban_movie_top250n start_urls = [https://movie.douban.com/top250]n n def parse(self, response):n item = DoubanMovieItem()n

這個一個基本的scrapy的spider的model,首先我們要導入Scrapy.spiders中的Spider類,以及scrapyspider.items中我們剛剛定義好的DoubanMovieItem。n接著創建我們自己的爬蟲類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):n return [scrapy.FormRequest("http://www.example.com/login",n formdata={user: john, pass: secret},n callback=self.logged_in)]nndef logged_in(self, response):n # here you would extract links to follow and return Requests forn # each of them, with another callbackn passn

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 的可迭代的對象。

參數:tresponse (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網站學習一下,很快就能上手。n首先我們在chrome瀏覽器里進入豆瓣電影TOP250頁面並按F12打開開發者工具。

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

<ol class="grid_view">n <li>n <div class="item">n <div class="pic">n <em class="">1</em>n <a href="https://movie.douban.com/subject/1292052/">n <img alt="肖申克的救贖" src="https://img3.doubanio.com/view/movie_poster_cover/ipst/public/p480747492.jpg" class="">n </a>n </div>n <div class="info">n <div class="hd">n <a href="https://movie.douban.com/subject/1292052/" class="">n <span class="title">肖申克的救贖</span>n <span class="title"> / The Shawshank Redemption</span>n <span class="other"> / 月黑高飛(港) / 刺激1995(台)</span>n </a>nnn <span class="playable">[可播放]</span>n </div>n <div class="bd">n <p class="">n 導演: 弗蘭克·德拉邦特 Frank Darabont   主演: 蒂姆·羅賓斯 Tim Robbins /...<br>n 1994 / 美國 / 犯罪 劇情n </p>nn n <div class="star">n <span class="rating5-t"></span>n <span class="rating_num" property="v:average">9.6</span>n <span property="v:best" content="10.0"></span>n <span>766719人評價</span>n </div>nn <p class="quote">n <span class="inq">希望讓人自由。</span>n </p>n </div>n </div>n </div>n </li>n ...n ...n ...n</ol>n

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

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

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

運行爬蟲

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

scrapy crawl douban_movie_top250 -o douban.csvn

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

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

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

from scrapy import Requestnfrom scrapy.spiders import Spidernfrom scrapyspider.items import DoubanMovieItemnnnclass DoubanMovieTop250Spider(Spider):n name = douban_movie_top250n headers = {n 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,n }nn def start_requests(self):n url = https://movie.douban.com/top250n yield Request(url, headers=self.headers)nn def parse(self, response):n item = DoubanMovieItem()n movies = response.xpath(//ol[@class="grid_view"]/li)n for movie in movies:n item[ranking] = movie.xpath(n .//div[@class="pic"]/em/text()).extract()[0]n item[movie_name] = movie.xpath(n .//div[@class="hd"]/a/span[1]/text()).extract()[0]n item[score] = movie.xpath(n .//div[@class="star"]/span[@class="rating_num"]/text()n ).extract()[0]n item[score_num] = movie.xpath(n .//div[@class="star"]/span/text()).re(ur(d+)人評價)[0]n yield itemn

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

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

自動翻頁

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

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

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

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

from scrapy import Requestnfrom scrapy.spiders import Spidernfrom scrapyspider.items import DoubanMovieItemnnnclass DoubanMovieTop250Spider(Spider):n name = douban_movie_top250n headers = {n 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,n }nn def start_requests(self):n url = https://movie.douban.com/top250n yield Request(url, headers=self.headers)nn def parse(self, response):n item = DoubanMovieItem()n movies = response.xpath(//ol[@class="grid_view"]/li)n for movie in movies:n item[ranking] = movie.xpath(n .//div[@class="pic"]/em/text()).extract()[0]n item[movie_name] = movie.xpath(n .//div[@class="hd"]/a/span[1]/text()).extract()[0]n item[score] = movie.xpath(n .//div[@class="star"]/span[@class="rating_num"]/text()n ).extract()[0]n item[score_num] = movie.xpath(n .//div[@class="star"]/span/text()).re(ur(d+)人評價)[0]n yield itemnn next_url = response.xpath(//span[@class="next"]/a/@href).extract()n if next_url:n next_url = https://movie.douban.com/top250 + next_url[0]n yield Request(next_url, headers=self.headers)n

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

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

結尾

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

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


推薦閱讀:

9、續7--文章的編寫頁面(略)
為什麼python中不建議在for循環中修改列表?
Python技術分享的亂象
Python 黑帽編程大綱(變化中)

TAG:Python | 爬虫 | scrapy |