Python自動化開發學習-Scrapy

講師博客:https://www.cnblogs.com/wupeiqi/p/6229292.html
中文資料(有示例參考):http://www.scrapyd.cn/doc/

項目準備

Scrapy是一個爲了爬取網站數據,提取結構性數據而編寫的應用框架。使用之前有一個類似django的創建項目以及目錄結構的過程。

Scrapy 安裝

使用pip安裝(windows會有問題):

pip3 install scrapy

裝不上主要是因爲依賴的模塊Twisted安裝不上,所以得先安裝Twisted,並且不能用pip直接下載安裝。先去下載Twisted的whl安裝文件:https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
然後使用pip本地安裝:

pip install E:\Downloads\Twisted-18.9.0-cp36-cp36m-win_amd64.whl
pip install -i https://mirrors.aliyun.com/pypi/simple/ scrapy
pip install -i https://mirrors.aliyun.com/pypi/simple/ pywin32

Scrapy 組件

Scrapy主要包括了以下組件:

  • 引擎(Scrapy):
    用來處理整個系統的數據流處理, 觸發事務(框架核心)
  • 調度器(Scheduler):
    用來接受引擎發過來的請求, 壓入隊列中, 並在引擎再次請求的時候返回. 可以想像成一個URL(抓取網頁的網址或者說是鏈接)的優先隊列, 由它來決定下一個要抓取的網址是什麼, 同時去除重複的網址
  • 下載器(Downloader):
    用於下載網頁內容, 並將網頁內容返回給蜘蛛(Scrapy下載器是建立在twisted這個高效的異步模型上的)
  • 爬蟲(Spiders):
    爬蟲是主要幹活的, 用於從特定的網頁中提取自己需要的信息, 即所謂的實體(Item)。用戶也可以從中提取出鏈接,讓Scrapy繼續抓取下一個頁面
  • 項目管道(Pipeline):
    負責處理爬蟲從網頁中抽取的實體,主要的功能是持久化實體、驗證實體的有效性、清除不需要的信息。當頁面被爬蟲解析後,將被髮送到項目管道,並經過幾個特定的次序處理數據。
  • 下載器中間件(Downloader Middlewares):
    位於Scrapy引擎和下載器之間的框架,主要是處理Scrapy引擎與下載器之間的請求及響應。
  • 爬蟲中間件(Spider Middlewares):
    介於Scrapy引擎和爬蟲之間的框架,主要工作是處理蜘蛛的響應輸入和請求輸出。
  • 調度中間件(Scheduler Middewares):
    介於Scrapy引擎和調度之間的中間件,從Scrapy引擎發送到調度的請求和響應。

Python自動化開發學習-Scrapy

工作流程:
綠線是數據流向,引擎是整個程序的入口。首先從初始 URL 開始(這步大概是引擎把初始URL加到調度器),Scheduler 會將其交給 Downloader 進行下載,下載之後會交給 Spider 進行分析,Spider 分析出來的結果有兩種:一種是需要進一步抓取的鏈接,例如“下一頁”的鏈接,這些東西會被傳回 Scheduler ;另一種是需要保存的數據,它們則被送到 Item Pipeline 那裏,那是對數據進行後期處理(詳細分析、過濾、存儲等)的地方。
另外,引擎和其他3個組件直接有通道。在數據流動的通道里還可以安裝各種中間件,進行必要的處理。

Scrapy 項目結構

啓動項目
打開終端進入想要存儲 Scrapy 項目的目錄,然後運行 scrapy startproject (project name)。創建一個項目:

> scrapy startproject PeppaScrapy

執行完成後,會生成如下的文件結構:

ProjectName/
├── ProjectName
│   ├── __init__.py
│   ├── items.py
│   ├── middlewares.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders
│       └── __init__.py
└── scrapy.cfg

文件說明

  • scrapy.cfg : 項目的主配置信息。(真正爬蟲相關的配置信息在settings.py文件中)
  • items.py : 設置數據存儲模板,用於結構化數據,如:Django的Model
  • pipelines : 數據處理行爲,如:一般結構化的數據持久化
  • settings.py : 配置文件,如:遞歸的層數、併發數,延遲下載等
  • spiders : 爬蟲目錄,如:創建文件,編寫爬蟲規則

關於配置文件,需要的時候可以先去下面的地址查,版本不是最新的,不過是中文。
https://www.jianshu.com/p/df9c0d1e9087

創建爬蟲應用
先切換到項目目錄,在執行grnspider命令 scrapy genspider [-t template] (name) (domain) 。比如:

> cd PeppaScrapy
> scrapy genspider spider_lab lab.scrapyd.cn

效果就是在spiders目錄下,創建了一個spider_lab.py的文件。這裏沒有用-t參數指定模板,就是用默認模板創建的。其實不用命令也行了,自己建空文件,然後自己寫也是一樣的。
可以使用-l參數,查看有哪些模板:

> scrapy genspider -l
Available templates:
  basic
  crawl
  csvfeed
  xmlfeed

然後再用-d參數,加上上面查到的模板名,查看模板的內容:

> scrapy genspider -d basic
# -*- coding: utf-8 -*-
import scrapy

class $classname(scrapy.Spider):
    name = '$name'
    allowed_domains = ['$domain']
    start_urls = ['http://$domain/']

    def parse(self, response):
        pass

爬取頁面

把之前的創建的應用的文件修改一下,簡單完善一下parse方法:

import scrapy

class SpiderLabSpider(scrapy.Spider):
    name = 'spider_lab'
    allowed_domains = ['lab.scrapyd.cn']
    start_urls = ['http://lab.scrapyd.cn/']

    def parse(self, response):
        print(response.url)
        print(response.body.decode())

查看應用列表:

> scrapy list
spider_lab

運行單獨爬蟲應用,這裏加上了--nolog參數,避免打印日誌的干擾:

> scrapy crawl spider_lab --nolog

在python裏啓動爬蟲

每次都去命令行打一遍命令也很麻煩,也是可以直接寫python代碼,執行python來啓動的。把下面的代碼加到引用文件的最後:

if __name__ == '__main__':
    from scrapy import cmdline
    log_level = '--nolog'
    name = SpiderLabSpider.name
    cmdline.execute(('scrapy crawl %s %s' % (name, log_level)).split())

其實就是提供了在python裏調用命令行執行命令的方法。之後,還可以寫一個main.py放到項目根目錄下,寫上啓動整個項目的命令。

Windows 編碼問題

有可能會遇到編碼問題,不過我的windows沒問題,如果遇到了,試一下下面的方法:

import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030')

踩坑(爬蟲Robots協議)

Robots協議就是每個網站對於來到的爬蟲所提出的要求。並非強制要求遵守的協議,只是一種建議。
默認scrapy遵守robot協議。我在爬 http://dig.chouti.com/ 的時候遇到了這個問題。把 --nolog 參數去掉,查看錯誤日誌,有如下的信息:

[scrapy.core.engine] DEBUG: Crawled (200) <GET http://dig.chouti.com/robots.txt> (referer: None)
[scrapy.downloadermiddlewares.robotstxt] DEBUG: Forbidden by robots.txt: <GET http://dig.chouti.com/>

先去下載robots.txt文件,然後根據文件的建議,就禁止繼續爬取了。可以直接瀏覽器輸入連接查看文件內容:

User-agent: *
Allow: /link/
Disallow: /?
Disallow: /*?
Disallow: /user
Disallow: /link/*/comments
Disallow: /admin/login

# Sitemap files
Sitemap: https://dig.chouti.com/sitemap.xml

你要守規矩的的話,就只能爬 https://dig.chouti.com/link/xxxxxxxx 這樣的url,一個帖子一個帖子爬下來。
如果可以選擇不遵守協議,那麼就在爬的時候把這個設置設爲False。全局的設置在settings.py文件裏:

# Obey robots.txt rules
ROBOTSTXT_OBEY = True

也可以只對一個應用修改設置:

import scrapy

class SpiderLabSpider(scrapy.Spider):
    name = 'chouti'
    allowed_domains = ['chouti.com']
    start_urls = ['http://dig.chouti.com/']
    custom_settings = {'ROBOTSTXT_OBEY': False}

    def parse(self, response):
        print(response.url)
        print(response.encoding)
        print(response.text)

if __name__ == '__main__':
    from scrapy import cmdline
    log_level = '--nolog'
    name = SpiderLabSpider.name
    cmdline.execute(('scrapy crawl %s %s' % (name, log_level)).split())

自定義請求頭

上面踩坑的過程中,一度以爲是請求頭有問題,已定義請求頭的方法也是設置settings.py文件,裏面有一個剩下的默認配置:

# Override the default request headers:
#DEFAULT_REQUEST_HEADERS = {
#   'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
#   'Accept-Language': 'en',
#}

默認都註釋掉了,你可以在這裏爲全局加上自定義的請求頭,當然也可以只爲單獨的應用配置:

import scrapy

class SpiderLabSpider(scrapy.Spider):
    name = 'test'
    allowed_domains = ['chouti.cn']
    start_urls = ['http://dig.chouti.com/']
    # 這個網站會屏蔽User-Agent裏包含python的請求
    custom_settings = {'ROBOTSTXT_OBEY': False,
                       'DEFAULT_REQUEST_HEADERS': {'User-Agent': 'python'},
                       }

    def parse(self, response):
        print(response.request.headers)  # 這個是請求頭
        print(response.headers)  # 這個是響應頭

if __name__ == '__main__':
    from scrapy import cmdline
    log_level = ''
    name = SpiderLabSpider.name
    cmdline.execute(('scrapy crawl %s %s' % (name, log_level)).split())

xpath 選擇器

使用xpaht選擇器可以提取數據,另外還有CSS選擇器也可以用。
XPath 是一門在 XML 文檔中查找信息的語言。XPath 可用來在 XML 文檔中對元素和屬性進行遍歷。對 XPath 的理解是很多高級 XML 應用的基礎。

scrapy 裏的 xpath

解析頁面內容會用到Selector這個類,下面只貼出parse回調函數裏的代碼:

from scrapy.selector import Selector

    def parse(self, response):
        title1 = response.xpath('//title')
        print('title1', title1)
        title2 = Selector(response).xpath('//title')
        print('title2', title2)

上面的兩種用法是一樣的,通過response對象也可以直接調用xpath方法。這裏說明了xpath方法是Selector這個類提供的。另外用方法二還有一個好處,就是因爲之後需要調用Selector類裏的方法,這樣顯示的聲明Selector類之後,編輯器可以找到類似的方法,給出各種提示。直接用response調用,就沒有這種便利了。
另外還有一個XmlXPathSelector類,作用和Selector類差不多,可能是就版本使用的類。

表達式

常用的表達式:

  • node_name : 選取從節點的所有子節點。就是標籤名,比如上面的title
  • // : 匹配當前節點下的所有節點,不考慮位置。就是選擇下面的子子孫孫
  • / : 匹配當前節點下的子節點,只往下找一層,就是找兒子。類似文件路徑
  • . : 選擇當前節點。類似文件路徑
  • .. : 選擇當前節點的父節點。類似文件路徑
  • @ : 選取屬性

提取屬性
提取屬性的話,也是先定位到標籤的範圍,然後最後@屬性名稱,拿到所有對應的屬性。另外@*可以拿到所有的屬性。要當某個標籤下的屬性,就在標籤名之後/@就好了:

Selector(response).xpath('//@href')  # 提取所有的href屬性
Selector(response).xpath('//ol[@class="page-navigator"]//@href')  # ol.page-navigator下的所有的href屬性
Selector(response).xpath('//head/meta/@*').extract()  # head>meta 標籤了所有的屬性
Selector(response).xpath('//*[@id="body"]/div/@class')  # id爲body的標籤的下一級標籤裏的class屬性

查找標籤,限定屬性
使用這樣的表達式:標籤[@屬性名='屬性值'] ,另外還能用not(),注意要用小括號把取反的內容包起來:

Selector(response).xpath('//div[@id="body"]//span[@class="text"]')  # 只要 span.text 的span標籤
Selector(response).xpath('//div[@id="body"]//span[not(@class="text")]')  # 沒有text這個class的span標籤
Selector(response).xpath('//meta[@name]')  # 有name屬性的meta
Selector(response).xpath('//meta[not(@name)]')  # 沒有name屬性meta

提取值
xpath方法返回的是個對象,這個對象還可以無限次的再調用xpath方法。拿到最終的對象之後,我們需要獲取值,這裏有 extract() 和 extract_first() 這兩個方法。因爲查找的結果可能是多個值,extract方法返回列表,而extract_first方法直接返回值,但是是列表是第一個元素的值。

提取文字
表達式:/text() 可以把文字提取出來:

    def parse(self, response):
        tags = Selector(response).xpath('//ul[@class="tags-list"]//a/text()').extract()
        print(tags)  # 這樣打印效果不是很好
        for tag in tags:
            print(tag.strip())

還有個方法,可以提取整段文字拼到一起。表達式:string() :

Selector(response).xpath('string(//ul[@class="tags-list"]//a)').extract()  # 這樣沒拿全
Selector(response).xpath('string(//ul[@class="tags-list"])').extract()  # 這樣纔拿全了

上面第一次沒拿全,某個a標籤下的文字就是一段。string()表達式看來值接收一個值,如果傳的是個列表,可能就只操作第一個元素。
在我們商品詳情、小說內容的時候可能會比較好用。

匹配class的問題
xpath中沒有提供對class的原生查找方法。因爲class裏是可以包含多個值的。比如下面的這個標籤:

<div class="test main">Test</div>

下面的表達式是無法匹配到的:

response.xpath('//div[@class="test"]')

要匹配到,你得寫死:

response.xpath('//div[@class="test main"]')

但是這樣顯然是不能接受的,如果還有其他test但是沒出main的標籤就匹配不上了。
contains 函數 (XPath),檢查第一個參數字符串是否包含第二個參數字符串。用這個函數就能做好了

response.xpath('//div[contains(@class, "test")]')

這樣又有新問題了,如果有別的class名字比如:test1、mytest,這種也都會被上面的方法匹配上。
concat 函數 (XPath),返回參數的串聯。就是字符串拼接,contains的兩個參數的兩邊都加上空格,就能解決上面的問題。之所以要引入concat函數時因爲,後面的字符串可以手動在兩邊加上空格,但是@class是變量,這個也不能用加號,就要用這個函數做拼接:

response.xpath('//div[contains(concat(" ", @class, " "), " test ")]')

normalize-space 函數 (XPath),返回去掉了前導、尾隨和重複的空白的參數字符串。上面已經沒問題了。不過還不夠完美。在拼接@class之前,先把兩邊可能會出現的其他空白字符給去掉,可能會有某些操作需要改變一下class,但是又不要對這個class有任何實際的影響。總之這個是最終的解決方案:

response.xpath('//div[contains(concat(" ", normalize-space(@class), " "), " test ")]')

這裏已經引出了好幾個函數了,還有更多別的函數,需要的時候再查吧。

正則匹配
xpath也是可以用正則匹配的,用法很簡單 re:test(x, y) 。第一個參數用@屬性比較多,否則就是正則匹配標籤了,就和純的正則匹配似乎沒什麼差別了。

Selector(response=response).xpath('//a[re:test(@id, "i\d+")]')

xpath 與 css定位方式的比較

https://www.cnblogs.com/tina-cherish/p/7127812.html
xpath很強大,但是不支持原生的class,不過上面已經給了比較嚴謹的解決方案了。
css有部分功能無法實現。比如不能向上找,只能匹配當前層級,要通過判斷子元素來確定當前元素是否匹配就不行。這種情況使用xpath的話,中括號裏可以在嵌套中括號的。
不過css感覺更直觀,也已經沒什麼學習成本了。

實戰

登錄抽屜並點贊。邊一步一步實現,邊補充用到的知識點。

獲取首頁的內容

import scrapy
from scrapy.selector import Selector

class SpiderLabSpider(scrapy.Spider):
    name = 'chouti'
    allowed_domains = ['chouti.com']
    start_urls = ['http://dig.chouti.com/']
    custom_settings = {'ROBOTSTXT_OBEY': False}

    def parse(self, response):
        items = Selector(response).xpath('//*[@id="content-list"]/div[@class="item"]')
        for item in items:
            news = item.xpath(
                './div[@class="news-content"]'
                '//a[contains(concat(" ", normalize-space(@class), " "), " show-content ")]'
                '/text()'
            ).extract()[-1]
            print(news.strip())

if __name__ == '__main__':
    from scrapy import cmdline
    log_level = '--nolog'
    name = SpiderLabSpider.name
    cmdline.execute(('scrapy crawl %s %s' % (name, log_level)).split())

這裏爬取的只是首頁的內容

爬取所有頁的內容

現在要獲取所有分頁的url,然後繼續爬取。下面就是在parse回調函數後面增加了一點代碼是做好了。不過現在的代碼還不完善,會無休止的爬取下去,先不要運行,之後還要再改:

import urllib.parse

    def parse(self, response):
        items = Selector(response).xpath('//*[@id="content-list"]/div[@class="item"]')
        for item in items:
            news = item.xpath(
                './div[@class="news-content"]'
                '//a[contains(concat(" ", normalize-space(@class), " "), " show-content ")]'
                '/text()'
            ).extract()[-1]
            print(news.strip())
        # 不找下一頁,而是找全部的頁,這樣會有去重的問題,就是要這個效果
        pages = Selector(response).xpath('//div[@id="dig_lcpage"]//a/@href').extract()
        print(pages)
        url_parse = urllib.parse.urlparse(response.url)
        for page in pages:
            url = "%s://%s%s" % (url_parse.scheme, url_parse.hostname, page)
            yield scrapy.Request(url=url)

這裏做的事情就是當從前也分析了分頁的信息,把分頁信息生成新的url,然後再給調度器繼續爬取。
這裏用的 scrapy.Request() ,實際上是應該要通過 from scrapy.http import Request 導入再用的。不過這裏並不需要導入,並且只能能在scrapy下調用。因爲在 scrapy/__init__.py 裏有導入這個模塊了。並且這裏已經不是系統第一次調用這個類了,程序啓動的時候,其實就是跑了下面的代碼把 start_urls 的地址開始爬取網頁了:

for url in self.start_urls:
    yield Request(url, dont_filter=True)

這段代碼就是在當前類的父類 scrapy.Spider 裏的 start_requests 方法裏面。

爬取深度

爬取深度,允許抓取任何網站的最大深度。如果爲零,則不施加限制。
這個是可以在配置文件裏設置的。默認的配置裏沒有寫這條,並且默認值是0,就是爬取深度沒有限制。所以就會永不停止的爬取下去。實際上不會無休止,似乎默認就有去重的功能,爬過的頁面不會重複爬取。所以不設置爬取深度,就能把所有的頁面都爬下來了
這裏要講的是爬取深度的設置,所以和其他設置一樣,可以全局的在settings.py裏設置。也可以現在類的公用屬性 custom_settings 這個字典裏:

    custom_settings = {
        'ROBOTSTXT_OBEY': False,
        'DEPTH_LIMIT': 1,
    }

這個深度可以在返回的response參數裏找到,在meta這個字典裏:response.meta['depth']

去重規則

默認有下面2條配置:

DUPEFILTER_CLASS = 'scrapy.dupefilters.RFPDupeFilter'
DUPEFILTER_DEBUG = False

去重的功能默認就是在 'scrapy.dupefilters.RFPDupeFilter' 這個類裏做的。這個類有個父類 BaseDupeFilter 幫我們定義好了接口,我們可以寫一個自己的類自定義去重規則,繼承 BaseDupeFilter 實現裏面的方法:

from scrapy.dupefilters import BaseDupeFilter

class MyFilter(BaseDupeFilter):

    def __init__(self):
        # 去重可以用上集合
        # 在request_seen方法裏判斷這個set,操作這個set
        self.visited_url = set()

    @classmethod
    def from_settings(cls, settings):
        """初始化時調用的方法
        返回一個實例,作用就是可以調用配置的信息生成實例
        實例化時使用:obj = MyFilter.from_settings()
        所以不要這樣實例化:obj = MyFilter()
        什麼都不寫,上面兩重方法生成的實例是一樣的
        """
        return cls()

    def request_seen(self, request):
        """過濾規則
        檢測當前請求是否需要過濾(去重)
        返回True表示需要過濾,返回False表示不用過濾
        """
        return False

    def open(self):  # can return deferred
        """開始爬蟲時,調用一次
        比如要記錄到文件的,在這裏檢查和重建記錄文件
        """
        pass

    def close(self, reason):  # can return a deferred
        """結束爬蟲時,調用一次
        這裏可以把之前的記錄文件close掉
        """
        pass

    def log(self, request, spider):  # log that a request has been filtered
        """日誌消息的記錄或打印可以寫在這裏"""
        pass

現在知道了,默認就是有去重規則的。所以上面爬取所有頁面的代碼並並不會無休止的執行下去,而是可以把所有頁面都爬完的。

啓動和回調函數

程序啓動後,首先會調用父類 scrapy.Spider 裏的 start_requests 方法。我們也可以不設置 start_urls 屬性,然後自己重構 start_requests 方法。啓動的效果是一樣的:

    # start_urls = ['http://lab.scrapyd.cn/']

    def start_requests(self):
        urls = ['http://lab.scrapyd.cn/']
        for url in urls:
            yield scrapy.Request(url=url, dont_filter=True)

另外就是這個 scrapy.Request 類,回調函數 parse 方法最後也是調用這個方法類。這裏還有一個重要的參數 callback 。默認不設置時 callback=parse ,所以可以手動設置callback參數,使用別的回調函數。或者準備多個回調函數,每次調度的時候設置不同額callback。比如第一次用默認的,之後在 parse 方法裏再調用的時候,設置 callback=func 使用另外的回調函數。

Cookie

默認就是開啓Cookie的,所以其實我們並不需要操作什麼。
配置的 COOKIES_ENABLED 選項一旦關閉,則不會有Cookie了,別處再怎麼設置也沒用。
可以用meta參數,爲請求單獨設置cookie:

yield scrapy.Request(url, self.login, meta={'cookiejar': True})

不過如果要爲請求單獨設置的話,就得爲每個請求都顯示的聲明。否則不寫,就是認爲是不要cookie。meta可以有如下設置:

meta={'cookiejar': True}  # 使用Cookie
meta={'cookiejar': False}  # 不使用Cookie,也就寫在第一個請求裏。之後的請求不設置就是不使用Cookie
meta={'cookiejar': response.meta['cookiejar']}  # 使用上一次的cookie,上一次必須是True或者這個,否則會有問題

手動設置cookie值
Request 實例化的時候有 cookies 參數,直接傳字典進去就可以了。

獲取cookie的值
並沒有cookie這個專門的屬性。本質上cookie就是headers裏的一個鍵值對,用下面的方法去headers裏獲取:

response.request.headers.getlist('Cookie')  # 請求的Cookie
response.headers.getlist('Set-Cookie')  # 響應的Cookie

登錄抽屜並點贊

最後就是綜合應用了。登錄需要Cookies的操作。不過其實什麼都不做就可以了,默認方法就能把Cookies操作好。
然後就是從打開頁面、完成登錄、到最後點贊,需要發多次的請求,然後每次請求返回後所需要做的操作也是不一樣的,這裏就需要準備多個回調函數,並且再發起請求的時候指定回調函數。代碼如下:

import scrapy
from scrapy.selector import Selector
from utils.base64p import b64decode_str  # 自己寫的從文件讀密碼的方法,不是重點

class SpiderLabSpider(scrapy.Spider):
    name = 'chouti_favor'
    custom_settings = {
        'ROBOTSTXT_OBEY': False,
    }

    def start_requests(self):
        url = 'http://dig.chouti.com/'
        yield scrapy.Request(url, self.login)

    def login(self, response):
        # 避免把密碼公開出來,去文件裏拿,並且做了轉碼,這不是這裏的重點
        with open('../../utils/password') as f:
            auth = f.read()
            auth = auth.split('\n')
        post_dict = {
            'phone': '86%s' % auth[0],  # 從請求正文裏發現,會在手機號前加上86
            'password': b64decode_str(auth[1]),  # 直接填明文的用戶名和密碼也行的
        }
        yield scrapy.FormRequest(
            url='http://dig.chouti.com/login',
            formdata=post_dict,
            callback=self.check_login,
        )

    def check_login(self, response):
        print(response.request.headers.getlist('Cookie'))
        print(response.headers.getlist('Set-Cookie'))
        print(response.text)
        yield scrapy.Request(
            url='http://dig.chouti.com/',
            dont_filter=True,  # 這頁之前爬過了,如果不關掉過濾,就不會再爬了
        )

    def parse(self, response):
        items = Selector(response).xpath('//*[@id="content-list"]/div[@class="item"]')
        do_favor = True
        for item in items:
            news = item.xpath(
                './div[@class="news-content"]'
                '//a[contains(concat(" ", normalize-space(@class), " "), " show-content ")]'
                '/text()'
            ).extract()[-1]
            print(news.strip())
            # 點贊,做個判斷,只贊第一條
            if do_favor:
                do_favor = False
                linkid = item.xpath('./div[@class="news-content"]/div[@share-linkid]/@share-linkid').extract_first()
                yield scrapy.Request(
                    url='https://dig.chouti.com/link/vote?linksId=%s' % linkid,
                    method='POST',
                    callback=self.favor,
                )

    def favor(self, response):
        print("點贊", response.text)

if __name__ == '__main__':
    from scrapy import cmdline
    log_level = '--nolog'
    name = SpiderLabSpider.name
    cmdline.execute(('scrapy crawl %s %s' % (name, log_level)).split())

注意:首頁的地址 http://dig.chouti.com 一共訪問了兩次。第二次如果不把 dont_filter 設爲True,關閉過濾,就不會再去爬了。當然也可以第一次爬完之後,就保存在變量裏,等登錄後再從這個返回開始之後的處理。
上面的POST請求,用到了 FormRequest 這個類。這個類繼承的是 Request 。裏面主要就是把字典拼接成請求體,設置一下請求頭的 Content-Type ,默認再幫我們把 method 設爲 POST 。也是可以繼續用 Request 的,就是把上面的3個步驟自己做了。主要是請求體,大概是按下面這樣拼接一下傳給body參數:

body='phone=86151xxxxxxxx&password=123456&oneMonth=1',

格式化處理

之前只是簡單的處理,所以在parse方法中直接處理。對於想要獲取更多的數據處理,則可以利用Scrapy的items將數據格式化,然後統一交由pipelines來處理。
回顧一下 Scrapy 組件和工作流程,項目管道(Pipeline) 組件負責這個工作。

items

先要編輯一下 items.py 裏的類,默認會幫我們生成一個類,並有簡單的註釋。必須要處理2個數據 title 和 href ,則改寫 items.py 如下:

import scrapy

class PeppascrapyItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()
    href = scrapy.Field()

然後去修改之前的 parse 方法,導入上面的類,把要處理的數據傳遞進去生成實例,然後 yield :

from PeppaScrapy.items import PeppascrapyItem

class SpiderLabSpider(scrapy.Spider):
    name = 'spider_lab'
    allowed_domains = ['lab.scrapyd.cn']
    start_urls = ['http://lab.scrapyd.cn/']

    def parse(self, response):
        print(response.url)
        items = Selector(response).xpath(
            '//div[@id="body"]//div[@id="main"]/div[@class="quote post"]')
        for item in items:
            title = item.xpath('./span[@class="text"]/text()').extract_first()
            href = item.xpath('./span/a/@href').extract_first()
            yield PeppascrapyItem(title=title, href=href)

上面這段代碼,只需要注意最後3行。把要保存的數據用items.py裏的類實例化後,yield返回。
回顧下流程,之前yield返回給 scrapy.Request ,就是把數據返回給調度器繼續繼續爬取
這裏yield返回給 scrapy.Item ,就是 Item Pipeline 裏的 Item 進入數據的處理。
在 Item 裏只是把數據傳遞出來,數據的處理則在 Pipeline 裏。
如果有多處數據要返回,則可以自定義多個 scrapy.Item 類,來做數據的格式化處理。

Pipline

還有一個 pipelines.py 文件,默認裏面只有一個 return ,但是傳入2個參數 item 和 spider,先打印看看:

class PeppascrapyPipeline(object):
    def process_item(self, item, spider):
        print(item)
        print(spider)
        return item

只是編寫處理方法還不夠,這個方法需要註冊。在settings.py文件裏,默認寫好了註冊的方法,只需要把註釋去掉。ITEM_PIPELINES 的 key 就是要註冊的方法,而 value 則是優先級。理論上字典沒有順序,優先級小的方法先執行:

ITEM_PIPELINES = {
   'PeppaScrapy.pipelines.PeppascrapyPipeline': 300,
}

最後返回的item是個字典,我們報錯的變量名是key,值就是value。而spider則是這個爬蟲 scrapy.Spider 對象。
執行多個操作
這裏一個類就是執行一個操作,如果對返回的數據要有多次操作,也可以多定義幾個類,然後註冊上即可。
每次操作的item,就是上一次操作最後 return item 傳遞下來的。第一次操作的item則是從 scrapy.Item 傳過來的。所以也可以對item進行處理,然後之後的操作就是在上一次操作對item的修改之上進行的。所以也可以想return什麼就return什麼,就是給下一個操作處理的數據。

DropItem

接着講上面的執行多個操作。如果在某個地方要終止之後所有的操作,則可以用 DropItem 。用法如下:

from scrapy.exceptions import DropItem

class PeppascrapyPipeline(object):
    def process_item(self, item, spider):
        print(item)
        raise DropItem()

這樣對這組數據的操作就終止了。一般應該把這句放在某個條件的分支裏。

初始化操作

Pipeline 這個類裏,還可以定義更多方法。處理上面的處理方法,還有另外3個方法,其中一個是類方法。所有的方法名都不能修改,具體如下:

class PeppascrapyPipeline(object):

    def __init__(self, value):
        self.value = value

    def process_item(self, item, spider):
        """操作並進行持久化"""
        print(item)
        # 表示將item丟棄,不會被後續pipeline處理
        raise DropItem()
        # print(spider)
        # return item 給後續的pipeline繼續處理
        # return item

    @classmethod
    def from_crawler(cls, crawler):
        """初始化時候,用於創建pipeline對象"""
        val = crawler.settings.get('BOT_NAME')
        # getint 方法可以直接獲取 int 參數
        # val = crawler.settings.getint('DEPTH_LIMIT')
        return cls(val)

    def open_spider(self,spider):
        """爬蟲開始執行時,調用"""
        print('START')

    def close_spider(self,spider):
        """爬蟲關閉時,被調用"""
        print('OVER')

類方法 from_crawler 是用於創建pipeline對象的。主要是接收了crawler參數,可以獲取到settings裏的參數然後傳給構造方法。比如這裏獲取了settings.py裏的值傳給了對象。
另外2個方法 open_spider 和 close_spider ,是在爬蟲開始和關閉時執行的。即使爬蟲有多次返回,處理方法要調用多次,但是這2個方法都只會調用一次。這2個方法是在爬蟲 scrapy.Spider 開始和關閉的時候各執行一次的。而不是第一次返回數據處理和最後一次數據處理完畢。
打開文件的操作
以寫入文件爲例,寫入一段數據需要3步:打開文件,寫入,關閉文件。如果把這3不都寫在 process_item 方法裏,則會有多次的打開和關閉操作。正確的做法是,打開文件在 open_spider 方法裏執行,寫入還是在 process_item 方法裏每次返回都可以寫入,最後在 close_spider 方法裏關閉文件。

中間件

默認有一個 middlewares.py 文件,裏面默認創建了2個類,分別是爬蟲中間件和下載中間件

爬蟲中間件

class PeppascrapySpiderMiddleware(object):
    # Not all methods need to be defined. If a method is not defined,
    # scrapy acts as if the spider middleware does not modify the
    # passed objects.

    @classmethod
    def from_crawler(cls, crawler):
        # This method is used by Scrapy to create your spiders.
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    def process_spider_input(self, response, spider):
        """下載完成,執行,然後交給parse處理"""
        # Called for each response that goes through the spider
        # middleware and into the spider.

        # Should return None or raise an exception.
        return None

    def process_spider_output(self, response, result, spider):
        """spider處理完成,返回時調用
        返回Request或者Item(字典也行,Item本身也是個字典)
        Request就是給調度器繼續處理
        Item就是給項目管道保存
        """
        # Called with the results returned from the Spider, after
        # it has processed the response.

        # Must return an iterable of Request, dict or Item objects.
        for i in result:
            yield i

    def process_spider_exception(self, response, exception, spider):
        """異常調用"""
        # Called when a spider or process_spider_input() method
        # (from other spider middleware) raises an exception.

        # Should return either None or an iterable of Response, dict
        # or Item objects.
        pass

    def process_start_requests(self, start_requests, spider):
        """爬蟲啓動時調用"""
        # Called with the start requests of the spider, and works
        # similarly to the process_spider_output() method, except
        # that it doesn’t have a response associated.

        # Must return only requests (not items).
        for r in start_requests:
            yield r

    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)

爬蟲中間件這裏要注意下 process_spider_output() 返回的內容之後是要交給調度器繼續爬取的,或者是交給項目管道做保存操作。所以返回的可以是 Request 或者是 Item 。

下載器中間件

class PeppascrapyDownloaderMiddleware(object):
    # Not all methods need to be defined. If a method is not defined,
    # scrapy acts as if the downloader middleware does not modify the
    # passed objects.

    @classmethod
    def from_crawler(cls, crawler):
        # This method is used by Scrapy to create your spiders.
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    def process_request(self, request, spider):
        """請求需要被下載時,經過所有下載器中間件的process_request調用"""
        # Called for each request that goes through the downloader
        # middleware.

        # Must either:
        # - return None: continue processing this request
        # - or return a Response object
        # - or return a Request object
        # - or raise IgnoreRequest: process_exception() methods of
        #   installed downloader middleware will be called
        return None

    def process_response(self, request, response, spider):
        """spider處理完成,返回時調用"""
        # Called with the response returned from the downloader.

        # Must either;
        # - return a Response object
        # - return a Request object
        # - or raise IgnoreRequest
        return response

    def process_exception(self, request, exception, spider):
        """異常處理
        當下載處理器(download handler)
        或 process_request() (下載中間件)拋出異常時執行
        """
        # Called when a download handler or a process_request()
        # (from other downloader middleware) raises an exception.

        # Must either:
        # - return None: continue processing this exception
        # - return a Response object: stops process_exception() chain
        # - return a Request object: stops process_exception() chain
        pass

    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)

自定義操作

自定製命令

自定製命令
一、在spiders同級創建任意目錄,如:commands
二、在目錄裏創建 crawlall.py 文件,名字任意取,這個文件名將來就是執行這段代碼的命令
下面是一個啓動spiders裏所有爬蟲的代碼:

from scrapy.commands import ScrapyCommand
from scrapy.utils.project import get_project_settings

class Command(ScrapyCommand):
    requires_project = True

    def syntax(self):
        return '[options]'

    def short_desc(self):
        return 'Runs all of the spiders'

    def run(self, args, opts):
        spider_list = self.crawler_process.spiders.list()
        for name in spider_list:
            self.crawler_process.crawl(name, **opts.__dict__)
        self.crawler_process.start()

三、在 settings.py 中添加配置 COMMANDS_MODULE = '項目名稱.目錄名稱' ,比如:

COMMANDS_MODULE = "PeppaScrapy.commands"

四、執行命令: scrapy crawlall

自定義擴展

利用信號在指定位置註冊制定操作。
自定義的型號要寫在寫一類,然後在settings裏註冊。默認的配置文件裏是有EXTENSIONS的,註釋掉了,這裏就放開註釋然後改一下:

# Enable or disable extensions
# See https://doc.scrapy.org/en/latest/topics/extensions.html
EXTENSIONS = {
   # 'scrapy.extensions.telnet.TelnetConsole': None,
   'PeppaScrapy.extensions.MyExtension': 100
}

根據上面的操作,就是創建 extensions.py 文件,然後寫一個 MyExtension 的類:

# PeppaScrapy/extensions.py 文件
from scrapy import signals

class MyExtension(object):
    def __init__(self, value):
        self.value = value

    @classmethod
    def from_crawler(cls, crawler):
        val = crawler.settings.get('BOT_NAME')
        ext = cls(val)
        # 註冊你的方法和信息
        crawler.signals.connect(ext.spider_start, signal=signals.spider_opened)
        crawler.signals.connect(ext.spider_stop, signal=signals.spider_closed)
        return ext

    # 寫你要執行的方法
    def spider_start(self, spider):
        print('open')

    def spider_stop(self, spider):
        print('close')

所有的信號
上面的例子裏用到了 spider_opened 和 spider_closed 這2個信號。
在 scrapy/signals.py 裏可以查到所有的信號:

engine_started = object()
engine_stopped = object()
spider_opened = object()
spider_idle = object()
spider_closed = object()
spider_error = object()
request_scheduled = object()
request_dropped = object()
response_received = object()
response_downloaded = object()
item_scraped = object()
item_dropped = object()

配置文件詳細

# -*- coding: utf-8 -*-

# Scrapy settings for step8_king project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
#     http://doc.scrapy.org/en/latest/topics/settings.html
#     http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
#     http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html

# 1. 爬蟲名稱
BOT_NAME = 'step8_king'

# 2. 爬蟲應用路徑
SPIDER_MODULES = ['step8_king.spiders']
NEWSPIDER_MODULE = 'step8_king.spiders'

# Crawl responsibly by identifying yourself (and your website) on the user-agent
# 3. 客戶端 user-agent請求頭
# USER_AGENT = 'step8_king (+http://www.yourdomain.com)'

# Obey robots.txt rules
# 4. 禁止爬蟲配置
# ROBOTSTXT_OBEY = False

# Configure maximum concurrent requests performed by Scrapy (default: 16)
# 5. 併發請求數
# CONCURRENT_REQUESTS = 4

# Configure a delay for requests for the same website (default: 0)
# See http://scrapy.readthedocs.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
# 6. 延遲下載秒數
# DOWNLOAD_DELAY = 2

# The download delay setting will honor only one of:
# 7. 單域名訪問併發數,並且延遲下次秒數也應用在每個域名
# CONCURRENT_REQUESTS_PER_DOMAIN = 2
# 單IP訪問併發數,如果有值則忽略:CONCURRENT_REQUESTS_PER_DOMAIN,並且延遲下次秒數也應用在每個IP
# CONCURRENT_REQUESTS_PER_IP = 3

# Disable cookies (enabled by default)
# 8. 是否支持cookie,cookiejar進行操作cookie
# COOKIES_ENABLED = True
# COOKIES_DEBUG = True

# Disable Telnet Console (enabled by default)
# 9. Telnet用於查看當前爬蟲的信息,操作爬蟲等...
#    使用telnet ip port ,然後通過命令操作
# TELNETCONSOLE_ENABLED = True
# TELNETCONSOLE_HOST = '127.0.0.1'
# TELNETCONSOLE_PORT = [6023,]

# 10. 默認請求頭
# Override the default request headers:
# DEFAULT_REQUEST_HEADERS = {
#     'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
#     'Accept-Language': 'en',
# }

# Configure item pipelines
# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
# 11. 定義pipeline處理請求
# ITEM_PIPELINES = {
#    'step8_king.pipelines.JsonPipeline': 700,
#    'step8_king.pipelines.FilePipeline': 500,
# }

# 12. 自定義擴展,基於信號進行調用
# Enable or disable extensions
# See http://scrapy.readthedocs.org/en/latest/topics/extensions.html
# EXTENSIONS = {
#     # 'step8_king.extensions.MyExtension': 500,
# }

# 13. 爬蟲允許的最大深度,可以通過meta查看當前深度;0表示無深度
# DEPTH_LIMIT = 3

# 14. 爬取時,0表示深度優先Lifo(默認);1表示廣度優先FiFo

# 後進先出,深度優先
# DEPTH_PRIORITY = 0
# SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleLifoDiskQueue'
# SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.LifoMemoryQueue'
# 先進先出,廣度優先

# DEPTH_PRIORITY = 1
# SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleFifoDiskQueue'
# SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.FifoMemoryQueue'

# 15. 調度器隊列
# SCHEDULER = 'scrapy.core.scheduler.Scheduler'
# from scrapy.core.scheduler import Scheduler

# 16. 訪問URL去重
# DUPEFILTER_CLASS = 'step8_king.duplication.RepeatUrl'

# Enable and configure the AutoThrottle extension (disabled by default)
# See http://doc.scrapy.org/en/latest/topics/autothrottle.html

"""
17. 自動限速算法
    from scrapy.contrib.throttle import AutoThrottle
    自動限速設置
    1. 獲取最小延遲 DOWNLOAD_DELAY
    2. 獲取最大延遲 AUTOTHROTTLE_MAX_DELAY
    3. 設置初始下載延遲 AUTOTHROTTLE_START_DELAY
    4. 當請求下載完成後,獲取其"連接"時間 latency,即:請求連接到接受到響應頭之間的時間
    5. 用於計算的... AUTOTHROTTLE_TARGET_CONCURRENCY
    target_delay = latency / self.target_concurrency
    new_delay = (slot.delay + target_delay) / 2.0 # 表示上一次的延遲時間
    new_delay = max(target_delay, new_delay)
    new_delay = min(max(self.mindelay, new_delay), self.maxdelay)
    slot.delay = new_delay
"""

# 開始自動限速
# AUTOTHROTTLE_ENABLED = True
# The initial download delay
# 初始下載延遲
# AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
# 最大下載延遲
# AUTOTHROTTLE_MAX_DELAY = 10
# The average number of requests Scrapy should be sending in parallel to each remote server
# 平均每秒併發數
# AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0

# Enable showing throttling stats for every response received:
# 是否顯示
# AUTOTHROTTLE_DEBUG = True

# Enable and configure HTTP caching (disabled by default)
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings

"""
18. 啓用緩存
    目的用於將已經發送的請求或相應緩存下來,以便以後使用

    from scrapy.downloadermiddlewares.httpcache import HttpCacheMiddleware
    from scrapy.extensions.httpcache import DummyPolicy
    from scrapy.extensions.httpcache import FilesystemCacheStorage
"""
# 是否啓用緩存策略
# HTTPCACHE_ENABLED = True

# 緩存策略:所有請求均緩存,下次在請求直接訪問原來的緩存即可
# HTTPCACHE_POLICY = "scrapy.extensions.httpcache.DummyPolicy"
# 緩存策略:根據Http響應頭:Cache-Control、Last-Modified 等進行緩存的策略
# HTTPCACHE_POLICY = "scrapy.extensions.httpcache.RFC2616Policy"

# 緩存超時時間
# HTTPCACHE_EXPIRATION_SECS = 0

# 緩存保存路徑
# HTTPCACHE_DIR = 'httpcache'

# 緩存忽略的Http狀態碼
# HTTPCACHE_IGNORE_HTTP_CODES = []

# 緩存存儲的插件
# HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'

"""
19. 代理,需要在環境變量中設置
    from scrapy.contrib.downloadermiddleware.httpproxy import HttpProxyMiddleware

    方式一:使用默認
        os.environ
        {
            http_proxy:http://root:[email protected]:9999/
            https_proxy:http://192.168.11.11:9999/
        }
    方式二:使用自定義下載中間件

    def to_bytes(text, encoding=None, errors='strict'):
        if isinstance(text, bytes):
            return text
        if not isinstance(text, six.string_types):
            raise TypeError('to_bytes must receive a unicode, str or bytes '
                            'object, got %s' % type(text).__name__)
        if encoding is None:
            encoding = 'utf-8'
        return text.encode(encoding, errors)

    class ProxyMiddleware(object):
        def process_request(self, request, spider):
            PROXIES = [
                {'ip_port': '111.11.228.75:80', 'user_pass': ''},
                {'ip_port': '120.198.243.22:80', 'user_pass': ''},
                {'ip_port': '111.8.60.9:8123', 'user_pass': ''},
                {'ip_port': '101.71.27.120:80', 'user_pass': ''},
                {'ip_port': '122.96.59.104:80', 'user_pass': ''},
                {'ip_port': '122.224.249.122:8088', 'user_pass': ''},
            ]
            proxy = random.choice(PROXIES)
            if proxy['user_pass'] is not None:
                request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port'])
                encoded_user_pass = base64.encodestring(to_bytes(proxy['user_pass']))
                request.headers['Proxy-Authorization'] = to_bytes('Basic ' + encoded_user_pass)
                print "**************ProxyMiddleware have pass************" + proxy['ip_port']
            else:
                print "**************ProxyMiddleware no pass************" + proxy['ip_port']
                request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port'])

    DOWNLOADER_MIDDLEWARES = {
       'step8_king.middlewares.ProxyMiddleware': 500,
    }

"""

"""
20. Https訪問
    Https訪問時有兩種情況:
    1. 要爬取網站使用的可信任證書(默認支持)
        DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory"
        DOWNLOADER_CLIENTCONTEXTFACTORY = "scrapy.core.downloader.contextfactory.ScrapyClientContextFactory"

    2. 要爬取網站使用的自定義證書
        DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory"
        DOWNLOADER_CLIENTCONTEXTFACTORY = "step8_king.https.MySSLFactory"

        # https.py
        from scrapy.core.downloader.contextfactory import ScrapyClientContextFactory
        from twisted.internet.ssl import (optionsForClientTLS, CertificateOptions, PrivateCertificate)

        class MySSLFactory(ScrapyClientContextFactory):
            def getCertificateOptions(self):
                from OpenSSL import crypto
                v1 = crypto.load_privatekey(crypto.FILETYPE_PEM, open('/Users/wupeiqi/client.key.unsecure', mode='r').read())
                v2 = crypto.load_certificate(crypto.FILETYPE_PEM, open('/Users/wupeiqi/client.pem', mode='r').read())
                return CertificateOptions(
                    privateKey=v1,  # pKey對象
                    certificate=v2,  # X509對象
                    verify=False,
                    method=getattr(self, 'method', getattr(self, '_ssl_method', None))
                )
    其他:
        相關類
            scrapy.core.downloader.handlers.http.HttpDownloadHandler
            scrapy.core.downloader.webclient.ScrapyHTTPClientFactory
            scrapy.core.downloader.contextfactory.ScrapyClientContextFactory
        相關配置
            DOWNLOADER_HTTPCLIENTFACTORY
            DOWNLOADER_CLIENTCONTEXTFACTORY

"""

"""
21. 爬蟲中間件
    class SpiderMiddleware(object):

        def process_spider_input(self,response, spider):
            '''
            下載完成,執行,然後交給parse處理
            :param response: 
            :param spider: 
            :return: 
            '''
            pass

        def process_spider_output(self,response, result, spider):
            '''
            spider處理完成,返回時調用
            :param response:
            :param result:
            :param spider:
            :return: 必須返回包含 Request 或 Item 對象的可迭代對象(iterable)
            '''
            return result

        def process_spider_exception(self,response, exception, spider):
            '''
            異常調用
            :param response:
            :param exception:
            :param spider:
            :return: None,繼續交給後續中間件處理異常;含 Response 或 Item 的可迭代對象(iterable),交給調度器或pipeline
            '''
            return None

        def process_start_requests(self,start_requests, spider):
            '''
            爬蟲啓動時調用
            :param start_requests:
            :param spider:
            :return: 包含 Request 對象的可迭代對象
            '''
            return start_requests

    內置爬蟲中間件:
        'scrapy.contrib.spidermiddleware.httperror.HttpErrorMiddleware': 50,
        'scrapy.contrib.spidermiddleware.offsite.OffsiteMiddleware': 500,
        'scrapy.contrib.spidermiddleware.referer.RefererMiddleware': 700,
        'scrapy.contrib.spidermiddleware.urllength.UrlLengthMiddleware': 800,
        'scrapy.contrib.spidermiddleware.depth.DepthMiddleware': 900,

"""
# from scrapy.contrib.spidermiddleware.referer import RefererMiddleware
# Enable or disable spider middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
SPIDER_MIDDLEWARES = {
    # 'step8_king.middlewares.SpiderMiddleware': 543,
}

"""
22. 下載中間件
    class DownMiddleware1(object):
        def process_request(self, request, spider):
            '''
            請求需要被下載時,經過所有下載器中間件的process_request調用
            :param request:
            :param spider:
            :return:
                None,繼續後續中間件去下載;
                Response對象,停止process_request的執行,開始執行process_response
                Request對象,停止中間件的執行,將Request重新調度器
                raise IgnoreRequest異常,停止process_request的執行,開始執行process_exception
            '''
            pass

        def process_response(self, request, response, spider):
            '''
            spider處理完成,返回時調用
            :param response:
            :param result:
            :param spider:
            :return:
                Response 對象:轉交給其他中間件process_response
                Request 對象:停止中間件,request會被重新調度下載
                raise IgnoreRequest 異常:調用Request.errback
            '''
            print('response1')
            return response

        def process_exception(self, request, exception, spider):
            '''
            當下載處理器(download handler)或 process_request() (下載中間件)拋出異常
            :param response:
            :param exception:
            :param spider:
            :return:
                None:繼續交給後續中間件處理異常;
                Response對象:停止後續process_exception方法
                Request對象:停止中間件,request將會被重新調用下載
            '''
            return None

    默認下載中間件
    {
        'scrapy.contrib.downloadermiddleware.robotstxt.RobotsTxtMiddleware': 100,
        'scrapy.contrib.downloadermiddleware.httpauth.HttpAuthMiddleware': 300,
        'scrapy.contrib.downloadermiddleware.downloadtimeout.DownloadTimeoutMiddleware': 350,
        'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': 400,
        'scrapy.contrib.downloadermiddleware.retry.RetryMiddleware': 500,
        'scrapy.contrib.downloadermiddleware.defaultheaders.DefaultHeadersMiddleware': 550,
        'scrapy.contrib.downloadermiddleware.redirect.MetaRefreshMiddleware': 580,
        'scrapy.contrib.downloadermiddleware.httpcompression.HttpCompressionMiddleware': 590,
        'scrapy.contrib.downloadermiddleware.redirect.RedirectMiddleware': 600,
        'scrapy.contrib.downloadermiddleware.cookies.CookiesMiddleware': 700,
        'scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware': 750,
        'scrapy.contrib.downloadermiddleware.chunked.ChunkedTransferMiddleware': 830,
        'scrapy.contrib.downloadermiddleware.stats.DownloaderStats': 850,
        'scrapy.contrib.downloadermiddleware.httpcache.HttpCacheMiddleware': 900,
    }

"""
# from scrapy.contrib.downloadermiddleware.httpauth import HttpAuthMiddleware
# Enable or disable downloader middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
# DOWNLOADER_MIDDLEWARES = {
#    'step8_king.middlewares.DownMiddleware1': 100,
#    'step8_king.middlewares.DownMiddleware2': 500,
# }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章