scrapy 進階使用

前段時間我寫了一篇《scrapy快速入門》,簡單介紹了一點scrapy的知識。最近個人搬瓦工讓牆了,並且我又學了一點mongodb的知識,因此此次就來介紹一些scrapy的進階知識,作一些真正有用的爬蟲來。php

scrapy組件介紹

首先先來介紹一下scrapy的體系結構和組件。css

  • scrapy引擎。顧名思義,它負責調用爬蟲爬取頁面並將數據交給管道來處理。
  • 調度程序。調度程序從引擎獲取請求,而後將這些請求交給相應的處理者來處理。
  • 下載器。下載器負責獲取web頁面,而後將它們交給引擎來處理。
  • 爬蟲。這是咱們要編寫的部分,爬蟲負責從頁面抽取要處理的數據,而後交由管道來處理。
  • 項目管道。爬蟲抽取出來的數據,就要交給管道來處理,咱們能夠編寫本身的管道來清洗、驗證和持久化數據。
  • 下載器中間件。下載器中間件在下載器和scrapy引擎之間工做,咱們能夠利用下載器中間件在將頁面交給爬蟲以前作一些事情。scrapy內置了一些下載器中間件,這些中間件將在後面介紹。
  • 爬蟲中間件。爬蟲中間件可讓咱們控制爬蟲的回調等等。在後面會介紹一些內置的爬蟲中間件。

下面是scrapy官網的結構圖,能夠幫助咱們理解scrapy的體系。
scrapy結構圖html

項目(Item)

在之前的爬蟲中咱們都是直接返回一個字典,其實這並非最佳實踐。scrapy提供了一個Item基類,咱們能夠經過繼承這個類定義本身的結構化數據,比處處傳遞字典更好。下面是官方文檔的例子。python

import scrapy

class Product(scrapy.Item):
    name = scrapy.Field()
    price = scrapy.Field()
    stock = scrapy.Field()
    last_updated = scrapy.Field(serializer=str)

這些項目類通常都定義在scrapy項目的items.py文件中。定義好以後,在爬蟲中咱們就不該該在反掌字典了,而是初始化並返回咱們自定義的Item對象。git

請求和響應對象

scrapy經過請求和響應對象來處理網頁請求,這部分的文檔能夠參考https://doc.scrapy.org/en/latest/topics/request-response.html。請求和響應還有一些子類,能夠幫助咱們完成更具體的工做。例如Request的子類FormRequest就能夠幫助咱們模擬用戶登陸。github

有時候須要模擬用戶登陸,這時候可使用FormRequest.from_response方法。這時候爬蟲功能稍有變化,parse函數用來發送用戶名和密碼,抽取數據的操做放在回調函數中進行。web

import scrapy

class LoginSpider(scrapy.Spider):
    name = 'example.com'
    start_urls = ['http://www.example.com/users/login.php']

    def parse(self, response):
        return scrapy.FormRequest.from_response(
            response,
            formdata={'username': 'john', 'password': 'secret'},
            callback=self.after_login
        )

    def after_login(self, response):
        # 檢查是否登陸成功
        if "authentication failed" in response.body:
            self.logger.error("Login failed")
            return

        # 在這裏繼續爬取數據

管道(pipeline)

管道用來處理爬蟲抽取到的數據,咱們能夠經過管道對數據進行驗證和持久化等操做。管道其實就是帶有process_item(self, item, spider)函數的一個普通類。下面是scrapy官方文檔的例子,這個例子驗證獲取到的數據是否存在價格字段,並丟棄沒有價格字段的無效數據。這裏還引用了scrapy預約義的DropItem異常,這個異常必須在管道中拋出,表示管道應該丟棄這個數據。若是想了解scrapy異常,能夠查看官方文檔算法

from scrapy.exceptions import DropItem

class PricePipeline(object):

    vat_factor = 1.15

    def process_item(self, item, spider):
        if item['price']:
            if item['price_excludes_vat']:
                item['price'] = item['price'] * self.vat_factor
            return item
        else:
            raise DropItem("Missing price in %s" % item)

管道不是必定義好就能用的,還須要在配置文件settings.py中激活。mongodb

ITEM_PIPELINES = {
    'myproject.pipelines.PricePipeline': 300,
    'myproject.pipelines.JsonWriterPipeline': 800,
}

將數據保存到MongoDB的管道

管道除了驗證數據,還能夠將數據保存到數據庫中。這時候僅僅一個process_item(self, item, spider)函數就不夠了。因此操做數據庫的管道還應該包含幾個函數用於創建和關閉數據庫鏈接。數據庫

下面的例子也是scrapy官方文檔的例子,演示了持久化數據管道的用法。這個管道是從類方法from_crawler(cls, crawler)中初始化出來的,該方法實際上讀取了scrapy的配置文件。這和直接將數據庫鏈接寫在代碼中相比,是更加通用的方式。初始化和關閉數據庫鏈接的操做都在對應的方法中執行。

import pymongo

class MongoPipeline(object):

    collection_name = 'scrapy_items'

    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI'),
            mongo_db=crawler.settings.get('MONGO_DATABASE', 'items')
        )

    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]

    def close_spider(self, spider):
        self.client.close()

    def process_item(self, item, spider):
        self.db[self.collection_name].insert_one(dict(item))
        return item

使用文件和圖片管道

文件和圖片管道處理過程

除了本身編寫管道以外,scrapy還預約義了幾個管道,能夠幫助咱們方便的保存文件和圖片。這些管道有如下特色:

  • 能夠避免重複下載最近的文件。
  • 指定文件保存位置(文件系統或者亞馬遜S3)

對於圖片管道來講還有額外功能:

  • 將圖片轉換成常見格式(JPG)和模式(RGB)
  • 生成圖片縮略圖
  • 只下載大於某長寬的圖片

使用文件管道的過程以下:

  1. 首先須要Item類中有file_urlsfiles兩個屬性,而後在爬蟲中將想爬取的文件地址放到file_urls屬性中,而後返回
  2. 在Item傳遞到文件管道的時候,調度程序會用下載器將地址對應的文件下載下來,將文件屬性(包括保存路徑等)放到files屬性中,file_urlsfiles中是一一對應的

使用圖片管道的過程是類似的,不過要操做的屬性是image_urlsimages

若是你不想使用這幾個屬性,其實屬性名也是能夠修改的,須要修改下面四個屬性。

FILES_URLS_FIELD = 'field_name_for_your_files_urls'
FILES_RESULT_FIELD = 'field_name_for_your_processed_files'
IMAGES_URLS_FIELD = 'field_name_for_your_images_urls'
IMAGES_RESULT_FIELD = 'field_name_for_your_processed_images'

管道配置

要啓用文件管道和圖片管道,一樣須要激活,固然若是同時激活這兩個管道也是可行的。

ITEM_PIPELINES = {'scrapy.pipelines.images.ImagesPipeline': 1}
# 或者
ITEM_PIPELINES = {'scrapy.pipelines.files.FilesPipeline': 1}

文件和圖片保存位置須要分別指定。

FILES_STORE = '/path/to/valid/dir'
IMAGES_STORE = '/path/to/valid/dir'

文件和圖片管道能夠避免下載最近的文件,對應的文件過時時間也能夠配置,單位是天。

# 120 days of delay for files expiration
FILES_EXPIRES = 120

# 30 days of delay for images expiration
IMAGES_EXPIRES = 30

圖片管道能夠在保存圖片的時候同時生成縮略圖,縮略圖配置是一個字典,鍵是縮略圖的名字,值是縮略圖長和寬。

IMAGES_THUMBS = {
    'small': (50, 50),
    'big': (270, 270),
}

最後圖片會保存成下面這樣,圖片的文件名是圖片路徑的SHA1哈希值。

/圖片保存路徑/full/完整圖片.jpg
/圖片保存路徑/thumbs/small/小圖片.jpg
/圖片保存路徑/thumbs/big/中圖片.jpg

若是不想使用SHA1哈希值做爲文件名,能夠繼承ImagesPipeline基類並重寫file_path函數,這裏是另一位簡書做者的爬蟲項目,他重寫了這個函數。咱們能夠做爲參考。

若是要過濾小圖片,啓用下面的配置。默認狀況下對圖片尺寸沒有約束,因此全部圖片都會下載。

IMAGES_MIN_HEIGHT = 110
IMAGES_MIN_WIDTH = 110

默認狀況下文件和圖片管道不支持重定向,遇到須要重定向的連接意味着下載失敗,不過咱們也能夠啓用重定向。

MEDIA_ALLOW_REDIRECTS = True

下載器中間件

下載器中間件能夠在scrapy引擎和爬蟲之間操縱請求和響應對象。要啓用下載器中間件,啓用下面的配置。這是一個字典,字典的鍵是要啓用的中間件,值會用來比較中間件之間的順序。

DOWNLOADER_MIDDLEWARES = {
    'myproject.middlewares.CustomDownloaderMiddleware': 543,
}

若是但願禁用某些內置的中間件,能夠將值設置爲None

DOWNLOADER_MIDDLEWARES = {
    'myproject.middlewares.CustomDownloaderMiddleware': 543,
    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
}

編寫本身的下載器中間件

自定義下載器中間件應該繼承scrapy.downloadermiddlewares.DownloaderMiddleware類,該類有以下幾個方法,用於操縱請求和響應,咱們只要重寫這幾個方法便可。這幾個方法的做用請參考官方文檔,它們比較複雜,因此我就不說了。

  • process_request(request, spider)
  • process_response(request, response, spider)
  • process_exception(request, exception, spider)

內置的下載器中間件

scrapy內置了14個下載器中間件,我簡單介紹一下其中的幾個。詳情參考文檔。

CookiesMiddleware

用於在爬蟲發起請求和獲取響應的時候保持Cookie。

DefaultHeadersMiddleware

用於設置請求的默認請求頭。

該配置位於DEFAULT_REQUEST_HEADERS,默認值以下。

{
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'en',
}

HttpProxyMiddleware

設置使用的網絡代理。

UserAgentMiddleware

設置使用的用戶代理。

爬蟲中間件

與下載器中間件相似,啓用爬蟲中間件須要一個字典來配置。

SPIDER_MIDDLEWARES = {
    'myproject.middlewares.CustomSpiderMiddleware': 543,
}

想要關閉某個中間件的時候傳遞None值。

SPIDER_MIDDLEWARES = {
    'myproject.middlewares.CustomSpiderMiddleware': 543,
    'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': None,
}

自定義爬蟲中間件

編寫本身的爬蟲中間件須要繼承scrapy.spidermiddlewares.SpiderMiddleware基類,並重寫如下幾個方法。

  • process_spider_input(response, spider)
  • process_spider_output(response, result, spider)
  • process_spider_exception(response, exception, spider)
  • process_start_requests(start_requests, spider)

內置的爬蟲中間件

scrapy內置了5個爬蟲中間件,這裏我僅介紹一兩個。

DepthMiddleware

該中間件記錄了爬蟲爬取請求地址的深度。

咱們可使用DEPTH_LIMIT來指定爬蟲爬取的深度。

UrlLengthMiddleware

該中間件會過濾掉超出最大容許長度的URL,爬蟲不會訪問這些超長URL。

最大長度經過URLLENGTH_LIMIT配置來指定,默認值是2083。

URLLENGTH_LIMIT = 2083

內建服務

scrapy內置了幾個服務,可讓咱們使用scrapy更加方便。

日誌

爬蟲類定義了log函數,咱們能夠方便的在爬蟲類中記錄日誌。

import scrapy

class MySpider(scrapy.Spider):

    name = 'myspider'
    start_urls = ['https://scrapinghub.com']

    def parse(self, response):
        self.logger.info('Parse function called on %s', response.url)

日誌相關的配置,點擊能夠跳轉到官方文檔查看詳細信息。

發送電子郵件

有時候咱們可能但願爬到必定數量的數據就發送電子郵件進行提醒。scrapy也內置了這個功能。咱們能夠經過構造函數參數來建立郵件發送器。

from scrapy.mail import MailSender
mailer = MailSender(這裏是構造函數參數)

也能夠從配置文件實例化。

mailer = MailSender.from_settings(settings)

而後調用send方法就能夠發送郵件了。

mailer.send(to=["someone@example.com"], subject="Some subject", body="Some body", cc=["another@example.com"])

電子郵件相關配置參考官方文檔

web服務

這個功能原本是寫在官方文檔內建服務條目下的,可是實際上這個功能已經變成了一個單獨的項目,須要額外安裝。

pip install scrapy-jsonrpc

而後在擴展中包含這個功能。

EXTENSIONS = {
    'scrapy_jsonrpc.webservice.WebService': 500,
}

還須要在配置中啓用該功能。

JSONRPC_ENABLED = True

而後在爬蟲運行的時候訪問http://localhost:6080/crawler便可查看爬蟲運行狀況了。

該項目的其餘配置查看其官方文檔

優化爬蟲

爬蟲項目能夠經過修改一些配置進行優化。

增大併發數

併發數能夠經過下面的配置進行設置。具體的併發數須要根據服務器的CPU等設置來進行更改。通常來講服務器CPU使用在80%-90%之間利用率比較高。咱們能夠從併發數100開始反覆進行測試。

CONCURRENT_REQUESTS = 100

增大線程池

scrapy經過一個線程池來進行DNS查詢,增大這個線程池通常也能夠提升scrapy性能。

REACTOR_THREADPOOL_MAXSIZE = 20

下降日誌級別

默認狀況下scrapy使用debug級別來打印日誌,經過下降日誌級別,咱們能夠減小日誌打印,從而提升程序運行速度。

LOG_LEVEL = 'INFO'

禁用Cookie

若是不是必須的,咱們能夠經過禁用Cookie來提升性能。若是須要登陸用戶才能爬取數據,不要禁用Cookie。

COOKIES_ENABLED = False

關閉重試

頻繁重試可能致使目標服務器響應緩慢,咱們本身訪問不了別人也訪問不了。因此能夠考慮關閉重試。

RETRY_ENABLED = False

減小下載超時

若是網絡鏈接比較快的話,咱們能夠減小下載超時,讓爬蟲卡住的請求中跳出來,通常能夠提升爬蟲效率。

DOWNLOAD_TIMEOUT = 15

關閉重定向

若是不是必要的話,咱們能夠關閉重定向來提升爬蟲性能。

REDIRECT_ENABLED = False

自動調整爬蟲負載

scrapy有一個擴展能夠自動調節服務器負載,它經過一個算法來肯定最佳的爬蟲延時等設置。它的文檔在這裏

相關配置以下,點擊連接能夠跳轉到對應文檔。

部署爬蟲

官方文檔介紹了兩種部署爬蟲的方式,能夠將爬蟲部署到服務器上遠程執行。第一種是經過Scrapyd開源項目來部署,也是這裏要介紹的方式。第二種是經過scrapy公司提供的商業收費版服務Scrapy Cloud部署,推薦有財力的公司考慮。

服務器端

首先服務器須要安裝scrapyd包,若是是Linux系統還能夠考慮使用對應的包管理器來安裝。

pip install scrapyd
apt-get install scrapyd

而後運行scrapyd服務,若是使用系統包管理器安裝,那麼可能已經配置好了systemd文件。

scrapyd
# 或者
systemctl enable scrapyd

scrapyd附帶了一個簡單的web界面能夠幫助咱們查看爬蟲運行狀況,默認狀況下訪問http://localhost:6800/來查看這個界面。

scrapyd的配置文件能夠是~/.scrapyd.conf或者/etc/scrapyd/scrapyd.conf。下面是一個簡單配置,綁定全部端口,這樣一來從任意位置均可以訪問web界面。

[scrapyd]
bind_address = 0.0.0.0

scrapyd的功能能夠查看其API文檔

客戶端

客戶端若是要上傳爬蟲,能夠經過服務器API的端點addversion.json來實現,或者安裝一個簡便工具scrapyd-client

首先安裝客戶端工具。

pip install scrapyd-client

這個客戶端目前好像有bug,在windows下運行scrapy-deploy命令不會直接執行,而是彈出一個文件關聯對話框。若是你遇到這種狀況,能夠找到Python安裝路徑下的腳本路徑(例如C:\Program Files\Python36\Scripts),而後編寫一個scrapyd-deploy.bat批處理文件,內容以下。這樣就能夠正常運行了。

@"c:\program files\python36\python.exe" "c:\program files\python36\Scripts\scrapyd-deploy" %*

而後切換到項目路徑,編輯項目全局配置文件scrapy.cfg,添加部署路徑。

[deploy]
url = http://192.168.64.136:6800/
project = quotesbot

而後直接運行scrapy-deploy命令,就能夠看到項目已經成功部署到服務器上了。

部署成功截圖

運行爬蟲須要使用scrapyd的API,例如使用curl,能夠用下面的命令。

curl http://192.168.64.136:6800/schedule.json -d project=quotesbot -d spider=toscrape-css

或者使用Jetbrains 系列IDE 2017.3的基於編輯器的HTTP客戶端。
基於編輯器的HTTP客戶端

而後點擊Jobs就能夠看到爬蟲已經開始運行了。若是要查看狀態,點擊右邊的log便可。
運行的爬蟲

以上就是scrapy的進階介紹了,利用這些功能,咱們能夠編寫更加實用的爬蟲,並將它們部署到服務器上。

相關文章
相關標籤/搜索