爬蟲之scrapy框架

1、scrapy框架介紹

一、介紹

Scrapy,Python開發的一個快速、高層次的屏幕抓取和web抓取框架,用於抓取web站點並從頁面中提取結構化的數據。Scrapy用途普遍,能夠用於數據挖掘、監測和自動化測試。css

Scrapy吸引人的地方在於它是一個框架,任何人均可以根據需求方便的修改。它也提供了多種類型爬蟲的基類,如BaseSpider、sitemap爬蟲等,最新版本又提供了web2.0爬蟲的支持。html

Scrap,是碎片的意思,這個Python的爬蟲框架叫Scrapy。python

Scrapy 是基於twisted框架開發而來,twisted是一個流行的事件驅動的python網絡框架。所以Scrapy使用了一種非阻塞(又名異步)的代碼來實現併發。git

 

二、scrapy架構圖

1. 這是官方給出的架構圖github

 

2. 各個組件的做用web

引擎(Engine)
引擎負責控制數據流在系統中全部組件中流動,並在相應動做發生時觸發事件。ajax


調度器(Scheduler)
用來接收引擎發過來的請求, 壓入隊列中, 並在引擎再次請求的時候返回. 至關於一個URL的優先級隊列, 由它來決定下一個要抓取的網址是什麼, 同時去除重複的網址redis


下載器(Downloader)
用於下載網頁內容, 並將網頁內容返回給EGINE,下載器是創建在twisted這個高效的異步模型上的mongodb


下載器中間件(Downloader middlewares)
下載器中間件是在引擎及下載器之間的特定鉤子(specific hook),處理Downloader傳遞給引擎的response(也包括引擎傳遞給下載器的Request)。 其提供了一個簡便的機制,經過插入自定義代碼來擴展Scrapy功能。shell


爬蟲(Spiders)
Spider是Scrapy用戶編寫用於分析response並提取item(即獲取到的item)或額外跟進的URL的類。 每一個spider負責處理一個特定(或一些)網站


爬蟲中間件(Spider middlewares)
Spider中間件是在引擎及Spider之間的特定鉤子(specific hook),處理spider的輸入(response)和輸出(items及requests)。 其提供了一個簡便的機制,經過插入自定義代碼來擴展Scrapy功能。


項目管道(Item Pipeline)
Item Pipeline負責處理被spider提取出來的item。典型的處理有清理、 驗證及持久化(例如存取到數據庫中)。

 

三、scrapy框架的基本流程

0. 程序開始運行

1. Spiders用yeild將url發送給Engine(引擎)

2. Engine把url發送給調度器(Scheduler)

3. 調度器(Scheduler)會將url生成request返回給Engine(引擎)

4. Engine(引擎)拿到request,經過下載器中間件(Downloader middlewares)進行層層過濾發送給下載器(Downloader)

5. 下載器(Downloader)在網上獲取到response數據以後,又通過下載器中間件(Downloader middlewares)進行層層過濾發送給Engine(引擎)

6. Engine(引擎)獲取到response數據以後,返回給Spiders,Spiders的parse()方法對獲取到的response數據進行處理,解析出items或者requests

7. 將解析出來的items或者requests發送給Engine(引擎)

8. Engine(引擎)獲取到items或者requests,將items發送給Item Pipeline進行數據的存儲

9. 注意,只有當調度器中不存在任何request了,整個程序纔會中止,也就是說,對於下載失敗的URL,Scrapy也會從新下載

 

四、scrapy框架主要的幾個方法

1. process_request(request, spider)
當每一個request經過下載中間件時,該方法被調用,這裏能夠修改UA,代理,Refferer

2. process_response(request, response, spider)
這裏能夠看返回是不是200加入重試機制

3. process_exception(request, exception, spider)
這裏能夠處理超時

4. SpiderMiddleware主要處理解析Item的相關邏輯修正,好比數據不完整要添加默認,增長其餘額外信息等
process_spider_input(response, spider)
當response經過spider中間件時,該方法被調用,處理該response。


rocess_spider_output(response, result, spider)
當Spider處理response返回result時,該方法被調用。

process_spider_exception(response, exception, spider)
當spider或(其餘spider中間件的) process_spider_input()
拋出異常時, 該方法被調用。

 

2、scrapy的基礎

一、安裝

1. Linux系統
    pip3 install scrapy  # 從官網下載
    pip3 install -i https://pypi.douban.com/simple/ scrapy  # 從豆瓣源下載


2. Windows
    1. pip install -i https://pypi.douban.com/simple/ wheel

    2. 下載twisted: http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted

    3. 進入下載目錄,執行命令:pip3 install Twisted-18.9.0-cp36-cp36m-win_amd64.whl

    4. pip3 install -i https://pypi.douban.com/simple/ pywin32

    5. pip3 install -i https://pypi.douban.com/simple/ scrapy

 

二、cmd下的命令行工具

1. 查看幫助
    scrapy -h
    scrapy <command> -h


2. 有兩種命令:其中Project-only必須切到項目文件夾下才能執行,而Global的命令則不須要
    Global commands:
        startproject  # 建立項目
        genspider     # 建立爬蟲程序
        settings      # 若是是在項目目錄下,則獲得的是該項目的配置
        runspider     # 運行一個獨立的python文件,沒必要建立項目
        shell         # scrapy shell url地址  在交互式調試,如選擇器規則正確與否
        fetch         # 獨立於程單純地爬取一個頁面,能夠拿到請求頭
        view          # 下載完畢後直接彈出瀏覽器,以此能夠分辨出哪些數據是ajax請求
        version       # scrapy version 查看scrapy的版本,scrapy version -v查看scrapy依賴庫的版本
    
    Project-only commands:
        crawl         # 運行爬蟲,必須建立項目才行,確保配置文件中ROBOTSTXT_OBEY = False
        check         # 檢測項目中有無語法錯誤
        list          # 列出項目中所包含的爬蟲名
        edit          # 編輯器,通常不用
        parse         # scrapy parse url地址 --callback 回調函數  #以此能夠驗證咱們的回調函數是否正確
        bench         # scrapy bentch壓力測試

3. 官網連接
    https://docs.scrapy.org/en/latest/topics/commands.html

 

三、目錄結構

project_name/
   scrapy.cfg
   project_name/
       __init__.py
       items.py
       middlewares.py
       pipelines.py
       settings.py
       spiders/
           __init__.py
           爬蟲1.py
           爬蟲2.py
           爬蟲3.py

文件說明:
    scrapy.cfg     項目的主配置信息,用來部署scrapy時使用,爬蟲相關的配置信息在settings.py文件中。
    items.py       設置數據存儲模板,用於結構化數據,如:Django的Model
    middlewares.py 中間件是處於引擎(crawler.engine)和下載器(crawler.engine.download())之間的一層組件
    pipelines      數據處理行爲,如:通常結構化的數據持久化
    settings.py    配置文件,如:遞歸的層數、併發數,延遲下載等。強調:配置文件的選項必須大寫不然視爲無效
    spiders        爬蟲目錄,如:建立文件,編寫爬蟲規則

注意:
1、通常建立爬蟲文件時,以網站域名命名
2、默認只能在終端執行命令,爲了更便捷操做:
# 在項目根目錄下新建:entrypoint.py
from scrapy.cmdline import execute
execute(['scrapy', 'crawl', 'qsbk', '--nolog'])

 

如何在pycharm中運行scrapy程序

1、通常建立爬蟲文件時,以網站域名命名
2、默認只能在終端執行命令,爲了更便捷操做:
# 在項目根目錄下新建:entrypoint.py
from scrapy.cmdline import execute
execute(['scrapy', 'crawl', 'qsbk', '--nolog'])

 

四、Spider主爬蟲程序類

Spiders是定義如何抓取某個站點(或一組站點)的類,包括如何執行爬行(即跟隨連接)以及如何從其頁面中提取結構化數據(即抓取項目)。換句話說,Spiders是爲特定站點(或者在某些狀況下,一組站點)爬網和解析頁面定義自定義行爲的地方。 

1. 生成初始的Requests來爬取第一個URLS,而且標識一個回調函數
    第一個請求定義在start_requests()方法內默認從start_urls列表中得到url地址來生成Request請求,
    默認的回調函數是parse方法。回調函數在下載完成返回response時自動觸發

2. 在回調函數中,解析response而且返回值
    返回值能夠4種:
        包含解析數據的字典
        Item對象
        新的Request對象(新的Requests也須要指定一個回調函數)
        或者是可迭代對象(包含Items或Request)

3. 在回調函數中解析頁面內容
   一般使用Scrapy自帶的Selectors,但很明顯你也可使用Beutifulsoup,lxml或其餘你愛用啥用啥。

4. 最後,針對返回的Items對象將會被持久化到數據庫
   經過Item Pipeline組件存到數據庫:https://docs.scrapy.org/en/latest/topics/item-pipeline.html#topics-item-pipeline)
   或者導出到不一樣的文件(經過Feed exports:https://docs.scrapy.org/en/latest/topics/feed-exports.html#topics-feed-exports)

 

五、Scrapy自帶的選擇器Selectors(xpath)

使用Scrapy自帶的選擇器,獲取到的數據都是Selectors對象,
須要使用extract方法把Selectors對象的內容提取出來。

1,div.xpath('.//div[@class="author clearfix"]/a/h2/text()')
結果是Selector對象組成的列表:[<Selector xpath='xxx' data='xxx'>,]

2,div.xpath('.//div[@class="author clearfix"]/a/h2/text()').extract()
把Selector對象的內容提取出來,仍是列表:['data的內容',]

3,div.xpath('.//div[@class="author clearfix"]/a/h2/text()').extract()[0]
把提取出來的列表按索引取出某一個值,若只提取一個,也可使用extract_first()
div.xpath('.//div[@class="author clearfix"]/a/h2/text()').extract_first()

4,div.xpath('.//div[@class="author clearfix"]/a/h2/text()')[0].extract()
先在列表中取出某個Selector對象,在把Selector對象的內容提取出來

 

六、DupeFilter(去重)

1. 默認使用方式
DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter'
Request(...,dont_filter=False)  # 若是dont_filter=True則告訴Scrapy這個URL不參與去重。


2. 自定義去重規則
from scrapy.dupefilter import RFPDupeFilter,看源碼,仿照BaseDupeFilter
 
# 步驟一:在項目目錄下自定義去重文件dup.py
class UrlFilter(object):
    def __init__(self):
        self.visited = set()  # 或者放到數據庫
 
    @classmethod
    def from_settings(cls, settings):
        return cls()
 
    def request_seen(self, request):
        if request.url in self.visited:
            return True
        self.visited.add(request.url)
 
    def open(self):  # can return deferred
        pass
 
    def close(self, reason):  # can return a deferred
        pass
 
    def log(self, request, spider):  # log that a request has been filtered
        pass

 

3、scrapy的小Demo

一、建立並執行爬蟲應用程序

1. 在cmd下輸入命令
cd 工做目錄
scrapy  startproject  項目名  # 建立項目
cd 項目名
scrapy  genspider  應用名稱  爬取的起始url # 建立爬蟲程序
scrapy crawl 應用名稱  # 該種執行形式會顯示執行的日誌信息
scrapy crawl 應用名稱 --nolog  # 該種執行形式不會顯示執行的日誌信息
scrapy crawl 應用名稱 -o xxx.xml # 存儲數據文本格式


2. 示例
# 爬取糗事百科
cd E:\SpiderProject\爬蟲\scrapy框架  # 進入工做目錄
scrapy startproject QSBK  # 建立一個名爲QSBK的項目
cd QSBK  # 進入項目目錄
scrapy genspider qsbk www.qiushibaike.com  # 建立爬蟲程序(會在項目下的spiders目錄下建立qsbk.py程序)
scrapy  crawl  qsbk  # 執行應用程序
scrapy  crawl  qsbk -o qsbk.xml  # 將爬取到的數據解析後存儲成xml格式的文件


3.spiders目錄下建立的qsbk.py程序
# -*- coding: utf-8 -*-
import scrapy


class QsbkSpider(scrapy.Spider):
    name = 'qsbk'  # 應用名稱
    # 容許爬取的域名(若是遇到非該域名的url則爬取不到數據)
    allowed_domains = ['www.qiushibaike.com']
    # 起始爬取的url
    start_urls = ['http://www.qiushibaike.com/']
    
    # 訪問起始URL並獲取結果後的回調函數,該函數的response參數就是向起始的url發送請求後,獲取的響應對象.
    # 該函數返回值必須爲可迭代對象或者NUll
    def parse(self, response):
        print(response.text)  # 獲取字符串類型的響應內容
        print(response.body)  # 獲取字節類型的響應內容


4. 項目的配置文件(settings.py)相關配置
修改內容及其結果以下:
19行:假裝請求載體身份(假裝成瀏覽器)
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36'

22行:能夠忽略或者不遵照robots協議
ROBOTSTXT_OBEY = False

注意:settings.py的配置項的名稱都要大寫


5. 爬取糗百首頁中文字段子的內容和做者
對qsbk.py進行修改
"""
注意:記得修改settings.py的配置哦!!!

類QsbkSpider是繼承了scrapy.Spider這個類的,
scrapy.Spider中的start_requests方法是咱們爬蟲主邏輯、策略,
由於start_requests方法默認只是作了一些簡單的爬取措施,
若是要爬取的網頁有了很強的反爬措施,此時就須要咱們重寫start_requests方法,破解反爬措施。
start_requests方法必須yield Request對象,所以須要導入 from scrapy import Request
"""

import scrapy
from scrapy import Request
# from scrapy.http import Request  # 跟上面導入的Request是同樣的


class QsbkSpider(scrapy.Spider):
    name = 'qsbk'  # 應用名稱
    # 容許爬取的域名(若是遇到非該域名的url則爬取不到數據)
    allowed_domains = ['www.qiushibaike.com']
    # 起始爬取的url
    start_urls = ['https://www.qiushibaike.com/text/']

    def start_requests(self):
        # 爬蟲主邏輯 策略
        yield Request(
            url="https://www.qiushibaike.com/text/",
            callback=self.parse,  # 定義回調函數,不寫默認就是 parse
        )

    def parse(self, response):
        # 用xpath或者BS或者 css selector進行數據解析
        # 這裏的response自帶xpath方法,能夠將xpath表達式直接做用於該函數中
        odiv = response.xpath('//div[@id="content-left"]/div')
        content_list = []  # 用於存儲解析到的數據
        # xpath函數返回的爲列表,列表中存放的數據爲Selector類型的數據
        # 咱們解析到的內容被封裝在了Selector對象中,須要調用extract()函數將解析的內容從Selecor中取出
        for div in odiv:
            # 1.獲取用戶名author
            author = div.xpath('.//div[@class="author clearfix"]/a/h2/text()')
            if author:
                author = author[0].extract()
            else:
                author = "匿名用戶"
            # 2.獲取這個用戶的段子的內容contents
            contents = div.xpath('.//div[@class="content"]/span/text()')  # 遇到換行br就會生成一個Selector對象

            content_list.append({
                "author": author,
                "content": "".join([selector.extract().strip() for selector in contents])
            })
        print("content_list", content_list)
        return content_list

 

二、使用BeautifulSoup解析

"""
除了解析代碼,其他代碼跟上面的例子同樣
"""

from bs4 import BeautifulSoup

def parse(self, response): print(response.text) soup = BeautifulSoup(response.text, 'lxml') odiv = soup.find_all('div', class_='article') content_list = [] # 用於存儲解析到的數據 for div in odiv: author = div.find('div', class_='author').text print(author) content = div.find('div', class_='content').text print(content) dic = { '做者': author, '內容': content } print(dic) # 將數據存儲到content_list這個列表中 content_list.append(dic) return content_list

 

4、爬取亞馬遜的iphoneXs商品

1.主爬蟲程序
"""
登陸亞馬遜官方,搜索:iphone xs max
須要解析出來的數據:商品簡介、價格、配送方
爬取前10頁

注意在settings中設置UA反爬和不遵照robots
"""

import scrapy
from scrapy import Request


class AmazonSpider(scrapy.Spider):
    name = 'amazon'
    allowed_domains = ['www.amazon.cn']
    start_urls = ['https://www.amazon.cn/']

    def start_requests(self):
        yield Request(
            url="https://www.amazon.cn/s/ref=nb_sb_noss_1?__mk_zh_CN=%E4%BA%9A%E9%A9%AC%E9%80%8A%E7%BD%91%E7%AB%99&url=search-alias%3Daps&field-keywords=iphone+xs+max",
            callback=self.parse_index,
            dont_filter=True
        )

    def parse_index(self, response):
        '''
        解析商品首頁獲取幾十個商品詳情的url,發請求
        解析函數的返回值:
           (1) 字典或者迭代數據(好比列表套字典)
           (2) 請求Request對象
           (3) item對象(經過pipeline作持久化的)
        '''

        # 商品列表頁的全部商品的URL
        detail_urls = response.xpath('//*[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract()
        for detail_url in detail_urls:
            print("detail_url", detail_url)
            yield Request(
                url=detail_url,
                callback=self.parse_detail,  # parse_detail解析詳情頁的函數
                dont_filter=True
            )

        # 解析一個下一頁的URL,通過測試發現下面xpath解析出來的url是相對路徑,所以須要咱們拼接出完整的url
        # response.urljoin方法能幫咱們把要爬取的網頁域名和須要拼接的url進行拼接
        next_url = response.urljoin(response.xpath('//*[@id="pagnNextLink"]/@href').extract_first())
        yield Request(
            url=next_url,
            callback=self.parse_index,
            dont_filter=True
        )

    def parse_detail(self, response):
        '''
        response: 某一個商品詳情頁的響應體
        '''
        title = response.xpath('//*[@id="productTitle"]/text()')[0].extract().strip()
        price = response.xpath('//*[@id="priceblock_ourprice"]/text()')[0].extract().strip()
        delivery = response.xpath('//*[@id="ddmMerchantMessage"]/*[1]/text()').extract()

        print(title)
        print(price)
        print(delivery)


2.免費代理網站
http://www.goubanjia.com/


3.設置代理池
當你的ip訪問亞馬遜太過頻繁的時候,亞馬遜會對你進行反爬(暫封你的ip),
若你發現沒法爬取亞馬遜網頁的時候,可設置代理池(下面會詳細講,如今先把網頁爬下來)

步驟:
    1. 在免費代理網站上找一個能用的代理ip
    2. 在middlewares.py中找到AmazonDownloaderMiddleware類下的process_request方法
    3. 在process_request中寫入以下幾行
        proxy = 'http://77.70.115.104:8080'
        request.meta['download_timeout'] = 20
        request.meta['proxy'] = proxy

 

5、Item

Item對象是用於收集抓取數據的簡單容器。它們提供相似字典的 API,並具備用於聲明其可用字段的方便語法。
Scrapy Items相似於Django Models,可是Scrapy Items更簡單,由於沒有不一樣字段類型的概念。

1.聲明Item
在項目中找到items.py,使用簡單的類定義語法和Field對象聲明項目

import scrapy

class AmazonItem(scrapy.Item):
    # define the fields for your item here like:
    title = scrapy.Field()
    price = scrapy.Field()
    delivery = scrapy.Field()


2.使用Item
# 在須要使用的地方(爬蟲主程序)導入這個類
from ..items import AmazonItem

# 實例化對象並添加數據
item = AmazonItem()
item["title"] = title
item["price"] = price
item["delivery"] = delivery


3. Demo
# 上面亞馬遜的parse_detail能夠這樣寫(先實例化再設置數據)
def parse_detail(self, response):
    '''
    response: 某一個商品詳情頁的響應體
    '''
    title = response.xpath('//*[@id="productTitle"]/text()')[0].extract().strip()
    price = response.xpath('//*[@id="priceblock_ourprice"]/text()')[0].extract().strip()
    delivery = response.xpath('//*[@id="ddmMerchantMessage"]/*[1]/text()').extract()

    print(title)
    print(price)
    print(delivery)

    item = AmazonItem()  # 先實例化
    item["title"] = title  # 再設置數據
    item["price"] = price
    item["delivery"] = delivery

    return item

# 還能夠這樣寫(實例化的時候初始化數據)
def parse_detail(self, response):
    '''
    response: 某一個商品詳情頁的響應體
    '''
    title = response.xpath('//*[@id="productTitle"]/text()')[0].extract().strip()
    price = response.xpath('//*[@id="priceblock_ourprice"]/text()')[0].extract().strip()
    delivery = response.xpath('//*[@id="ddmMerchantMessage"]/*[1]/text()').extract()

    print(title)
    print(price)
    print(delivery)

    item = AmazonItem(title=title, price=price, delivery=delivery)  # 實例化的時候初始化數據
    # 獲取字段值
    print(item['title'])
    print(item.get('price'))
    
    # 訪問全部鍵
    print(item.keys())
    
    # 訪問全部的鍵值對
    print(item.items())
    
    return item

 

6、Item PipeLine

在一個項目被蜘蛛抓取以後,它被髮送到項目管道,該項目管道經過順序執行的幾個組件處理它。

每一個項目管道組件(有時簡稱爲「項目管道」)是一個實現簡單方法的Python類。他們收到一個項目並對其執行操做,同時決定該項目是否應該繼續經過管道或被丟棄而且再也不處理。

項目管道的典型用途是:
cleansing HTML data(清洗數據)
validating scraped data (checking that the items contain certain fields)(校驗數據)
checking for duplicates (and dropping them)(去重)
storing the scraped item in a database(排序存儲)

PipeLine只接收Item對象,PipeLine在項目中的pipelines.py裏面定義。
定義好的PipeLine類須要在settings裏面進行聲明,只要在主程序中return item就會自動去settings中找到聲明的PipeLine類,
而後對item進行處理,存儲。

# 大概在settings的67行設置pipeline類,
ITEM_PIPELINES = {
   'Amazon.pipelines.AmazonPipeline': 300,
}

 

一、編寫本身的項目管道

每一個項管道組件都是一個如下方法的Python類:

# item進入pipeline前會調用這個方法
open_spider(self,蜘蛛)
打開蜘蛛時會調用此方法。

# 必須實現的方法,用來處理item數據的方法(核心方法)
process_item(self,項目,蜘蛛)
爲每一個項目管道組件調用此方法。process_item() 
返回帶數據的dict,返回一個Item (或任何後代類)對象,返回Twisted Deferred或引起 DropItem異常。丟棄的項目再也不由其餘管道組件處理。

# item處理完畢後調用這個方法
close_spider(self,蜘蛛)
當蜘蛛關閉時調用此方法。

# 當pipeline類實例化的時候,若是有from_crawler方法會先執行這個方法,再執行init方法
# 參數crawler表明這個爬蟲程序,能夠在此拿到爬蟲的名字、域名、配置參數等,均可以從這裏拿到
from_crawler(cls,crawler)
若是存在,則調用此類方法以從a建立管道實例Crawler。它必須返回管道的新實例。Crawler對象提供對全部Scrapy核心組件的訪問,
如設置和信號; 它是管道訪問它們並將其功能掛鉤到Scrapy的一種方式。

 

二、使用MongoDB存儲item

1. settings中的配置
# 與Mongodb數據庫相關配置
HOST = "127.0.0.1"
PORT = 27017
USER = "root"
PWD = ""
DB = "amazon"
TABLE = "goods"

# 聲明pipeline類
ITEM_PIPELINES = {
   'Amazon.pipelines.MongodbPipeline': 300,
}


2. 定義pipeline類
from pymongo import MongoClient


class MongodbPipeline(object):
    def __init__(self, host, port, user, pwd, db, table):
        self.host = host
        self.port = port
        self.user = user
        self.pwd = pwd
        self.db = db
        self.table = table

    @classmethod
    def from_crawler(cls, crawler):
        """
        Scrapy會先經過getattr判斷咱們是否自定義了from_crawler,有則調它來完成實例化
        從爬蟲程序crawler中的settings配置拿到咱們須要的數據,返回給這個類,而後調用init實例化
        """
        HOST = crawler.settings.get('HOST')
        PORT = crawler.settings.get('PORT')
        USER = crawler.settings.get('USER')
        PWD = crawler.settings.get('PWD')
        DB = crawler.settings.get('DB')
        TABLE = crawler.settings.get('TABLE')
        # 必須返回這個類的對象
        return cls(HOST, PORT, USER, PWD, DB, TABLE)

    def open_spider(self, spider):
        """
        爬蟲剛啓動時執行一次
        """
        # self.client = MongoClient('mongodb://%s:%s@%s:%s' %(self.user,self.pwd,self.host,self.port))
        self.client = MongoClient(host=self.host, port=self.port)

    def close_spider(self, spider):
        """
        爬蟲關閉時執行一次
        """
        self.client.close()

    def process_item(self, item, spider):
        # 操做並進行持久化
        d = dict(item)  # 把item轉換成字典類型
        if all(d.values()):  # 當字典不爲空的時候插入數據
            self.client[self.db][self.table].save(d)  # save方法也等於插入數據並保存
        return item

 

三、item流向多個pipeline

1. 基於上面的settings再進行配置
# 聲明pipeline類
ITEM_PIPELINES = {
   'Amazon.pipelines.MongodbPipeline': 300,
   'Amazon.pipelines.FilePipeline': 400,
}


2. 再定義一個pipeline類
class FilePipeline(object):
    def open_spider(self, spider):
        """
        爬蟲剛啓動時執行一次
        """
        print("文件寫入一個item")
        # self.client = MongoClient('mongodb://%s:%s@%s:%s' %(self.user,self.pwd,self.host,self.port))
        self.file = open("file_pipeline.txt", "w")

    def close_spider(self, spider):
        """
        爬蟲關閉時執行一次
        """
        self.file.close()

    def process_item(self, item, spider):
        # 操做並進行持久化
        d = dict(item)
        import json
        self.file.write(json.dumps(d) + "\n")

3. 多個管道的流程
ITEM_PIPELINES = {
   'Amazon.pipelines.MongodbPipeline': 300,
   'Amazon.pipelines.FilePipeline': 400,
}
300和400是權重,設置多少都行,看本身喜愛,權重小的管道先執行,
所以MongodbPipeline先執行,執行完畢後,process_item方法必須return item,
return item後會把item傳給下一個管道FilePipeline進行處理。

若是你想處理完後再也不給別人處理了,那麼能夠
from scrapy.exceptions import DropItem

def process_item(self, item, spider):
    if 某種狀況:
        raise DropItem("Duplicate item found: %s" % item)

 

7、下載中間件

scrapy的下載中間件個Django的中間件相似,
process_request、process_response等方法也是相似的

不一樣的是:
Django中process_request返回一個對象後,process_response是從process_request對應的process_response開始返回,
而scrapy的下載中間件process_request返回一個對象後,process_response是從第一個process_response開始返回,


1. settings中的配置
# 大概在settings的55行
DOWNLOADER_MIDDLEWARES = {
   'Amazon.middlewares.MyDownMiddleware': 543,
}

2. 權重
process_request權重小的先執行
process_response權重大的先執行
process_request權重小的先執行

3. 各個方法的做用
class MyDownMiddleware(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


所以咱們更換代理的時候,應該放在process_exception中
def process_exception(self, request, exception, spider):
    # 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
    proxy = 'http://77.70.115.104:8080'
    request.meta['download_timeout'] = 20
    request.meta['proxy'] = proxy
    return request  # 返回request,即把請求返回給調度器,調度器再從新去發請求

 

8、代理池

一、原理

咱們跟換代理的時候,應該是在某個代理網站上,把可用的代理所有爬下來,
當出現異常的時候就去代理池中把真正能用的ip跟換到個人程序中,
所以還須要寫一個爬代理ip網站的程序,這個不用擔憂,去github中搜就能夠,
把搜到的應用結合到咱們的爬蟲程序中

 

二、步驟

1. 把下載到的程序放到咱們爬蟲程序中
2. 讀README.md文件,根據步驟進行一些初始化配置
3. 把爬ip的程序啓動後,把爬取到的代理ip存到MongoDB中
    [DB]
    ;Configure the database information
    ;type: SSDB/MONGODB if use redis, only modify the host port,the type should be SSDB
    type = MONGODB
    host = 127.0.0.1
    port = 27017
    name = proxy

4. 若是要在爬蟲代碼中使用的話, 能夠將此api封裝成函數直接使用,例如:
import requests

def get_proxy():
    return requests.get("http://127.0.0.1:5010/get/").content

def delete_proxy(proxy):
    requests.get("http://127.0.0.1:5010/delete/?proxy={}".format(proxy))

開發的api接口在這裏設置
[API]
# API config http://127.0.0.1:5010
# The ip specified when starting the web API
ip = 0.0.0.0
# he port on which to run the web API
port = 5010

 

三、代碼實現

1. 首先確保已經把代理ip的那個網站爬下來了

2. 確保DB和API設置好了

3. 在咱們的爬蟲程序中新建一個proxy.py文件
代碼以下:
import requests

def get_proxy():
    return requests.get("http://127.0.0.1:5010/get/").content

def delete_proxy(proxy):
    requests.get("http://127.0.0.1:5010/delete/?proxy={}".format(proxy))

4. 咱們爬蟲程序的下載中間件代碼
from .proxy import get_proxy,delete_proxy

class AmazonDownloaderMiddleware(object):
    def process_exception(self, request, exception, spider):
        # 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
        proxy = "http://"+get_proxy()
        request.meta['download_timeout'] = 20
        request.meta["proxy"] = proxy
        return request
相關文章
相關標籤/搜索