1. 程式人生 > >Scrapy爬蟲入門教程四 Spider(爬蟲)

Scrapy爬蟲入門教程四 Spider(爬蟲)

目錄

通用爬蟲

Spider

爬蟲是定義如何抓取某個網站(或一組網站)的類,包括如何執行抓取(即關注連結)以及如何從其網頁中提取結構化資料(即抓取專案)。換句話說,Spider是您定義用於為特定網站(或在某些情況下,一組網站)抓取和解析網頁的自定義行為的位置。

對於爬蟲,迴圈經歷這樣的事情:

  1. 您首先生成用於抓取第一個URL的初始請求,然後指定要使用從這些請求下載的響應呼叫的回撥函式。

    第一個執行的請求通過呼叫 start_requests()(預設情況下)Request為在start_urls和中指定的URL生成的parse方法獲取, 並且該方法作為請求的回撥函式。

  2. 在回撥函式中,您將解析響應(網頁),並返回帶有提取的資料,Item物件, Request物件或這些物件的可迭代的物件。這些請求還將包含回撥(可能是相同的),然後由Scrapy下載,然後由指定的回撥處理它們的響應。

  3. 在回撥函式中,您通常使用選擇器來解析頁面內容 (但您也可以使用BeautifulSoup,lxml或您喜歡的任何機制),並使用解析的資料生成專案。

  4. 最後,從爬蟲返回的專案通常將持久儲存到資料庫(在某些專案管道中)或使用Feed匯出寫入檔案。

即使這個迴圈(或多或少)適用於任何種類的爬蟲,有不同種類的預設爬蟲捆綁到Scrapy中用於不同的目的。我們將在這裡談論這些型別。

class scrapy.spiders.Spider

這是最簡單的爬蟲,每個其他爬蟲必須繼承的爬蟲(包括與Scrapy捆綁在一起的爬蟲,以及你自己寫的爬蟲)。它不提供任何特殊功能。它只是提供了一個預設start_requests()實現,它從start_urlsspider屬性發送請求,並parse 為每個結果響應呼叫spider的方法。

name 
定義此爬蟲名稱的字串。爬蟲名稱是爬蟲如何由Scrapy定位(和例項化),因此它必須是唯一的。但是,沒有什麼能阻止你例項化同一個爬蟲的多個例項。這是最重要的爬蟲屬性,它是必需的。

如果爬蟲抓取單個域名,通常的做法是在域後面命名爬蟲。因此,例如,抓取的爬蟲mywebsite.com通常會被呼叫 mywebsite。

注意 
在Python 2中,這必須是ASCII。

allowed_domains 
允許此爬蟲抓取的域的字串的可選列表,指定一個列表可以抓取,其它就不會抓取了。

start_urls 
當沒有指定特定網址時,爬蟲將開始抓取的網址列表。

custom_settings 
執行此爬蟲時將從專案寬配置覆蓋的設定字典。它必須定義為類屬性,因為設定在例項化之前更新。

有關可用內建設定的列表,請參閱: 內建設定參考

crawler 
此屬性from_crawler()在初始化類後由類方法設定,並連結Crawler到此爬蟲例項繫結到的物件。

Crawlers在專案中封裝了很多元件,用於單個條目訪問(例如擴充套件,中介軟體,訊號管理器等)。有關詳情,請參閱抓取工具API

settings 
執行此爬蟲的配置。這是一個 Settings例項,有關此主題的詳細介紹,請參閱設定主題

logger 
用Spider建立的Python記錄器name。您可以使用它通過它傳送日誌訊息,如記錄爬蟲程式中所述

from_crawler(crawler,* args,** kwargs ) 
是Scrapy用來建立爬蟲的類方法。

您可能不需要直接覆蓋這一點,因為預設實現充當方法的代理,__init__()使用給定的引數args和命名引數kwargs呼叫它。

儘管如此,此方法 在新例項中設定crawler和settings屬性,以便以後可以在爬蟲程式中訪問它們。

  • 引數: 
    • crawler(Crawlerinstance) - 爬蟲將繫結到的爬蟲
    • args(list) - 傳遞給init()方法的引數
    • kwargs(dict) - 傳遞給init()方法的關鍵字引數

start_requests() 
此方法必須返回一個可迭代的第一個請求來抓取這個爬蟲。

有了start_requests(),就不寫了start_urls,寫了也沒有用。

預設實現是:start_urls,但是可以複寫的方法start_requests。 
例如,如果您需要通過使用POST請求登入來啟動,您可以:

class MySpider(scrapy.Spider):
    name = 'myspider'

    def start_requests(self):
        return [scrapy.FormRequest("http://www.example.com/login",
                                   formdata={'user': 'john', 'pass': 'secret'},
                                   callback=self.logged_in)]

    def logged_in(self, response):
        # here you would extract links to follow and return Requests for
        # each of them, with another callback
        pass

make_requests_from_url(url) 
一種接收URL並返回Request 物件(或Request物件列表)進行抓取的方法。此方法用於在方法中構造初始請求 start_requests(),並且通常用於將URL轉換為請求。

除非重寫,此方法返回具有方法的Requests parse() 作為它們的回撥函式,並啟用dont_filter引數(Request有關更多資訊,請參閱類)。

parse(response) 
這是Scrapy用於處理下載的響應的預設回撥,當它們的請求沒有指定回撥時。

該parse方法負責處理響應並返回所抓取的資料或更多的URL。其他請求回撥具有與Spider類相同的要求。

此方法以及任何其他請求回撥必須返回一個可迭代的Request和dicts或Item物件。

  • 引數: 
    • response(Response) - 解析的響應

log(message[, level, component]) 
包裝器通過爬蟲傳送日誌訊息logger,保持向後相容性。有關詳細資訊,請參閱 從Spider記錄。

closed(reason) 
當爬蟲關閉時召喚。此方法為spider_closed訊號的signals.connect()提供了一個快捷方式。

讓我們看一個例子:

import scrapy


class MySpider(scrapy.Spider):
    name = 'example.com'
    allowed_domains = ['jianshu.com']
    start_urls = [
        'http://www.jianshu.com/p/cc8d69ed5601',
        'http://www.jianshu.com/p/41322417e0be',
        'http://www.jianshu.com/p/6e8b207cf833',
    ]

    def parse(self, response):
        self.logger.info('A response from %s just arrived!', response.url)

從單個回撥中返回多個請求和項:

import scrapy

class MySpider(scrapy.Spider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = [
        'http://www.example.com/1.html',
        'http://www.example.com/2.html',
        'http://www.example.com/3.html',
    ]

    def parse(self, response):
        for h3 in response.xpath('//h3').extract():
            yield {"title": h3}

        for url in response.xpath('//a/@href').extract():
            yield scrapy.Request(url, callback=self.parse)

你可以直接使用start_requests(),而不是start_urls; 專案可以更加方便獲取資料:

import scrapy
from myproject.items import MyItem

class MySpider(scrapy.Spider):
    name = 'example.com'
    allowed_domains = ['example.com']

    def start_requests(self):
        yield scrapy.Request('http://www.example.com/1.html', self.parse)
        yield scrapy.Request('http://www.example.com/2.html', self.parse)
        yield scrapy.Request('http://www.example.com/3.html', self.parse)

    def parse(self, response):
        for h3 in response.xpath('//h3').extract():
            yield MyItem(title=h3)

        for url in response.xpath('//a/@href').extract():
            yield scrapy.Request(url, callback=self.parse)

Spider arguments

爬蟲可以接收修改其行為的引數。爬蟲引數的一些常見用法是定義起始URL或將爬網限制到網站的某些部分,但它們可用於配置爬蟲的任何功能。

Spider crawl引數使用該-a選項通過命令 傳遞。例如:

scrapy crawl myspider -a category=electronics

爬蟲可以在他們的init方法中訪問引數:

import scrapy

class MySpider(scrapy.Spider):
    name = 'myspider'

    def __init__(self, category=None, *args, **kwargs):
        super(MySpider, self).__init__(*args, **kwargs)
        self.start_urls = ['http://www.example.com/categories/%s' % category]
        # ...

預設的init方法將獲取任何爬蟲引數,並將它們作為屬性複製到爬蟲。上面的例子也可以寫成如下:

import scrapy

class MySpider(scrapy.Spider):
    name = 'myspider'

    def start_requests(self):
        yield scrapy.Request('http://www.example.com/categories/%s' % self.category)

請記住,spider引數只是字串。爬蟲不會自己做任何解析。如果要從命令列設定start_urls屬性,則必須將它自己解析為列表,使用像 ast.literal_eval 或json.loads之類的屬性 ,然後將其設定為屬性。否則,你會導致迭代一個start_urls字串(一個非常常見的python陷阱),導致每個字元被看作一個單獨的url。

有效的用例是設定使用的http驗證憑據HttpAuthMiddleware 或使用者代理使用的使用者代理UserAgentMiddleware: 
scrapy crawl myspider -a http_user=myuser -a http_pass=mypassword -a user_agent=mybot

Spider引數也可以通過Scrapyd schedule.jsonAPI 傳遞。請參閱Scrapyd文件

通用爬蟲

Scrapy附帶一些有用的通用爬蟲,你可以使用它來子類化你的爬蟲。他們的目的是為一些常見的抓取案例提供方便的功能,例如根據某些規則檢視網站上的所有連結,從站點地圖抓取或解析XML / CSV Feed。

對於在以下爬蟲中使用的示例,我們假設您有一個TestItemmyproject.items模組中宣告的專案:

import scrapy

class TestItem(scrapy.Item):
    id = scrapy.Field()
    name = scrapy.Field()
    description = scrapy.Field()

CrawlSpider

類 scrapy.spiders.CrawlSpider 
這是最常用的爬行常規網站的爬蟲,因為它通過定義一組規則為下列連結提供了一種方便的機制。它可能不是最適合您的特定網站或專案,但它是足夠通用的幾種情況,所以你可以從它開始,根據需要覆蓋更多的自定義功能,或只是實現自己的爬蟲。

CrawlSpider這個爬蟲還暴露了可覆蓋的方法:

parse_start_url(response) 
對於start_urls響應呼叫此方法。它允許解析初始響應,並且必須返回Item物件,Request物件或包含任何物件的迭代器。

通過命令建立CrawlSpider

scrapy genspider -t crawl tencent tencent.com

LinkExtractors:

class scrapy.linkextractors.LinkExtractor

Link Extractors 的目的很簡單: 提取連結。

每個LinkExtractor有唯一的公共方法是 extract_links(),它接收一個 Response 物件,並返回一個 scrapy.link.Link 物件。

Link Extractors要例項化一次,並且 extract_links 方法會根據不同的 response 呼叫多次提取連結。

class scrapy.linkextractors.LinkExtractor(
    allow = (),
    deny = (),
    allow_domains = (),
    deny_domains = (),
    deny_extensions = None,
    restrict_xpaths = (),
    tags = ('a','area'),
    attrs = ('href'),
    canonicalize = True,
    unique = True,
    process_value = None
)

主要引數:

  • allow:滿足括號中“正則表示式”的值會被提取,如果為空,則全部匹配。
  • deny:與這個正則表示式(或正則表示式列表)不匹配的URL一定不提取。
  • allow_domains:會被提取的連結的domains。
  • deny_domains:一定不會被提取連結的domains。
  • restrict_xpaths:使用xpath表示式,和allow共同作用過濾連結。

除了從Spider繼承的屬性(你必須指定),這個類支援一個新的屬性:

Rules:

在rules中包含一個或多個Rule物件,每個Rule對爬取網站的動作定義了特定操作。如果多個rule匹配了相同的連結,則根據規則在本集合中被定義的順序,第一個會被使用。

Rule:抓取規則

class scrapy.spiders.Rule(link_extractor,callback = None,cb_kwargs = None,follow = None,process_links = None,process_request = None ) 
link_extractor是一個連結提取程式物件,它定義如何從每個爬網頁面提取連結。

class scrapy.spiders.Rule(
        link_extractor, 
        callback = None, 
        cb_kwargs = None, 
        follow = None, 
        process_links = None, 
        process_request = None
)
  • link_extractor:是一個Link Extractor物件,用於定義需要提取的連結。
  • callback: 從link_extractor中每獲取到連結時,引數所指定的值作為回撥函式,該回調函式接受一個response作為其第一個引數。注意:當編寫爬蟲規則時,避免使用parse作為回撥函式。由於CrawlSpider使用parse方法來實現其邏輯,如果覆蓋了 parse方法,crawl spider將會執行失敗。
  • cb_kwargs 是包含要傳遞給回撥函式的關鍵字引數的dict。
  • follow:是一個布林(boolean)值,指定了根據該規則從response提取的連結是否需要跟進。 如果callback為None,follow 預設設定為True ,否則預設為False。
  • process_links:指定該spider中哪個的函式將會被呼叫,從link_extractor中獲取到連結列表時將會呼叫該函式。該方法主要用來過濾。
  • process_request:指定該spider中哪個的函式將會被呼叫, 該規則提取到每個request時都會呼叫該函式。 (用來過濾request)

CrawlSpider抓取爬蟲示例

現在讓我們來看一個CrawlSpider的例子:

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

class MySpider(CrawlSpider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = ['http://www.example.com']

    rules = (
        # Extract links matching 'category.php' (but not matching 'subsection.php')
        # and follow links from them (since no callback means follow=True by default).
        Rule(LinkExtractor(allow=('category\.php', ), deny=('subsection\.php', ))),

        # Extract links matching 'item.php' and parse them with the spider's method parse_item
        Rule(LinkExtractor(allow=('item\.php', )), callback='parse_item'),
    )

    def parse_item(self, response):
        self.logger.info('Hi, this is an item page! %s', response.url)
        item = scrapy.Item()
        item['id'] = response.xpath('//td[@id="item_id"]/text()').re(r'ID: (\d+)')
        item['name'] = response.xpath('//td[@id="item_name"]/text()').extract()
        item['description'] = response.xpath('//td[@id="item_description"]/text()').extract()
        return item

這個爬蟲會開始抓取example.com的主頁,收集類別連結和項鍊接,用parse_item方法解析後者。對於每個專案響應,將使用XPath從HTML中提取一些資料,並將Item使用它填充。

XMLFeedSpider

class scrapy.spiders.XMLFeedSpider 
XMLFeedSpider設計用於通過以特定節點名稱迭代XML訂閱源來解析XML訂閱源。迭代器可以選自:iternodes,xml和html。iternodes為了效能原因,建議使用迭代器,因為xml和迭代器html一次生成整個DOM為了解析它。但是,html當使用壞標記解析XML時,使用作為迭代器可能很有用。

要設定迭代器和標記名稱,必須定義以下類屬性:

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

    • 'iternodes' - 基於正則表示式的快速迭代器
    • 'html'- 使用的迭代器Selector。請記住,這使用DOM解析,並且必須載入所有DOM在記憶體中,這可能是一個大飼料的問題
    • 'xml'- 使用的迭代器Selector。請記住,這使用DOM解析,並且必須載入所有DOM在記憶體中,這可能是一個大飼料的問題 
      它預設為:'iternodes'

itertag 
一個具有要迭代的節點(或元素)的名稱的字串。示​​例: 
itertag = 'product'

namespaces 
定義該文件中將使用此爬蟲處理的名稱空間的元組列表。在 與將用於自動註冊使用的名稱空間 的方法。(prefix, uri)prefixuriregister_namespace()

然後,您可以在屬性中指定具有名稱空間的itertag 節點。

例:

class YourSpider(XMLFeedSpider):

    namespaces = [('n', 'http://www.sitemaps.org/schemas/sitemap/0.9')]
    itertag = 'n:url'
    # ...

除了這些新的屬性,這個爬蟲也有以下可重寫的方法:

adapt_response(response) 
一種在爬蟲開始解析響應之前,在響應從爬蟲中介軟體到達時立即接收的方法。它可以用於在解析之前修改響應主體。此方法接收響應並返回響應(它可以是相同的或另一個)。

parse_node(response, selector) 
對於與提供的標記名稱(itertag)匹配的節點,將呼叫此方法。接收Selector每個節點的響應和 。覆蓋此方法是必需的。否則,你的爬蟲將不工作。此方法必須返回一個Item物件,一個 Request物件或包含任何物件的迭代器。

process_results(response, results) 
對於由爬蟲返回的每個結果(Items or Requests),將呼叫此方法,並且它將在將結果返回到框架核心之前執行所需的任何最後處理,例如設定專案ID。它接收結果列表和產生那些結果的響應。它必須返回結果列表(Items or Requests)。

XMLFeedSpider示例

這些爬蟲很容易使用,讓我們看一個例子:

from scrapy.spiders import XMLFeedSpider
from myproject.items import TestItem

class MySpider(XMLFeedSpider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = ['http://www.example.com/feed.xml']
    iterator = 'iternodes'  # This is actually unnecessary, since it's the default value
    itertag = 'item'

    def parse_node(self, response, node):
        self.logger.info('Hi, this is a <%s> node!: %s', self.itertag, ''.join(node.extract()))

        item = TestItem()
        item['id'] = node.xpath('@id').extract()
        item['name'] = node.xpath('name').extract()
        item['description'] = node.xpath('description').extract()
        return item

基本上我們做的是建立一個爬蟲,從給定的下載一個start_urls,然後遍歷每個item標籤,打印出來,並存儲一些隨機資料Item。

CSVFeedSpider

class scrapy.spiders.CSVF 
這個爬蟲非常類似於XMLFeedSpider,除了它迭代行,而不是節點。在每次迭代中呼叫的方法是parse_row()。

delimiter 
CSV檔案中每個欄位的帶分隔符的字串預設為’,’(逗號)。

quotechar 
CSV檔案中每個欄位的包含字元的字串預設為’”’(引號)。

headers 
檔案CSV Feed中包含的行的列表,用於從中提取欄位。

parse_row(response, row) 
使用CSV檔案的每個提供(或檢測到)標頭的鍵接收響應和dict(表示每行)。這個爬蟲還給予機會重寫adapt_response和process_results方法的前和後處理的目的。

CSVFeedSpider示例

讓我們看一個類似於前一個例子,但使用 CSVFeedSpider:

from scrapy.spiders import CSVFeedSpider
from myproject.items import TestItem

class MySpider(CSVFeedSpider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = ['http://www.example.com/feed.csv']
    delimiter = ';'
    quotechar = "'"
    headers = ['id', 'name', 'description']

    def parse_row(self, response, row):
        self.logger.info('Hi, this is a row!: %r', row)

        item = TestItem()
        item['id'] = row['id']
        item['name'] = row['name']
        item['description'] = row['description']
        return item

SitemapSpider

class scrapy.spiders.SitemapSpider 
SitemapSpider允許您通過使用Sitemaps發現網址來抓取網站

它支援巢狀Sitemap和從robots.txt發現Sitemap網址 。

sitemap_urls 
指向您要抓取的網址的網站的網址列表。

您還可以指向robots.txt,它會解析為從中提取Sitemap網址。

sitemap_rules 
元組列表其中:(regex, callback)

  • regex是與從Sitemap中提取的網址相匹配的正則表示式。 regex可以是一個str或一個編譯的正則表示式物件。
  • callback是用於處理與正則表示式匹配的url的回撥。callback可以是字串(指示蜘蛛方法的名稱)或可呼叫的。

例如: 
sitemap_rules = [('/product/', 'parse_product')]

規則按順序應用,只有匹配的第一個將被使用。 
如果省略此屬性,則會在parse回撥中處理在站點地圖中找到的所有網址。

sitemap_follow 
應遵循的網站地圖的正則表示式列表。這隻適用於使用指向其他Sitemap檔案的Sitemap索引檔案的網站。

預設情況下,將跟蹤所有網站地圖。

sitemap_alternate_links 
指定是否url應遵循一個備用連結。這些是在同一個url塊中傳遞的另一種語言的同一網站的連結。

例如:

<url>
    <loc>http://example.com/</loc>
    <xhtml:link rel="alternate" hreflang="de" href="http://example.com/de"/>
</url>

使用sitemap_alternate_linksset,這將檢索兩個URL。隨著 sitemap_alternate_links禁用,只有http://example.com/將進行檢索。

預設為sitemap_alternate_links禁用。

SitemapSpider示例

最簡單的示例:使用parse回撥處理通過站點地圖發現的所有網址 :

from scrapy.spiders import SitemapSpider

class MySpider(SitemapSpider):
    sitemap_urls = ['http://www.example.com/sitemap.xml']

    def parse(self, response):
        pass # ... scrape item here ...

使用某個回撥處理一些網址,並使用不同的回撥處理其他網址:

from scrapy.spiders import SitemapSpider

class MySpider(SitemapSpider):
    sitemap_urls = ['http://www.example.com/sitemap.xml']
    sitemap_rules = [
        ('/product/', 'parse_product'),
        ('/category/', 'parse_category'),
    ]

    def parse_product(self, response):
        pass # ... scrape product ...

    def parse_category(self, response):
        pass # ... scrape category ...

關注robots.txt檔案中定義的sitemaps,並且只跟蹤其網址包含/sitemap_shop以下內容的Sitemap :

from scrapy.spiders import SitemapSpider

class MySpider(SitemapSpider):
    sitemap_urls = ['http://www.example.com/robots.txt']
    sitemap_rules = [
        ('/shop/', 'parse_shop'),
    ]
    sitemap_follow = ['/sitemap_shops']

    def parse_shop(self, response):
        pass # ... scrape shop here ...

將SitemapSpider與其他來源網址結合使用:

from scrapy.spiders import SitemapSpider

class MySpider(SitemapSpider):
    sitemap_urls = ['http://www.example.com/robots.txt']
    sitemap_rules = [
        ('/shop/', 'parse_shop'),
    ]

    other_urls = ['http://www.example.com/about']

    def start_requests(self):
        requests = list(super(MySpider, self).start_requests())
        requests += [scrapy.Request(x, self.parse_other) for x in self.other_urls]
        return requests

    def parse_shop(self, response):
        pass # ... scrape shop here ...

    def parse_other(self, response):
        pass # ... scrape other here ...