scrapy學(xué)習(xí)筆記(有示例版)

scrapy學(xué)習(xí)筆記(有示例版)

我的博客

1.使用scrapy

ps:linux環(huán)境

安裝:sudo install Scrapy

1.1創(chuàng)建工程

$ scrapy startproject Spider
就創(chuàng)建屬于自己的項(xiàng)目
同時(shí)生成一個(gè)Spider目錄:

Spider
    |- scrapy.cfg                        項(xiàng)目部署文件
    |- Spider                     該項(xiàng)目的python模塊,可以在這里加入代碼
        |- __init__.py                 
        |- items.py                       主要是將爬取的非結(jié)構(gòu)性的數(shù)據(jù)源提取結(jié)構(gòu)性數(shù)據(jù)
        |- middlewares.py          
        |- pipelines.py                  將爬取的數(shù)據(jù)進(jìn)行持久化存儲(chǔ)
        |-  __pycache__  
        |-  settings.py                  配置文件
        |-  spiders                       放置spider代碼的目錄
            |-  __init__.py 
            |-  __pycache__

1.2創(chuàng)建爬蟲模塊

import scrapy
class Spider(scrapy.Spider):                       #創(chuàng)建的Spider是繼承scrapy.Spider類
    name = "Spider"                                      #爬蟲的名稱(必須是項(xiàng)目里面唯一的)
    allowed_domain = ["xxx.com"]                #允許的域名(就是這個(gè)爬蟲爬取鏈接的范圍)
    start_urls = ["http://xxx.com/xxx/xxx"]     #開始爬取的鏈接
    
    def parse(self, response):                     
        pass

在spiders文件夾創(chuàng)建出這份代碼,除了以上解釋外,里面的parse()是一個(gè)Spider方法,被調(diào)用時(shí),每個(gè)初始url響應(yīng)后返回的Response對(duì)象,將會(huì)作為唯一的參數(shù)傳遞給該方法。該方法負(fù)責(zé)解析返回的數(shù)據(jù),提取數(shù)據(jù)以及生成需要進(jìn)一步處理的url的Responst對(duì)象

1.3網(wǎng)頁解析

創(chuàng)建完爬蟲模塊后就可以進(jìn)行網(wǎng)頁解析了,Scrapy有自己的一套數(shù)據(jù)提取機(jī)制,成為選擇器(selector),它們通過特定的XPath或者CSS表達(dá)式來選擇HTML文件中的某個(gè)部分,Scrapy選擇器構(gòu)建與lxml庫上,這意味著它們?cè)谒俣群徒馕鰷?zhǔn)確性上非常相似。當(dāng)然,也可以使用自己的,比如BeautifulSoup進(jìn)行解析

Selector對(duì)象有四個(gè)基本的方法

  • xpath(query):傳入XPath表達(dá)式query,返回該表達(dá)式所對(duì)應(yīng)的所有節(jié)點(diǎn)的selector list列表
  • css(query):傳入CSS表達(dá)式query,返回該表達(dá)式所對(duì)應(yīng)的所有節(jié)點(diǎn)的selector list列表
  • extract(): 序列化該節(jié)點(diǎn)為Unicode字符串并返回list列表。
  • re(regex):根據(jù)傳入的正則表達(dá)式對(duì)數(shù)據(jù)進(jìn)行提取,返回Unicode字符串列表。regex可以是一個(gè)已編譯的正則,也可以是一個(gè)將為re.compile(regex)編譯為正則表達(dá)式的字符串。

在spider類的parse()方法中,其中一個(gè)參數(shù)是response。所以使用選擇器有兩種方法

  • 將response傳入Selector:Selector(response).xpath()
    Selector(response).xpath('//span/text()').extract()
    
  • 或者直接調(diào)用:response.xpath()
    response.xpath('//span/text()').extract()
    

Scrapy提供了一個(gè)簡便的方式來查看表達(dá)式是否正確
打開命令行窗口后輸入:
$ scrapy shell "http://xxx.com"
或者響應(yīng)后
>>>response.xpath('//span/text()').extract()
就可以抽取出響應(yīng)的數(shù)據(jù)了,返回的是Unicode格式

示例代碼中extract()是提取里面的文本,并返回一個(gè) SelectorList實(shí)例
如果只想提取第一個(gè)匹配的元素,可以調(diào)用選擇器 .extract_first(),如果獲取不到則返回None
可以通過使用extract_first(default='xxx')來自定義返回?cái)?shù)值

了解了這些后可以寫出一個(gè)parse()的代碼
假設(shè)這個(gè)網(wǎng)頁我們要提取的數(shù)據(jù)有標(biāo)題,時(shí)間,評(píng)論,鏈接,且他們?cè)诰W(wǎng)頁的xxx里面

def parse(self, response):
    papers = response.xpath(".//*[@class='xxx']") 
    for paper in papers:
        url = paper.xpath(".//*[@class='title']/a/@href").extract()[0]
        title = paper.xpath(".//*[@class='title']/a/text()").extract()[0]
        time = paper.xpath(".//*[@class='ttime']/a/text())".extract()[0]
        content = paper.xpath(".//*[@class='content']/a/text()").extract()[0]
        item = SpiderItem(url=url, title=title, time=time,title=title, content=content)
        yield item
        yield scrapy.Request(url=url, callnack=self.parse)

yield item是把所有獲得的數(shù)據(jù)封裝起來,存放在Spideritem
yield scrapy.Request(url=url, callnack=self.parse)則是把獲取到的url利用request對(duì)象構(gòu)造為請(qǐng)求再利用callback回調(diào)到自己指定的Spideritem

1.4 item

Scrapy提供Item類。 Item對(duì)象是用于收集所抓取的數(shù)據(jù)的簡單容器。它們提供了一個(gè)類似字典的 API,具有用于聲明其可用字段的方便的語法
在items.py文件輸入

class SpiderItem(scrapt.Item):
    url = scrapy.Field()
    time = scrapy.Field()
    title = scrapy.Field()
    content = scrapy.Field()

就創(chuàng)建好了,如果要拓展item就在原始item上來拓展item

class newSpiderItem(SpiderItem):
    xxx = scrapy.Field()

1.5Item Pipeline

當(dāng)item在Spider中被手機(jī)后,它將會(huì)傳遞到Item PipeLine,利用Item PipeLine來進(jìn)行數(shù)據(jù)保存
先上一個(gè)示例代碼

import json
from scrapy.exceptions import DropItem
class SpiderPipeline(object):
    def __init__(self):
        self.file = open('xxx.json','w')
    def process_item(self,item,spider):
        if item['title']:
            line = json.dumps(dict(item)) + "\n"
            self.file.write(line)
            return item
        else:
            raise DropItem("沒有找到標(biāo)題"+title)

之后在setting.py的ITEM_PIPELINES變了中進(jìn)行激活

ITEM_PIPELINES = {
       'Spider.pipelines.spiderPipeline':300}

process_item()方法可以用來判斷是來自于哪個(gè)爬蟲,item是被爬取對(duì)象,spider是爬取該item的Spider
Dropitem是一個(gè)錯(cuò)誤捕獲
300是判斷運(yùn)行順序,范圍為0~1000

除此之外,Scrapy內(nèi)置了一些簡單的存儲(chǔ)方式生成一個(gè)帶有爬取數(shù)據(jù)的輸出文件自帶的類型有:

  • JSON
  • JSON lines
  • CSV
  • XMl
  • Pickle
  • Marshal
    食用方法:將命令行切換到項(xiàng)目目錄,比如想保存為csv格式,輸入命令:
    $ scrapy crawl Spider -o papers.csv
    就可以讓Spider采集到的數(shù)據(jù)保存為papers.csv文件了

1.6運(yùn)行

$ scrapy crawl Spider

就可以運(yùn)行爬蟲了,其中Spider就是我們定義的name
更過命令這里

也可以在spider.py文件添加一下代碼運(yùn)行

if __name =='__main__':
    process = CrawlerProcess(get_project_settings())
    process.crawl('spider')
    process.start()

2.深入Scrapy

通過以上運(yùn)行一個(gè)爬蟲肯定會(huì)覺得還不如自己寫代碼來爬蟲,但Scrapy框架還有很多厲害的地方

2.1Spider

2.1.1Spider

繼承于Spider是最簡單的spider,常用屬性為:

  • name
    定義此爬蟲名稱的字符串。爬蟲名稱是爬蟲如何由Scrapy定位(和實(shí)例化),因此它必須是唯一的。但是,沒有什么能阻止你實(shí)例化同一個(gè)爬蟲的多個(gè)實(shí)例。這是最重要的爬蟲屬性,它是必需的。
  • allowed_domains
    允許此爬蟲抓取的域的字符串的可選列表,指定一個(gè)列表可以抓取。
  • start_urls
    當(dāng)沒有指定特定網(wǎng)址時(shí),爬蟲將開始抓取的網(wǎng)址列表。
  • custom_settings
    運(yùn)行此爬蟲時(shí)將從項(xiàng)目寬配置覆蓋的設(shè)置字典。詳情在這里
  • crawler
    此屬性from_crawler()在初始化類后由類方法設(shè)置,并鏈接Crawler到此爬蟲實(shí)例綁定到的對(duì)象。
    Crawlers在項(xiàng)目中封裝了很多組件,用于單個(gè)條目訪問(例如擴(kuò)展,中間件,信號(hào)管理器等)
  • settings
    運(yùn)行此爬蟲的配置。
  • logger
    用Spider創(chuàng)建的Python記錄器name。
  • start_requests()
    此方法必須返回一個(gè)可迭代對(duì)象,該方法只被調(diào)用一次。
    主要用于在啟動(dòng)階段需要post登錄某個(gè)網(wǎng)站時(shí)才使用
    示例代碼:
    import scrapy
    class Spider(scrapy.Spider):                       #創(chuàng)建的Spider是繼承scrapy.Spider類
        name = "Spider"                                      #爬蟲的名稱(必須是項(xiàng)目里面唯一的)
        allowed_domain = ["xxx.com"]                #允許的域名(就是這個(gè)爬蟲爬取鏈接的范圍)
        def start_requests(self):
            return [scrapy.FromRequest("httP://www.xxx.com/login",
                                                                 formdata={'user':'xxx','pass':'xxx'},
                                                                 callback=self.login)]
        def login(self, response):
            pass
    

2.1.2CrawlSpidel

CrawlCpider除了從Spider繼承過來的屬性外,還提供了一個(gè)新的屬性rules,rules是一個(gè)包含一個(gè)或多個(gè)Rule對(duì)象的集合,每個(gè)Rule對(duì)爬取網(wǎng)站的動(dòng)作定義了特定的規(guī)則。如果多個(gè)Rule匹配了相同的鏈接,則根據(jù)它們?cè)趓ules屬性中被定義的順序,第一個(gè)會(huì)被使用。
Rule類的原型為:

scrapy.contrib,spiders.Rule(link_extractor,callback=None,cb_kwargs=None,follow=None,process_links=None, process_request=None)

參數(shù)說明

link_extractor 是一個(gè)LinkExtractor對(duì)象,定義了如何從爬取到的頁面提取鏈接。
callback回調(diào)函數(shù)接受一個(gè)response作為一個(gè)參數(shù),應(yīng)避免使用parse作為回調(diào)函數(shù)
cb_kwargs包含傳遞給回調(diào)函數(shù)的參數(shù)的字典
follow是一個(gè)布爾值,指定了根據(jù)規(guī)則從respose提取的鏈接是否需要跟進(jìn)
process_links是一個(gè)可調(diào)用的或一個(gè)字符串(在這種情況下,將使用具有該名稱的爬蟲對(duì)象的方法),將使用指定從每個(gè)響應(yīng)提取的每個(gè)鏈接列表調(diào)用該方法link_extractor。這主要用于過濾目的。
process_request 是一個(gè)可調(diào)用的或一個(gè)字符串(在這種情況下,將使用具有該名稱的爬蟲對(duì)象的方法),它將被此規(guī)則提取的每個(gè)請(qǐng)求調(diào)用,并且必須返回一個(gè)請(qǐng)求或無(過濾出請(qǐng)求)

link_extractor里面也大有乾坤,參數(shù)有:

allow: 提取滿足正則表達(dá)式的鏈接
deny: 排除正則表達(dá)式匹配的鏈接,優(yōu)先級(jí)高于allow
allow_domains: 允許的域名,可以是str,list
deny_domains:排除的域名,同上
restrict_xpaths: 提取滿足xpath選擇條件的鏈接,可以是str,list
restrict_css: 提取滿足css選擇條件的鏈接,可以是str,list
tags: 提取指定標(biāo)記下的鏈接,默認(rèn)從a和area中提取,可以是str或list。
attrs: 提取滿足擁有屬性的鏈接,默認(rèn)為href,類型為list
unique:鏈接是否去重,類型為boolean
process_value: 值處理函數(shù)(這個(gè)函數(shù)是對(duì)已經(jīng)捕獲的函數(shù)進(jìn)行修補(bǔ))
以上詳細(xì)在這里
關(guān)于process_value可以看這兩個(gè)函數(shù)示例(來自于stackoverflow):

import scrapy
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractors import LinkExtractor

class dmozItem(scrapy.Item):
    basic_url = scrapy.Field()

class dmozSpider(CrawlSpider):
    name = "dmoz"
    allowed_domains = ["scrapy.org"]
    start_urls = [
        "http://scrapy.org/",
    ]

    def clean_url(value):
        return value.replace('/../', '/')

    rules = (
        Rule(
            LinkExtractor(
                allow=('\S+/'),
                restrict_xpaths=(['.//ul[@class="navigation"]/a[1]',
                                  './/ul[@class="navigation"]/a[2]',
                                  './/ul[@class="navigation"]/a[3]',
                                  './/ul[@class="navigation"]/a[4]',
                                  './/ul[@class="navigation"]/a[5]']),
                process_value=clean_url
            ),
            callback='first_level'),
    )

    def first_level(self, response):
        taco = dmozItem()
        taco['basic_url'] = response.url
        return taco
def process_value(value):
    unique_id = re.search(r"/item/(\d+)", value).group(1)
    if unique_id in already_crawled_site_ids:
        return None
    return value

rules = [Rule(SgmlLinkExtractor(allow=['/item/\d+']), 'parse_item', process_value=process_value)]

2.1.3 XMLFeedSpider

XMLFeedSpider設(shè)計(jì)用于通過以特定節(jié)點(diǎn)名稱迭代XML訂閱源來解析XML訂閱源。迭代器可以選自:iternodes,xml和html。iternodes為了性能原因,建議使用迭代器,因?yàn)閤ml和迭代器html一次生成整個(gè)DOM為了解析它。但是,html當(dāng)使用壞標(biāo)記解析XML時(shí),使用作為迭代器可能很有用。

要設(shè)置迭代器和標(biāo)記名稱,必須定義以下類屬性:

  • iterator
    定義要使用的迭代器的字符串。它可以是:

    • 'iternodes' - 基于正則表達(dá)式的快速迭代器
    • 'html'- 使用的迭代器Selector。請(qǐng)記住,這使用DOM解析,并且必須加載所有DOM在內(nèi)存中,這可能是一個(gè)大飼料的問題
    • 'xml'- 使用的迭代器Selector。請(qǐng)記住,這使用DOM解析,并且必須加載所有DOM在內(nèi)存中,這可能是一個(gè)大飼料的問題
      它默認(rèn)為:'iternodes'。
  • itertag
    一個(gè)具有要迭代的節(jié)點(diǎn)(或元素)的名稱的字符串。示例:
    itertag = 'product'

  • namespaces
    定義該文檔中將使用此爬蟲處理的命名空間的元組列表。在 與將用于自動(dòng)注冊(cè)使用的命名空間 的方法。(prefix, uri)prefixuriregister_namespace()
    示例代碼:

    class YourSpider(XMLFeedSpider):
    
        namespaces = [('n', 'http://www.sitemaps.org/schemas/sitemap/0.9')]
        itertag = 'n:url'
    
  • adapt_response(response)
    一種在爬蟲開始解析響應(yīng)之前,在響應(yīng)從爬蟲中間件到達(dá)時(shí)立即接收的方法。它可以用于在解析之前修改響應(yīng)主體。此方法接收響應(yīng)并返回響應(yīng)(它可以是相同的或另一個(gè))。

  • parse_node(response, selector)
    對(duì)于與提供的標(biāo)記名稱(itertag)匹配的節(jié)點(diǎn),將調(diào)用此方法。接收Selector每個(gè)節(jié)點(diǎn)的響應(yīng)和 。覆蓋此方法是必需的。否則,你的爬蟲將不工作。此方法必須返回一個(gè)Item對(duì)象,一個(gè) Request對(duì)象或包含任何對(duì)象的迭代器。

  • process_results(response, results)
    對(duì)于由爬蟲返回的每個(gè)結(jié)果(Items or Requests),將調(diào)用此方法,并且它將在將結(jié)果返回到框架核心之前執(zhí)行所需的任何最后處理,例如設(shè)置項(xiàng)目ID。它接收結(jié)果列表和產(chǎn)生那些結(jié)果的響應(yīng)。它必須返回結(jié)果列表

2.2 Item Loader

Item Loader提供了一種便捷的方式填充抓取到的items。雖然items可以使用自帶的類字典形式API填充,但是itemsLoader提供了更便捷的API可以分析原始數(shù)據(jù)并對(duì)item進(jìn)行賦值。也就是Items提供保存捉取數(shù)據(jù)的容器,而Item Loader提供的是填充容器的機(jī)制,且可以方便的被拓展,重寫不同字段的解析規(guī)則,這對(duì)大型的爬蟲項(xiàng)目后期維護(hù)非常有利,拓展新的功能更加方便。
以下是Item和Item Loader的對(duì)比代碼:

class SpiderItem(scrapt.Item):
    url = scrapy.Field()
    time = scrapy.Field()
    title = scrapy.Field()
    content = scrapy.Field()
def parse(self, response):
    i = ItemLoader(item=Product(), response=response)
    l.add_xpath('title', '//div[@class="title1"]')
    l.add_xpath('title', '//div[@class="title2"]')
    l.add_xpath('time', '//div[@class="time"]')
    l.add_xpath('content', '//div[@class="content"]')
    return l.load_item()

可以發(fā)現(xiàn)的是ItemsLoader里面的name字段可以從頁面中兩個(gè)不同的XPAth位置提取,之后分配給name字段。下面類似操作后把所有數(shù)據(jù)收集起來,再使用l.load_item將數(shù)據(jù)實(shí)際填充到Item中

2.2.1 輸入與輸出處理器

Item Loder實(shí)現(xiàn)數(shù)據(jù)的收集處理和填充的功能都是通過輸入與輸出處理器來實(shí)現(xiàn)的,他們的功能是:

  • Item Loder在每個(gè)字段中都包含了一個(gè)輸入處理器和輸出處理器
  • 輸入處理器收到response后時(shí)立刻通過add_xpath(),add_css()或者add_value()等方法提取數(shù)據(jù),經(jīng)過輸入處理器的結(jié)果被手機(jī)起來并且保存在Item Loder內(nèi),
  • 收集到所有的數(shù)據(jù)后,調(diào)用Item Loder.load_item()方法來填充并返回item對(duì)象。load_item()方法內(nèi)部先調(diào)用輸出處理器來處理收集到的數(shù)據(jù),最后把結(jié)果存入Item中。

下面是2個(gè)栗子:

from scrapy.loader import ItemLoader
from scrapy.loader.processors import TakeFirst, MapCompose, Join

class ProductLoader(ItemLoader):

    default_output_processor = TakeFirst()

    name_in = MapCompose(unicode.title)
    name_out = Join()

    price_in = MapCompose(unicode.strip)

    # ...

可以看到,輸入處理器使用_in后綴聲明,而輸出處理器使用_out后綴聲明。您還可以使用ItemLoader.default_input_processor和 ItemLoader.default_output_processor屬性聲明默認(rèn)輸入/輸出 處理器。
輸入和輸出處理器可以在Item Loader定義中聲明,這種方式聲明輸入處理器是很常見的。但是,還可以在item中聲明:

import scrapy
from scrapy.loader.processors import Join, MapCompose, TakeFirst
from w3lib.html import remove_tags

def filter_price(value):
    if value.isdigit():
        return value

class Product(scrapy.Item):
    name = scrapy.Field(
        input_processor=MapCompose(remove_tags),
        output_processor=Join(),
    )
    price = scrapy.Field(
        input_processor=MapCompose(remove_tags, filter_price),
        output_processor=TakeFirst(),
    )

以上說了三種處理器聲明方式:

  • ItemLoader類中聲明類似field_in和field_out的屬性
  • itemd 的字段中聲明
  • ItemLoader默認(rèn)處理器ItemLoader.default_input_processor和 ItemLoader.default_output_processor
    這三種方式響應(yīng)優(yōu)先級(jí)是從上到下依次遞減

2.2.2 Item Loader Context

所有輸入和輸出處理器之間共享的任意鍵/值的dict。它可以在聲明,實(shí)例化或使用Item Loader時(shí)傳遞。它們用于修改輸入/輸出處理器的行為。

例如,假設(shè)有一個(gè)parse_length接收文本值并從中提取長度的函數(shù):

def  parse_length (text , loader_context ):
    unit  =  loader_context 。get ('unit' , 'm' )
    #...長度解析代碼在這里... 
    return  parsed_length

通過接受一個(gè)loader_context參數(shù),該函數(shù)顯式地告訴Item Loader它能夠接收一個(gè)Item Loader上下文,因此Item Loader在調(diào)用它時(shí)傳遞當(dāng)前活動(dòng)的上下文,因此處理器功能(parse_length在這種情況下)可以使用它們。
有幾種方法可以修改Item Loader上下文值:

  • 通過修改當(dāng)前活動(dòng)的Item Loader上下文(context屬性):
    loader = ItemLoader(product)
    loader.context['unit'] = 'cm'
    
  • On Item Loader實(shí)例化(Item Loader構(gòu)造函數(shù)的關(guān)鍵字參數(shù)存儲(chǔ)在Item Loader上下文中):
    loader = ItemLoader(product, unit='cm')
    
  • On Item Loader聲明,對(duì)于那些支持使用Item Loader上下文實(shí)例化的輸入/輸出處理器。MapCompose是其中之一:
    class ProductLoader(ItemLoader):
        length_out = MapCompose(parse_length, unit='cm')
    

2.2.3 重用和拓展 Item Loader

隨著你的項(xiàng)目越來越大,越來越多的爬蟲,維護(hù)成為一個(gè)根本的問題,特別是當(dāng)你必須處理每個(gè)爬蟲的許多不同的解析規(guī)則,有很多異常,但也想重用公共處理器。

項(xiàng)目加載器旨在減輕解析規(guī)則的維護(hù)負(fù)擔(dān),同時(shí)不會(huì)失去靈活性,同時(shí)提供了擴(kuò)展和覆蓋它們的方便的機(jī)制。因此,項(xiàng)目加載器支持傳統(tǒng)的python類繼承,以處理特定爬蟲(或爬蟲組)的差異。

例如,假設(shè)某個(gè)特定站點(diǎn)以三個(gè)短劃線(例如)包含其產(chǎn)品名稱,并且您不希望最終在最終產(chǎn)品名稱中刪除那些破折號(hào)?!狿lasma TV—

以下是如何通過重用和擴(kuò)展默認(rèn)產(chǎn)品項(xiàng)目Loader(ProductLoader)來刪除這些破折號(hào):

from scrapy.loader.processors import MapCompose
from myproject.ItemLoaders import ProductLoader

def strip_dashes(x):
    return x.strip('-')

class SiteSpecificLoader(ProductLoader):
    name_in = MapCompose(strip_dashes, ProductLoader.name_in)

另一種擴(kuò)展項(xiàng)目加載器可能非常有用的情況是,當(dāng)您有多種源格式,例如XML和HTML。在XML版本中,您可能想要?jiǎng)h除CDATA事件。下面是一個(gè)如何做的例子:

from scrapy.loader.processors import MapCompose
from myproject.ItemLoaders import ProductLoader
from myproject.utils.xml import remove_cdata

class XmlProductLoader(ProductLoader):
    name_in = MapCompose(remove_cdata, ProductLoader.name_in)

對(duì)于輸出處理器,更常用的方式是在item字段元數(shù)據(jù)里聲明,因?yàn)橥ǔK鼈円蕾囉诰唧w的字段而不是網(wǎng)站

2.2.4 內(nèi)置的處理器

除了可以使用可調(diào)用的函數(shù)作為輸入輸出處理器,Scrapy提供了一些常用的處理器,下面是一些內(nèi)置的處理器

  • Identity
    類原型: class scrapy.loader.processors.Identity
    最簡單的處理器,不進(jìn)行任何處理,直接返回原來的數(shù)據(jù),無參數(shù)
  • TakeFirst
    類原型: class scrapy.loader.processors.TakeFirst
    從接收到的值中返回第一個(gè)非空值/非空值,因此它通常用作單值字段的輸出處理器。它不接收任何構(gòu)造函數(shù)參數(shù),也不接受Loader上下文
    >>> from scrapy.loader.processors import TakeFirst
    >>> proc = TakeFirst()
    >>> proc(['', 'one', 'two', 'three'])
    'one'
    
  • Join
    類原型: class scrapy.loader.processors.Join(separator=u'')
    返回用分隔符separatoe連續(xù)后的值,分隔符seeparator默認(rèn)為空格。不接受Loader contexts。當(dāng)使用默認(rèn)分隔符的時(shí)候,這個(gè)處理器等同于Python字符串對(duì)象中join方法:''.join
    >>> from scrapy.loader.processors import Join
    >>> proc = Join()
    >>> proc(['one', 'two', 'three'])
    u'one two three'
    >>> proc = Join('<br>')
    >>> proc(['one', 'two', 'three'])
    u'one<br>two<br>three'
    
  • Compose
    類原型: class scrapy.loader.processors.Compose(functions,*default_loader_context)
    由給定函數(shù)的組合構(gòu)成的處理器。這意味著該處理器的每個(gè)輸入值都被傳遞給第一個(gè)函數(shù),并且該函數(shù)的結(jié)果被傳遞給第二個(gè)函數(shù),依此類推,直到最后一個(gè)函數(shù)返回該處理器的輸出值。
    默認(rèn)情況下,停止進(jìn)程N(yùn)one值??梢酝ㄟ^傳遞關(guān)鍵字參數(shù)來更改此行為stop_on_none=False
    >>> from scrapy.loader.processors import Compose
    >>> proc = Compose(lambda v: v[0], str.upper)
    >>> proc(['hello', 'world'])
    'HELLO'
    
  • MapCompose
    類原型: class scrapy.loader.processors.MapCompose(functions,*default_loader_context)
    和Compose類似,也是用給定的多個(gè)方法的組合來構(gòu)造處理器,不同的是內(nèi)部結(jié)果在方法見傳遞的方式:
    • 該處理器的輸入值被迭代,并且第一函數(shù)被應(yīng)用于每個(gè)元素。這些函數(shù)調(diào)用的結(jié)果(每個(gè)元素一個(gè))被連接以構(gòu)造新的迭代,然后用于應(yīng)用第二個(gè)函數(shù),等等,直到最后一個(gè)函數(shù)被應(yīng)用于收集的值列表的每個(gè)值遠(yuǎn)。最后一個(gè)函數(shù)的輸出值被連接在一起以產(chǎn)生該處理器的輸出。
    • 每個(gè)特定函數(shù)可以返回值或值列表,這些值通過應(yīng)用于其他輸入值的相同函數(shù)返回的值列表展平。函數(shù)也可以返回None,在這種情況下,該函數(shù)的輸出將被忽略,以便在鏈上進(jìn)行進(jìn)一步處理。
    • 此處理器提供了一種方便的方法來組合只使用單個(gè)值(而不是iterables)的函數(shù)。由于這個(gè)原因, MapCompose處理器通常用作輸入處理器,因?yàn)閿?shù)據(jù)通常使用選擇器的 extract()方法提取,選擇器返回unicode字符串的列表。
    >>> def filter_world(x):
    ...     return None if x == 'world' else x
    ...
    >>> from scrapy.loader.processors import MapCompose
    >>> proc = MapCompose(filter_world, unicode.upper)
    >>> proc([u'hello', u'world', u'this', u'is', u'scrapy'])
    [u'HELLO, u'THIS', u'IS', u'SCRAPY']
    
  • SelectJmes
    類原型: class scrapy.loader.processors.SelectJmes(json_path)
    使用提供給構(gòu)造函數(shù)的json路徑查詢值,并返回輸出。需要運(yùn)行jmespath。該處理器一次只需要一個(gè)輸入。
    >>> from scrapy.loader.processors import SelectJmes, Compose, MapCompose
    >>> proc = SelectJmes("foo") #for direct use on lists and dictionaries
    >>> proc({'foo': 'bar'})
    'bar'
    >>> proc({'foo': {'bar': 'baz'}})
    {'bar': 'baz'}
    
    和json一起使用
    
    >>> import json
    >>> proc_single_json_str = Compose(json.loads, SelectJmes("foo"))
    >>> proc_single_json_str('{"foo": "bar"}')
    u'bar'
    >>> proc_json_list = Compose(json.loads, MapCompose(SelectJmes('foo')))
    >>> proc_json_list('[{"foo":"bar"}, {"baz":"tar"}]')
    [u'bar']
    

2.3 Item Pipeline

前面已經(jīng)說了Item Pipeline的基本用法了,現(xiàn)在看的是如何利用item Pipeline來進(jìn)行數(shù)據(jù)存儲(chǔ)

2.3.1內(nèi)置圖片和文件下載

item Pipeline提供了一些可以重用的Pipeline,其中有filesPipeline和imagesPipeline,他們都實(shí)現(xiàn)了以下特性:

  • 避免重新下載最近已經(jīng)下載過的數(shù)據(jù)
  • 指定存儲(chǔ)的位置和方式

此外,imagesPipeline還提供了二位的特性:

  • 將所有下載的圖片轉(zhuǎn)換成通用的格式(jpg)和模式(rgb)
  • 粗略圖生成
  • 檢測圖像的寬/高,確保他們滿足最小限制

使用FIlesPipeline方法如下:
1)在setting.py的ITEM_PIPELINES中添加一條'scrapy.pipelines.files.FilesPipeline':1
2)在item中添加兩個(gè)字段,比如:
file_urls = scrapy.Field()
files = scrapy.Field()
3)在setting.py文件中添加下載路徑,文件url所在的item字段,和文件結(jié)果信息所在item字段,比如:
FILES_STORE = '/HOME/XXX/SPIDER'
FILES_URLS_FIELD ='file_urls'
FILES_RESULT_FIELD = 'files'
使用files_expires設(shè)置文件過期時(shí)間,示例如下:
FILES_EXPIRES = 30 #30天過期

使用ImagesPIpeline方法如下:
1)在setting.py的ITEM_PIPELINES中添加一條'scrapy.pipelines.images.ImagesPipeline':1
2)在item中添加兩個(gè)字段,比如:
image_urls = scrapy.Field()
images = scrapy.Field()
3 )在setting.py文件中添加下載路徑,文件url所在的item字段,和文件結(jié)果信息所在item字段,比如:
IMAGES_STORE = '/HOME/XXX/SPIDER'
IMAGES_URLS_FIELD ='file_urls'
IMAGES_RESULT_FIELD = 'files'
除此之外,使用IMAGES_THUMS制作縮略圖,并這是縮略圖尺寸大小,并使用IMAGES_EXPIRES設(shè)置文件過期時(shí)間,示例如下:
IMAGES_THUMBS = {
'small':(50, 50),
'big': (270,270),
}
IMAGES_EXPIRES = 30 #30天過期

除此之外,Item Pipeline還有三個(gè)方法非常常用

  • open_spider(self, spider)
    當(dāng)spider被開啟時(shí),這個(gè)方法被調(diào)用
  • close_spider(self, spider)
    當(dāng)spider被關(guān)閉是,這個(gè)方法被調(diào)用
  • from_crawler(cls, crawler)
    這個(gè)類方法從Crawles屬性中創(chuàng)建一個(gè)pipeline實(shí)例,Crawle對(duì)象能夠接觸所有Scrapy的核心組件,比如setting

以下是一個(gè)存儲(chǔ)到MongoDB的示例代碼:

import pymongo



class ZhihuspiderPipeline(object):

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

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

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

    def close_spider(self, spider):
        self.client.close()
    
    def process_item(self, item, spider):
        self.db.UserInfo.insert(dict(item))
        return item

代碼中首先通過from_crawler方法,獲取setting中的MongoDB的URL和數(shù)據(jù)庫名稱,從而創(chuàng)建一個(gè)MongoPIpeline實(shí)例,在SPider開始運(yùn)行是,在open_spider方法中建立數(shù)據(jù)庫連接,當(dāng)Spider關(guān)閉是,在close_spider方法中關(guān)閉數(shù)據(jù)庫連接。
當(dāng)是,這里配置的Pipeline會(huì)作用于所有的Spider,假如項(xiàng)目中有很多Spider在運(yùn)行,處理起來就會(huì)很麻煩。所以通過process_item(self,item,spider)中的SPider參數(shù)判斷是來自于哪個(gè)爬蟲,不過還有更好的方法:
就是配置SPider類中的suctom_settings對(duì)象,為每一個(gè)Spider配置不同的pipeline

class MySpider(CrawlSpider):
    custom_settings = {
        'ITEM_PIPELINES':{
            'test.pipelines.TestPipeline.TestPipeline':1,
        }
    }

2.4 請(qǐng)求與響應(yīng)

在編寫Spider的模塊中接觸最緊密的是請(qǐng)求和響應(yīng)。

2.4.1Request對(duì)象

一個(gè)Request對(duì)象代表著一個(gè)HTTP請(qǐng)求,通常在Spider類中產(chǎn)生,然后傳遞給下載器,最后返回一個(gè)響應(yīng)
類原型:class scrapy.http.Request(url[, callback, method='GET', headers, body, cookies, meta, encoding='utf-8', priority=0, dont_filter=False, errback])
構(gòu)造參數(shù)說明:

  • url(string) - 此請(qǐng)求的網(wǎng)址
  • callback(callable) - 將使用此請(qǐng)求的響應(yīng)(一旦下載)作為其第一個(gè)參數(shù)調(diào)用的函數(shù)。如果請(qǐng)求沒有指定回調(diào),parse()將使用spider的 方法。請(qǐng)注意,如果在處理期間引發(fā)異常,則會(huì)調(diào)用errback。
  • method(string) - 此請(qǐng)求的HTTP方法。默認(rèn)為’GET’。
  • meta(dict) - 屬性的初始值Request.meta。如果給定,在此參數(shù)中傳遞的dict將被淺復(fù)制。
  • body(str或unicode) - 請(qǐng)求體。如果unicode傳遞了a,那么它被編碼為 str使用傳遞的編碼(默認(rèn)為utf-8)。- 如果 body沒有給出,則存儲(chǔ)一個(gè)空字符串。不管這個(gè)參數(shù)的類型,存儲(chǔ)的最終值將是一個(gè)str(不會(huì)是unicode或None)。
  • headers(dict) - 這個(gè)請(qǐng)求的頭。dict值可以是字符串(對(duì)于單值標(biāo)頭)或列表(對(duì)于多值標(biāo)頭)。如果 None作為值傳遞,則不會(huì)發(fā)送HTTP頭。
  • cookie(dict或list) - 請(qǐng)求cookie。這些可以以兩種形式發(fā)送。
  • encoding(string) - 此請(qǐng)求的編碼(默認(rèn)為’utf-8’)。此編碼將用于對(duì)URL進(jìn)行百分比編碼,并將正文轉(zhuǎn)換為str(如果給定unicode)。
  • priority(int) - 此請(qǐng)求的優(yōu)先級(jí)(默認(rèn)為0)。調(diào)度器使用優(yōu)先級(jí)來定義用于處理請(qǐng)求的順序。具有較高優(yōu)先級(jí)值的請(qǐng)求將較早執(zhí)行。允許負(fù)值以指示相對(duì)低優(yōu)先級(jí)。
  • dont_filter(boolean) - 表示此請(qǐng)求不應(yīng)由調(diào)度程序過濾。當(dāng)您想要多次執(zhí)行相同的請(qǐng)求時(shí)忽略重復(fù)過濾器時(shí)使用。小心使用它,或者你會(huì)進(jìn)入爬行循環(huán)。默認(rèn)為False。
  • errback(callable) - 如果在處理請(qǐng)求時(shí)引發(fā)任何異常,將調(diào)用的函數(shù)。這包括失敗的404 HTTP錯(cuò)誤等頁面。

cookies參數(shù)設(shè)置的兩種方法
使用字典

request_with_cookies = Request(url="http://www.example.com",
                            cookies={'currency': 'USD', 'country': 'UY'})

使用字典列表發(fā)送

request_with_cookies = Request(url="http://www.example.com",
                              cookies=[{'name': 'currency',
                                       'value': 'USD',
                                       'domain': 'example.com',
                                       'path': '/currency'}]) 

當(dāng)某些網(wǎng)站返回Cookie(在響應(yīng)中)時(shí),這些Cookie會(huì)存儲(chǔ)在該域的Cookie中,并在將來的請(qǐng)求中再次發(fā)送。這是任何常規(guī)網(wǎng)絡(luò)瀏覽器的典型行為。但是,如果由于某種原因,想要避免與現(xiàn)有Cookie合并,可以設(shè)置Requ.meta中dont_merge_cookies字段的值。示例如下

request_with_cookies = Request(url="http://www.example.com",
                              cookies={'currency': 'USD', 'country': 'UY'},
                              meta={'dont_merge_cookies': True})

errback的使用方法示例:

import scrapy

from scrapy.spidermiddlewares.httperror import HttpError
from twisted.internet.error import DNSLookupError
from twisted.internet.error import TimeoutError, TCPTimedOutError

class ErrbackSpider(scrapy.Spider):
   name = "errback_example"
   start_urls = [
       "http://www.httpbin.org/",              # HTTP 200 expected
       "http://www.httpbin.org/status/404",    # Not found error
       "http://www.httpbin.org/status/500",    # server issue
       "http://www.httpbin.org:12345/",        # non-responding host, timeout expected
       "http://www.httphttpbinbin.org/",       # DNS error expected
   ]

   def start_requests(self):
       for u in self.start_urls:
           yield scrapy.Request(u, callback=self.parse_httpbin,
                                   errback=self.errback_httpbin,
                                   dont_filter=True)

   def parse_httpbin(self, response):
       self.logger.info('Got successful response from {}'.format(response.url))
       # do something useful here...

   def errback_httpbin(self, failure):
       # log all failures
       self.logger.error(repr(failure))

       # in case you want to do something special for some errors,
       # you may need the failure's type:

       if failure.check(HttpError):
           # these exceptions come from HttpError spider middleware
           # you can get the non-200 response
           response = failure.value.response
           self.logger.error('HttpError on %s', response.url)

       elif failure.check(DNSLookupError):
           # this is the original request
           request = failure.request
           self.logger.error('DNSLookupError on %s', request.url)

       elif failure.check(TimeoutError, TCPTimedOutError):
           request = failure.request
           self.logger.error('TimeoutError on %s', request.url)

下面寫的是Request的子類 FormRquest類,專門用來處理html表單,尤其對(duì)隱藏表單的處理非常方便,適合用來完成登錄操作
類原型:class scrapy.http.FormRequest(url[, formdata, ...])
其中構(gòu)造參數(shù)formdata可以是字典,也可以是可迭代的(key,value)元祖,代表著需要提交的表單數(shù)據(jù)。
示例如下:

return [FormRequest(url="http://www.example.com/post/action",
                    formdata={'name': 'John Doe', 'age': '27'},
                    callback=self.after_post)]

通常網(wǎng)站通過<input type="hidden">實(shí)現(xiàn)對(duì)某些表單字段的預(yù)填充,就像知乎的隱藏表單的_xfv數(shù)值,Scray在FromRequest類提供了一個(gè)類方法from_reponse來解決隱藏表單這個(gè)問題。方法原型如下:

classmethod from_response(response[, formname=None, formid=None, formnumber=0, formdata=None, formxpath=None, formcss=None, clickdata=None, dont_click=False, ...])

參數(shù)說明:

  • response(Responseobject) - 包含將用于預(yù)填充表單字段的HTML表單的響應(yīng)
  • formname(string) - 如果給定,將使用name屬性設(shè)置為此值的形式。
  • formid(string) - 如果給定,將使用id屬性設(shè)置為此值的形式。
  • formxpath(string) - 如果給定,將使用匹配xpath的第一個(gè)表單。
  • formcss(string) - 如果給定,將使用匹配css選擇器的第一個(gè)形式。
  • formnumber(integer) - 當(dāng)響應(yīng)包含多個(gè)表單時(shí)要使用的表單的數(shù)量。第一個(gè)(也是默認(rèn))是0。
  • formdata(dict) - 要在表單數(shù)據(jù)中覆蓋的字段。如果響應(yīng)元素中已存在字段,則其值將被在此參數(shù)中傳遞的值覆蓋。
  • clickdata(dict) - 查找控件被點(diǎn)擊的屬性。如果沒有提供,表單數(shù)據(jù)將被提交,模擬第一個(gè)可點(diǎn)擊元素的點(diǎn)擊。除了html屬性,控件可以通過其相對(duì)于表單中其他提交表輸入的基于零的索引,通過nr屬性來標(biāo)識(shí)。
  • dont_click(boolean) - 如果為True,表單數(shù)據(jù)將在不點(diǎn)擊任何元素的情況下提交。

下面是一個(gè)實(shí)現(xiàn)登錄功能的示例代碼:

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):
        # check login succeed before going on
        if "authentication failed" in response.body:
            self.logger.error("Login failed")
            return

2.4.2 Response對(duì)象

Response對(duì)象代表著HTTP響應(yīng),Response通常是從下載器獲取然后交給Spider處理:
類原型:class scrapy.http.Response(url[, status=200, headers=None, body=b'', flags=None, request=None])
構(gòu)造參數(shù)說明:

  • url(string) - 此響應(yīng)的URL
  • status(integer) - 響應(yīng)的HTTP狀態(tài)。默認(rèn)為200。
  • headers(dict) - 這個(gè)響應(yīng)的頭。dict值可以是字符串(對(duì)于單值標(biāo)頭)或列表(對(duì)于多值標(biāo)頭)。
  • body(str) - 響應(yīng)體。它必須是str,而不是unicode,除非你使用一個(gè)編碼感知響應(yīng)子類,如 TextResponse。
  • flags(list) - 是一個(gè)包含屬性初始值的 Response.flags列表。如果給定,列表將被淺復(fù)制。
  • request(Requestobject) - 屬性的初始值Response.request。這代表Request生成此響應(yīng)。
  • meta(dict):用來初始化Response.meta

關(guān)于參數(shù)的其他補(bǔ)充:

  • request
    Request生成此響應(yīng)的對(duì)象。在響應(yīng)和請(qǐng)求通過所有下載中間件后,此屬性在Scrapy引擎中分配。特別地,這意味著:
    HTTP重定向?qū)?dǎo)致將原始請(qǐng)求(重定向之前的URL)分配給重定向響應(yīng)(重定向后具有最終URL)。
    Response.request.url并不總是等于Response.url
    此屬性僅在爬蟲程序代碼和 Spider Middleware中可用,但不能在Downloader Middleware中使用(盡管您有通過其他方式可用的請(qǐng)求)和處理程序response_downloaded。
  • meta
    的快捷方式Request.meta的屬性 Response.request對(duì)象(即self.request.meta)。
    與Response.request屬性不同,Response.meta 屬性沿重定向和重試傳播,因此您將獲得Request.meta從您的爬蟲發(fā)送的原始屬性。
    也可以看看
    Request.meta 屬性
  • flags
    包含此響應(yīng)的標(biāo)志的列表。標(biāo)志是用于標(biāo)記響應(yīng)的標(biāo)簽。例如:’cached’,’redirected ‘等等。它們顯示在Response(_ str_ 方法)的字符串表示上,它被引擎用于日志記錄。
  • copy()
    返回一個(gè)新的響應(yīng),它是此響應(yīng)的副本。
  • replace([ url,status,headers,body,request,flags,cls ] )
    返回具有相同成員的Response對(duì)象,但通過指定的任何關(guān)鍵字參數(shù)賦予新值的成員除外。該屬性Response.meta是默認(rèn)復(fù)制。
  • urljoin(url )
    通過將響應(yīng)url與可能的相對(duì)URL 組合構(gòu)造絕對(duì)url。
    這是一個(gè)包裝在urlparse.urljoin,它只是一個(gè)別名,使這個(gè)調(diào)用:
    urlparse.urljoin(response.url, url)

同樣的,Response有一個(gè)子類TextResponse,多了一個(gè)智能編碼的功能。
類原型:class scrapy.http.TextResponse(url[, encoding[, ...]])
構(gòu)造參數(shù)encoding是一個(gè)包含編碼的字符串,如果使用一個(gè)unicode編碼的body構(gòu)造出TextResponse實(shí)例,那body屬性會(huì)使用encoding進(jìn)行編碼。
TextResponse類除了具有Response屬性,還擁有自己的屬性:

  • encoding:包含編碼的字符串。編碼的優(yōu)先級(jí)由高到低如下所示:
    1)首先選用構(gòu)造器中傳入的encoding
    2)選用http頭中Content-Type字段的編碼。如果編碼無效,則被忽略,繼續(xù)嘗試下面的規(guī)則
    3)選用響應(yīng)body中的編碼
    4)最后猜測響應(yīng)的編碼,這種方式是比較脆弱
  • selector:以當(dāng)前響應(yīng)為目標(biāo)的選擇器實(shí)例。
    TextResponse類除了支持Response中的方法,還支持以下方法response
  • body_as_unicode():返回unicode編碼的響應(yīng)body內(nèi)容。等價(jià)于:
    response.body.decode(response.encoding)
  • xpath(query):等價(jià)于TextResponse.selector.xpath(query).示例如下:
    response.xpath('//p')
  • css(query):等價(jià)于TextResponse.selector.css(query).示例如下:
    response.css('//p')

2.5下載器中間件

下載器中間件是介于Scrapy的request/response處理的鉤子框架,是用于全局修改Scrapy的request和response,可以幫助我們定制自己的爬蟲系統(tǒng)
激活哦下載器中間件的方法如下
在setting.py里面的DOWNLOADER_MIDDLEWARES修改,示例如下:
DOWNLOADER_MIDDLEWARES = {
‘myproject.middlewares.CustomDownloaderMiddleware': 543,
}
后面數(shù)值的設(shè)置要看自己的需求,越接近0越靠近引擎,越接近1000越靠近下載器,數(shù)字設(shè)置一般看自己需要先用到哪些中間件,就把數(shù)字設(shè)定得比哪個(gè)中間件大
中間件組建定義了以下python類,可以根據(jù)這些類來編寫自己的中間件

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

2.5.1process_request(request,spider)

當(dāng)每個(gè)request通過下載中間件時(shí),該方法被調(diào)用,返回值必須為none,response對(duì)象,request對(duì)象中的一個(gè)或raise IgnoreRequest異常
參數(shù)說明:
Request:處理的request
Spider:該Request對(duì)應(yīng)的Spider
返回值:如果返回None,Scrapy將繼續(xù)處理該Request,執(zhí)行其他中間件的相應(yīng)方法,直到合適的下載器處理函數(shù)被調(diào)用,該Request被執(zhí)行(其Response下載)
如果返回Response對(duì)象,SCrapy不會(huì)調(diào)用下面2個(gè)方法,或相應(yīng)的下載方法,將返回該response。已安裝的中間件的process_response()方法則會(huì)在每個(gè)respon返回時(shí)被調(diào)用
如果返回request對(duì)象,Scrapy則停止調(diào)用process_request方法并重新調(diào)度放回的Request。當(dāng)新返回的Request被執(zhí)行后,相應(yīng)地中間件鏈將會(huì)根據(jù)下載的Response被調(diào)用
如果是Raise IgnoreRequest異常,則安裝的下載中間件的process_exception()方法會(huì)被調(diào)用。如果沒有任何一個(gè)方法處理該異常,則Request的errback方法會(huì)被調(diào)用。如果沒有代碼處理拋出的異常,則該異常被忽略且不記錄

2.5.2process_request(request,response,spider)

該方法主要用來處理產(chǎn)生的Response返回值必須為none,response對(duì)象,request對(duì)象中的一個(gè)或raise IgnoreRequest異常
參數(shù)說明:
Request:處理的request
response:處理的response
Spider:該Request對(duì)應(yīng)的Spider
返回值:
如果返回Response對(duì)象,可以與傳入的respon相同,也可以是新的對(duì)象,該Response會(huì)被鏈中的其他中間見的process_reponse()方法處理
如果返回request對(duì)象,Scrapy則停止調(diào)用process_request方法并重新調(diào)度放回的Request。當(dāng)新返回的Request被執(zhí)行后,相應(yīng)地中間件鏈將會(huì)根據(jù)下載的Response被調(diào)用
如果是Raise IgnoreRequest異常,則安裝的下載中間件的process_exception()方法會(huì)被調(diào)用。如果沒有任何一個(gè)方法處理該異常,則Request的errback方法會(huì)被調(diào)用。如果沒有代碼處理拋出的異常,則該異常被忽略且不記錄

2.5.3process_request(request,exceptionmspider)

當(dāng)下載處理器或process_request()拋出異常,比如IgnoreRequest異常時(shí),Scrapy調(diào)用process_exception().process_exception()應(yīng)該返回none,response對(duì)象,request對(duì)象中的一個(gè)
參數(shù)說明:
Request:處理的request
exception:拋出的異常
Spider:該Request對(duì)應(yīng)的Spider
返回值:
如果返回None,Scrapy將會(huì)繼續(xù)處理該異常,接著調(diào)用已安裝的其他中間件的process_exception()方法,知道所有中間件都被調(diào)用完畢,則調(diào)用默認(rèn)的異常處理
如果返回response對(duì)象,則已安裝的中間級(jí)鏈的process_response()方法被調(diào)用。Scrapy將不會(huì)調(diào)用任何其他中間件的process_exception()方法
如果返回Request對(duì)象,則返回的request將會(huì)被重新調(diào)用下載,這將停止中間級(jí)的process_exception()方法執(zhí)行,類似于返回respon對(duì)象的處理

下面是自定義下載器中間件的兩個(gè)栗子:

設(shè)置User-Agent字段,需要自己在setting,py中設(shè)置User-Agent列表

class RandomUserAgent(object):
    def __init__(self,agents):
        self.agents =agents
        
    @classmethod
    def from_crawler(cls,crawler):
        return cls(crawler.settings.getlist('USER_AGENTS'))
    
    def process_request(self,request,spider):
        request.headers.setdefault('User-Agent', random.choice(self.agents))

動(dòng)態(tài)設(shè)置request代理ip,需要自己在setting,py中設(shè)置IPlist列表

class RandomProxy(object):
    def __init__(self,iplist):
        self.iplist=iplist

    @classmethod
    def from_crawler(cls,crawler):
        return cls(crawler.settings.getlist('IPLIST'))

    def process_request(self, request, spider):
        proxy = random.choice(self.iplist)
        request.meta['proxy'] =proxy

2.6Spider中間件

這個(gè)看官方文檔就好了 ——這里

3.部署

這里都是編寫完爬蟲代碼后的其他一些應(yīng)用,不完全屬于部署

3.1分布式下載器:Crawlea

官方提供了一個(gè)分布式下載器,用來幫助我們躲避反爬蟲的措施。首先在官網(wǎng)注冊(cè)。之后拿到APIKEY用作驗(yàn)證
不過是需要錢的
官網(wǎng):https://app.scrapinghub.com/account/login/?next=/next=/account/login
安裝:pip install scrapy-crawlera
之后修改setting.py
DOWNLOADER_MIDDLEWARES ={'scrapy_crawlera.CrawleraMiddleware':300}
CRAWLERA_ENABLED = True
CRAWLERA_APIKEY = '<API key>'

3.2Scrapyd

是的。官方又提供了一個(gè)部署爬蟲非常有用的工具,Scrapyd是運(yùn)行Scrapy爬蟲的服務(wù)程序,它吃屎以http命令方式通過json api進(jìn)行發(fā)布、刪除、啟動(dòng)、停止爬蟲程序的操作,而且它可以同時(shí)管理多個(gè)爬蟲,每個(gè)爬蟲還可以有多個(gè)版本,也是部署分布式爬蟲有效手段
安裝:pipi install scrapyd
使用:輸入scrapyd啟動(dòng),在瀏覽器輸入:http://127.0.0.1:6800/就可以看到界面了
API使用示例如下:

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

import requests
import json 

baseUrl ='http://127.0.0.1:6800/'
daemUrl ='http://127.0.0.1:6800/daemonstatus.json'
listproUrl ='http://127.0.0.1:6800/listprojects.json'
listspdUrl ='http://127.0.0.1:6800/listspiders.json?project=%s'
listspdvUrl= 'http://127.0.0.1:6800/listversions.json?project=%s'
listjobUrl ='http://127.0.0.1:6800/listjobs.json?project=%s'
delspdvUrl= 'http://127.0.0.1:6800/delversion.json'

#http://127.0.0.1:6800/daemonstatus.json
#查看scrapyd服務(wù)器運(yùn)行狀態(tài)
r= requests.get(daemUrl)
print '1.stats :\n %s \n\n'  %r.text  

#http://127.0.0.1:6800/listprojects.json
#獲取scrapyd服務(wù)器上已經(jīng)發(fā)布的工程列表
r= requests.get(listproUrl)
print '1.1.listprojects : [%s]\n\n'  %r.text
if len(json.loads(r.text)["projects"])>0 :
    project = json.loads(r.text)["projects"][0]

#http://127.0.0.1:6800/listspiders.json?project=myproject
#獲取scrapyd服務(wù)器上名為myproject的工程下的爬蟲清單
listspd=listspd % project
r= requests.get(listspdUrl)
print '2.listspiders : [%s]\n\n'  %r.text 
if json.loads(r.text).has_key("spiders")>0 :
    spider =json.loads(r.text)["spiders"][0]


#http://127.0.0.1:6800/listversions.json?project=myproject
##獲取scrapyd服務(wù)器上名為myproject的工程下的各爬蟲的版本
listspdvUrl=listspdvUrl % project
r = requests.get(listspdvUrl)
print '3.listversions : [%s]\n\n'  %rtext 
if len(json.loads(r.text)["versions"])>0 :
    version = json.loads(r.text)["versions"][0]

#http://127.0.0.1:6800/listjobs.json?project=myproject
#獲取scrapyd服務(wù)器上的所有任務(wù)清單,包括已結(jié)束,正在運(yùn)行的,準(zhǔn)備啟動(dòng)的。
listjobUrl=listjobUrl % proName
r=requests.get(listjobUrl)
print '4.listjobs : [%s]\n\n'  %r.text 


#schedule.json
#http://127.0.0.1:6800/schedule.json -d project=myproject -d spider=myspider
#啟動(dòng)scrapyd服務(wù)器上myproject工程下的myspider爬蟲,使myspider立刻開始運(yùn)行,注意必須以post方式
schUrl = baseurl + 'schedule.json'
dictdata ={ "project":project,"spider":spider}
r= reqeusts.post(schUrl, json= dictdata)
print '5.1.delversion : [%s]\n\n'  %r.text 


#http://127.0.0.1:6800/delversion.json -d project=myproject -d version=r99'
#刪除scrapyd服務(wù)器上myproject的工程下的版本名為version的爬蟲,注意必須以post方式
delverUrl = baseurl + 'delversion.json'
dictdata={"project":project ,"version": version }
r= reqeusts.post(delverUrl, json= dictdata)
print '6.1.delversion : [%s]\n\n'  %r.text 

#http://127.0.0.1:6800/delproject.json -d project=myproject
#刪除scrapyd服務(wù)器上myproject工程,注意該命令會(huì)自動(dòng)刪除該工程下所有的spider,注意必須以post方式
delProUrl = baseurl + 'delproject.json'
dictdata={"project":project  }
r= reqeusts.post(delverUrl, json= dictdata)
print '6.2.delproject : [%s]\n\n'  %r.text 

3.3Scrapyd-client

懶得截圖訪問這里查看

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容