Python爬蟲 - scrapy - 爬取豆瓣電影TOP250

0.前言

新接觸爬蟲,通過一段時間的實踐,寫了幾個簡單爬蟲,爬取豆瓣電影的爬蟲例子網上有不少,但都很簡單,大部分只介紹了請求頁面和解析部分,對於新手而言,我但願可以有一個比較全面的實例。因此找了不少實例和文章,並整合在一塊兒,在現有豆瓣爬蟲的基礎上,增長了一些內容,算是比較全的內容了。主要包括項目創建、請求頁面、xpath解析、自動翻頁、數據輸出、編碼處理等等。。php

系統環境

System Version:Ubuntu 16.04
Python Version:3.5.2
Scrapy Version:1.5.0html

1.創建項目

執行以下命令創建scrapy爬蟲項目linux

scrapy startproject spider_douban

命令執行完成後,創建了spider_douban文件夾,目錄結構以下:web

.
├── scrapy.cfg
└── spider_douban
    ├── __init__.py
    ├── items.py
    ├── middlewares.py
    ├── pipelines.py
    ├── settings.py
    └── spiders
        ├── douban_spider.py
        └── __init__.py

2.創建爬蟲數據模型

打開./spider_douban/items.py文件,編輯內容以下:正則表達式

import scrapy

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

3.新建爬蟲文件

新建./spiders/douban_spider.py文件,編輯內容以下:瀏覽器

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

class DoubanMovieTop250Spider(Spider):
    name = 'douban_movie_top250'
    start_urls = {
        'https://movie.douban.com/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')
        print(movies)
        print('=============================================')
        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(r'(\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)

爬蟲文件各部分功能記錄

douban_spider.py文件主要有幾部分構成。服務器

導入模塊

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

Request類用於請求要爬取的頁面數據
Spider類是爬蟲的基類
DoubanMovieItem是咱們第一步創建的爬取數據模型併發

初始設置

基於spider類定義的爬蟲類DoubanMovieTop250Spider中,首先定義爬蟲的基本信息:框架

name:在項目中爬蟲的名稱,能夠在項目目錄中執行 scrapy list獲取已經定義的爬蟲列表
start_urls:是爬取的第一個頁面地址
headers:是向web服務器發送頁面請求的時候附加的user-agent消息,告訴web服務器是什麼類型的瀏覽器或設備在請求頁面,對於不具有簡單反爬機制的網站,headers部分能夠省略。

爲了迷惑web服務器,通常會在爬蟲發送web請求的時候定義user-agent信息,這裏有兩種寫法。dom

  • header的第一種定義:
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)

能夠看到,這種寫法中,start_urls定義沒有了,轉而定義了start_requests函數,開始的url寫到了函數裏。同時,定義了headers字典,在發送Request請求的時候,將headers字典一併發送。這種寫法簡單直觀,缺點是在一個爬蟲項目執行期間,全部請求都是一個User-Agent屬性。

  • header的第二種定義:
start_urls = {
        'https://movie.douban.com/top250'
        }

簡單、直接的定義start_urls屬性,而Request中的header屬性經過其餘方法另外定義,容後再說。

parse處理函數

逐句分解說明
1.基於咱們定義的DoubanMovieItem類建立item實例

item = DoubanMovieItem()

2.解析頁面 - 獲取內容框架
clipboard.png

clipboard.png

經過分析頁面源碼,咱們可以看到,頁面中的電影信息是保存在了<ol>標籤中,這個<ol>標籤有一個獨特的樣式表grid_view,而每個單獨的電影信息保存在了<li>標籤中,下面代碼獲取class屬性爲grid_view<ol>標籤下的全部<li>標籤內容。

movies = response.xpath('//ol[@class="grid_view"]/li')

3.解析頁面 - 獲取分項

在每個<li>標籤中,還有內部結構,經過xpath()解析,將每一項內容解析出來,賦值給item實例中的各個字段。經過查看movie.douban.com/top250頁面的源碼能夠很容易找到這個標籤訂義的內容。若是咱們經過type()函數查看movies的變量類型,能夠發現他的類型是<class 'scrapy.selector.unified.SelectorList'><ol>標籤中的每個<li>標籤都是這個列表中的一項,那麼就能夠對movies作迭代。

首先看看<li>標籤中的頁面結構:

clipboard.png

能夠看到要提取數據的各部分所在標籤位置:

排名:class屬性爲pic的 <div>標籤下,, <em>標籤中...
電影名:class屬性爲hd的 <div>標籤下, <a>標籤中的第一個 <span>標籤...
評分:class屬性爲star的 <div>標籤下,class屬性爲rating_num的 <span>標籤中...
評論人數:class屬性爲star的 <div>標籤下, <span>標籤中。因爲使用了re正則表達式,因此沒有特別指定是哪個 <span>標籤。

回到代碼部分,對以前定義的movies作迭代,逐項獲取要抓取的數據。

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(r'(\d+)人評價')[0]
            yield item

4.Url跳轉(翻頁)

若是到此爲止,咱們能夠將https://movie.douban.com/top250頁面中的第一頁內容爬取到,但只有25項記錄,要爬取所有的250條記錄,就要執行下面代碼:

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)

首先經過xpath解析了頁面中後頁的連接,並賦值給next_url變量,若是咱們當前在第一頁,那麼解析後頁的連接就是?start=25&filter=。將解析的後頁連接與完整url鏈接造成完整的地址,再次執行Request(),就實現了對所有250條記錄的爬取。注意:經過xpath解析出的結果是列表,因此在引用的時候寫成next_url[0]

4.處理隨機Head屬性(隨機User-Agent)

實現隨機的head屬性發送。主要改兩個文件:

settings.py

USER_AGENT_LIST = [
    'zspider/0.9-dev http://feedback.redkolibri.com/',
    'Xaldon_WebSpider/2.0.b1',
    'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) Speedy Spider (http://www.entireweb.com/about/search_tech/speedy_spider/)',
    'Mozilla/5.0 (compatible; Speedy Spider; http://www.entireweb.com/about/search_tech/speedy_spider/)',
    'Speedy Spider (Entireweb; Beta/1.3; http://www.entireweb.com/about/search_tech/speedyspider/)',
    'Speedy Spider (Entireweb; Beta/1.2; http://www.entireweb.com/about/search_tech/speedyspider/)',
    'Speedy Spider (Entireweb; Beta/1.1; http://www.entireweb.com/about/search_tech/speedyspider/)',
    'Speedy Spider (Entireweb; Beta/1.0; http://www.entireweb.com/about/search_tech/speedyspider/)',
    'Speedy Spider (Beta/1.0; www.entireweb.com)',
    'Speedy Spider (http://www.entireweb.com/about/search_tech/speedy_spider/)',
    'Speedy Spider (http://www.entireweb.com/about/search_tech/speedyspider/)',
    'Speedy Spider (http://www.entireweb.com)',
    'Sosospider+(+http://help.soso.com/webspider.htm)',
    'sogou spider',
    'Nusearch Spider (www.nusearch.com)',
    'nuSearch Spider (compatible; MSIE 4.01; Windows NT)',
    'lmspider (lmspider@scansoft.com)',
    'lmspider lmspider@scansoft.com',
    'ldspider (http://code.google.com/p/ldspider/wiki/Robots)',
    'iaskspider/2.0(+http://iask.com/help/help_index.html)',
    'iaskspider',
    'hl_ftien_spider_v1.1',
    'hl_ftien_spider',
    'FyberSpider (+http://www.fybersearch.com/fyberspider.php)',
    'FyberSpider',
    'everyfeed-spider/2.0 (http://www.everyfeed.com)',
    'envolk[ITS]spider/1.6 (+http://www.envolk.com/envolkspider.html)',
    'envolk[ITS]spider/1.6 ( http://www.envolk.com/envolkspider.html)',
    'Baiduspider+(+http://www.baidu.com/search/spider_jp.html)',
    'Baiduspider+(+http://www.baidu.com/search/spider.htm)',
    'BaiDuSpider',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0) AddSugarSpiderBot www.idealobserver.com',
    ]

DOWNLOADER_MIDDLEWARES = {
    'spider_douban.middlewares.RandomUserAgentMiddleware': 400,
    'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': None,
}

USER_AGENT_LIST定義了一些瀏覽器user-agent屬性,網上有不少,能夠找來直接加進去,須要注意的是有些user-agent信息是移動設備(手機或平板)的,若是不注意的話,可能請求到的數據與你看到的數據有較大差別;
DOWNLOADER_MIDDLEWARES定義了下載器中間件,它在發送頁面請求數據的時候被調用。

middlewares.py

from spider_douban.settings import USER_AGENT_LIST
import random

class RandomUserAgentMiddleware():
    def process_request(self, request, spider):
        ua  = random.choice(USER_AGENT_LIST)
        if ua:
            request.headers.setdefault('User-Agent', ua)

RandomUserAgentMiddleware()中,每次發送請求數據,會在USER_AGENT_LIST中隨機選擇一條User-Agent記錄。

5.結果保存

編輯pipelines.py文件:

from scrapy import signals
from scrapy.contrib.exporter import CsvItemExporter

class SpiderDoubanPipeline(CsvItemExporter):
    def __init__(self):
        self.files = {}

    @classmethod
    def from_crawler(cls, crawler):
        print('==========pipeline==========from_crawler==========')
        pipeline = cls()
        crawler.signals.connect(pipeline.spider_opened, signals.spider_opened)
        crawler.signals.connect(pipeline.spider_closed, signals.spider_closed)
        return pipeline

    def spider_opened(self, spider):
        savefile = open('douban_top250_export.csv', 'wb+')
        self.files[spider] = savefile
        print('==========pipeline==========spider_opened==========')
        self.exporter = CsvItemExporter(savefile)
        self.exporter.start_exporting()

    def spider_closed(self, spider):
        print('==========pipeline==========spider_closed==========')
        self.exporter.finish_exporting()
        savefile = self.files.pop(spider)
        savefile.close()

    def process_item(self, item, spider):
        print('==========pipeline==========process_item==========')
        print(type(item))
        self.exporter.export_item(item)
        return item

SpiderDoubanPipeline類是創建項目的時候自行創建的,爲了保存文件,作了修改。

def from_crawler(cls, crawler):

  • 若是存在,則調用此類方法從Crawler建立pipeline實例。它必須返回一個新的pipeline實例。抓取對象提供對全部Scrapy核心組件的訪問,如settings和signals; 這是pipeline訪問它們並將其功能掛接到Scrapy的一種方式。

在此方法中,定義了一個數據收集器(cls)的實例:‘pipeline’。

signals:Scrapy使用信號來通知事情發生。您能夠在您的Scrapy項目中捕捉一些信號(使用 extension)來完成額外的工做或添加額外的功能,擴展Scrapy。雖然信號提供了一些參數,不過處理函數不用接收全部的參數 - 信號分發機制(singal dispatching mechanism)僅僅提供處理器(handler)接受的參數。您能夠經過 信號(Signals) API 來鏈接(或發送您本身的)信號。

connect:連接一個接收器函數(receiver function) 到一個信號(signal)。signal能夠是任何對象,雖然Scrapy提供了一些預先定義好的信號。

def spider_opened(self, spider):

  • 當spider開始爬取時發送該信號。該信號通常用來分配spider的資源,不過其也能作任何事。該信號支持返回deferreds。

此方法中,建立了一個文件對象實例:savefile

CsvItemExporter(savefile):輸出 csv 文件格式. 若是添加 fields_to_export 屬性, 它會按順序定義CSV的列名.

def spider_closed(self, spider):

  • 當某個spider被關閉時,該信號被髮送。該信號能夠用來釋放每一個spider在 spider_opened 時佔用的資源。該信號支持返回deferreds。

def process_item(self, item, spider):

  • 每一個item pipeline組件都須要調用該方法,這個方法必須返回一個 Item (或任何繼承類)對象, 或是拋出 DropItem 異常,被丟棄的item將不會被以後的pipeline組件所處理。

啓用pipeline

爲了讓咱們定義的pipeline生效,要在settings.py文件中,打開ITEM_PIPELINES註釋:

ITEM_PIPELINES = {
    'spider_douban.pipelines.SpiderDoubanPipeline': 300,
}

6.執行爬蟲

scrapy crawl douban_movie_top250

執行爬蟲可以看到爬取到的數據。。。

若是以前pipeline部分代碼沒有寫,也能夠用下面的命令,在爬蟲執行的時候直接導出數據:

scrapy crawl douban_movie_top250 -o douban.csv

增長-o參數,能夠將爬取到的數據保存到douban.csv文件中。。

7.文件編碼的問題

我在linux服務器執行爬蟲,生成csv文件後,在win7系統中用excel打開變成亂碼。在網上找了一些文章,有的文章直接改變linux文件默認編碼,可是感受這麼作會對其餘項目產生影響。最後選擇一個相對簡單的方式。按這幾步執行就能夠:

  1. 不要直接用excel打開csv文件。先打開excel,創建空白工做表。
  2. 選擇數據選項卡,打開獲取外部數據中的自文本
  3. 導入文本文件對話框中選擇要導入的csv文件。
  4. 文本導入嚮導 - 第1步中,設置文件原始格式65001 : Unicode (UTF-8)
  5. 繼續下一步選擇逗號分隔,就能夠導入正常文本了。
相關文章
相關標籤/搜索