Spiders
Spider類定義了如何爬取某個(或某些)網站。包括了爬取的動作(例如:是否跟進鏈接)以及如何從網頁的內容中提取結構化數據(爬取item)。 換句話說,Spider就是您定義爬取的動作及分析某個網頁(或者是有些網頁)的地方。
對spider來說,爬取的循環類似下文:
-
以初始的URL初始化Request,並設置回調函數。 當該request下載完畢並返回時,將生成response,並作為參數傳給該回調函數。
spider中初始的request是通過調用
start_requests()
來獲取的。start_requests()
讀取start_urls
中的URL, 並以parse
為回調函數生成Request
。 -
在回調函數內分析返回的(網頁)內容,返回
Item
對象或者Request
或者一個包括二者的可迭代容器。 返回的Request對象之后會經過Scrapy處理,下載相應的內容,並調用設置的callback函數(函數可相同)。 -
在回調函數內,您可以使用 選擇器(Selectors) (您也可以使用BeautifulSoup, lxml 或者您想用的任何解析器) 來分析網頁內容,並根據分析的數據生成item。
-
最后,由spider返回的item將被存到數據庫(由某些 Item Pipeline 處理)或使用 Feed exports 存入到文件中。
雖然該循環對任何類型的spider都(多少)適用,但Scrapy仍然為了不同的需求提供了多種默認spider。 之后將討論這些spider。
Spider參數
Spider可以通過接受參數來修改其功能。 spider參數一般用來定義初始URL或者指定限制爬取網站的部分。 您也可以使用其來配置spider的任何功能。
在運行 crawl 時添加 -a
可以傳遞Spider參數:
scrapy crawl myspider -a category=electronics
Spider在構造器(constructor)中獲取參數:
import scrapy class MySpider(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] # ...
Spider參數也可以通過Scrapyd的 schedule.json
API來傳遞。 參見 Scrapyd documentation.
內置Spider參考手冊
Scrapy提供多種方便的通用spider供您繼承使用。 這些spider為一些常用的爬取情況提供方便的特性, 例如根據某些規則跟進某個網站的所有鏈接、根據 Sitemaps 來進行爬取,或者分析XML/CSV源。
下面spider的示例中,我們假定您有個項目在 myproject.items
模塊中聲明了 TestItem
:
import scrapy class TestItem(scrapy.Item): id = scrapy.Field() name = scrapy.Field() description = scrapy.Field()
Spider
-
class
scrapy.spider.
Spider
-
Spider是最簡單的spider。每個其他的spider必須繼承自該類(包括Scrapy自帶的其他spider以及您自己編寫的spider)。 Spider並沒有提供什么特殊的功能。 其僅僅請求給定的
start_urls
/start_requests
,並根據返回的結果(resulting responses)調用spider的parse
方法。-
name
-
定義spider名字的字符串(string)。spider的名字定義了Scrapy如何定位(並初始化)spider,所以其必須是唯一的。 不過您可以生成多個相同的spider實例(instance),這沒有任何限制。 name是spider最重要的屬性,而且是必須的。
如果該spider爬取單個網站(single domain),一個常見的做法是以該網站(domain)(加或不加后綴 )來命名spider。 例如,如果spider爬取
mywebsite.com
,該spider通常會被命名為mywebsite
。
-
allowed_domains
-
可選。包含了spider允許爬取的域名(domain)列表(list)。 當
OffsiteMiddleware
啟用時, 域名不在列表中的URL不會被跟進。
-
start_urls
-
URL列表。當沒有制定特定的URL時,spider將從該列表中開始進行爬取。 因此,第一個被獲取到的頁面的URL將是該列表之一。 后續的URL將會從獲取到的數據中提取。
-
crawler
-
This attribute is set by the
from_crawler()
class method after initializating the class, and links to theCrawler
object to which this spider instance is bound.Crawlers encapsulate a lot of components in the project for their single entry access (such as extensions, middlewares, signals managers, etc). See Crawler API to know more about them.
-
settings
-
Configuration on which this spider is been ran. This is a
Settings
instance, see the Settingstopic for a detailed introduction on this subject.
-
from_crawler
(crawler, *args, **kwargs) -
This is the class method used by Scrapy to create your spiders.
You probably won’t need to override this directly, since the default implementation acts as a proxy to the
__init__()
method, calling it with the given arguments args and named arguments kwargs.Nonetheless, this method sets the
crawler
andsettings
attributes in the new instance, so they can be accessed later inside the spider’s code.參數:
-
-
start_requests
() -
該方法必須返回一個可迭代對象(iterable)。該對象包含了spider用於爬取的第一個Request。
當spider啟動爬取並且未制定URL時,該方法被調用。 當指定了URL時,
make_requests_from_url()
將被調用來創建Request對象。 該方法僅僅會被Scrapy調用一次,因此您可以將其實現為生成器。該方法的默認實現是使用
start_urls
的url生成Request。如果您想要修改最初爬取某個網站的Request對象,您可以重寫(override)該方法。 例如,如果您需要在啟動時以POST登錄某個網站,你可以這么寫:
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為request。默認未被復寫(overridden)的情況下,該方法返回的Request對象中,
parse()
作為回調函數,dont_filter參數也被設置為開啟。 (詳情參見Request
).
-
parse
(response) -
當response沒有指定回調函數時,該方法是Scrapy處理下載的response的默認方法。
parse
負責處理response並返回處理的數據以及(/或)跟進的URL。Spider
對其他的Request的回調函數也有相同的要求。該方法及其他的Request回調函數必須返回一個包含
Request
及(或)Item
的可迭代的對象。參數: response ( Response
) – 用於分析的response
-
log
(message[, level, component]) -
使用
scrapy.log.msg()
方法記錄(log)message。 log中自動帶上該spider的name
屬性。 更多數據請參見 Logging 。
-
closed
(reason) -
當spider關閉時,該函數被調用。 該方法提供了一個替代調用signals.connect()來監聽:signal:`spider_closed` 信號的快捷方式。
Spider樣例
讓我們來看一個例子:
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): self.log('A response from %s just arrived!' % response.url)
另一個在單個回調函數中返回多個Request以及Item的例子:
import scrapy from myproject.items import MyItem 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): sel = scrapy.Selector(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)
CrawlSpider
-
class
scrapy.contrib.spiders.
CrawlSpider
-
爬取一般網站常用的spider。其定義了一些規則(rule)來提供跟進link的方便的機制。 也許該spider並不是完全適合您的特定網站或項目,但其對很多情況都使用。 因此您可以以其為起點,根據需求修改部分方法。當然您也可以實現自己的spider。
除了從Spider繼承過來的(您必須提供的)屬性外,其提供了一個新的屬性:
-
rules
-
一個包含一個(或多個)
Rule
對象的集合(list)。 每個Rule
對爬取網站的動作定義了特定表現。 Rule對象在下邊會介紹。 如果多個rule匹配了相同的鏈接,則根據他們在本屬性中被定義的順序,第一個會被使用。
該spider也提供了一個可復寫(overrideable)的方法:
-
爬取規則(Crawling rules)
-
class
scrapy.contrib.spiders.
Rule
(link_extractor, callback=None, cb_kwargs=None, follow=None,process_links=None, process_request=None) -
link_extractor
是一個 Link Extractor 對象。 其定義了如何從爬取到的頁面提取鏈接。callback
是一個callable或string(該spider中同名的函數將會被調用)。 從link_extractor中每獲取到鏈接時將會調用該函數。該回調函數接受一個response作為其第一個參數, 並返回一個包含Item
以及(或)Request
對象(或者這兩者的子類)的列表(list)。警告
當編寫爬蟲規則時,請避免使用
parse
作為回調函數。 由於CrawlSpider
使用parse
方法來實現其邏輯,如果 您覆蓋了parse
方法,crawl spider 將會運行失敗。cb_kwargs
包含傳遞給回調函數的參數(keyword argument)的字典。follow
是一個布爾(boolean)值,指定了根據該規則從response提取的鏈接是否需要跟進。 如果callback
為None,follow
默認設置為True
,否則默認為False
。process_links
是一個callable或string(該spider中同名的函數將會被調用)。 從link_extractor中獲取到鏈接列表時將會調用該函數。該方法主要用來過濾。process_request
是一個callable或string(該spider中同名的函數將會被調用)。 該規則提取到每個request時都會調用該函數。該函數必須返回一個request或者None。 (用來過濾request)
CrawlSpider樣例
接下來給出配合rule使用CrawlSpider的例子:
import scrapy from scrapy.contrib.spiders import CrawlSpider, Rule from scrapy.contrib.linkextractors import LinkExtractor class MySpider(CrawlSpider): name = 'example.com' allowed_domains = ['example.com'] start_urls = ['http://www.example.com'] rules = ( # 提取匹配 'category.php' (但不匹配 'subsection.php') 的鏈接並跟進鏈接(沒有callback意味着follow默認為True) Rule(LinkExtractor(allow=('category\.php', ), deny=('subsection\.php', ))), # 提取匹配 'item.php' 的鏈接並使用spider的parse_item方法進行分析 Rule(LinkExtractor(allow=('item\.php', )), callback='parse_item'), ) def parse_item(self, response): self.log('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
該spider將從example.com的首頁開始爬取,獲取category以及item的鏈接並對后者使用 parse_item
方法。 當item獲得返回(response)時,將使用XPath處理HTML並生成一些數據填入 Item
中。
XMLFeedSpider
-
class
scrapy.contrib.spiders.
XMLFeedSpider
-
XMLFeedSpider被設計用於通過迭代各個節點來分析XML源(XML feed)。 迭代器可以從
iternodes
,xml
,html
選擇。 鑒於xml
以及html
迭代器需要先讀取所有DOM再分析而引起的性能問題, 一般還是推薦使用iternodes
。 不過使用html
作為迭代器能有效應對錯誤的XML。您必須定義下列類屬性來設置迭代器以及標簽名(tag name):
-
iterator
-
用於確定使用哪個迭代器的string。可選項有:
默認值為
iternodes
。
-
itertag
-
一個包含開始迭代的節點名的string。例如:
itertag = 'product'
-
namespaces
-
一個由
(prefix, url)
元組(tuple)所組成的list。 其定義了在該文檔中會被spider處理的可用的namespace。prefix
及uri
會被自動調用register_namespace()
生成namespace。您可以通過在
itertag
屬性中制定節點的namespace。例如:
class YourSpider(XMLFeedSpider): namespaces = [('n', 'http://www.sitemaps.org/schemas/sitemap/0.9')] itertag = 'n:url' # ...
除了這些新的屬性之外,該spider也有以下可以覆蓋(overrideable)的方法:
-
adapt_response
(response) -
該方法在spider分析response前被調用。您可以在response被分析之前使用該函數來修改內容(body)。 該方法接受一個response並返回一個response(可以相同也可以不同)。
-
parse_node
(response, selector) -
當節點符合提供的標簽名時(
itertag
)該方法被調用。 接收到的response以及相應的Selector
作為參數傳遞給該方法。 該方法返回一個Item
對象或者Request
對象 或者一個包含二者的可迭代對象(iterable)。
-
process_results
(response, results) -
當spider返回結果(item或request)時該方法被調用。 設定該方法的目的是在結果返回給框架核心(framework core)之前做最后的處理, 例如設定item的ID。其接受一個結果的列表(list of results)及對應的response。 其結果必須返回一個結果的列表(list of results)(包含Item或者Request對象)。
-
XMLFeedSpider例子
該spider十分易用。下邊是其中一個例子:
from scrapy import log from scrapy.contrib.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): log.msg('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
簡單來說,我們在這里創建了一個spider,從給定的 start_urls
中下載feed, 並迭代feed中每個item
標簽,輸出,並在 Item
中存儲有些隨機數據。
CSVFeedSpider
-
class
scrapy.contrib.spiders.
CSVFeedSpider
-
該spider除了其按行遍歷而不是節點之外其他和XMLFeedSpider十分類似。 而其在每次迭代時調用的是
parse_row()
。-
delimiter
-
在CSV文件中用於區分字段的分隔符。類型為string。 默認為
','
(逗號)。
-
headers
-
在CSV文件中包含的用來提取字段的行的列表。參考下邊的例子。
-
parse_row
(response, row) -
該方法接收一個response對象及一個以提供或檢測出來的header為鍵的字典(代表每行)。 該spider中,您也可以覆蓋
adapt_response
及process_results
方法來進行預處理(pre-processing)及后(post-processing)處理。
-
CSVFeedSpider例子
下面的例子和之前的例子很像,但使用了 CSVFeedSpider
:
from scrapy import log from scrapy.contrib.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 = ';' headers = ['id', 'name', 'description'] def parse_row(self, response, row): log.msg('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.contrib.spiders.
SitemapSpider
-
SitemapSpider使您爬取網站時可以通過 Sitemaps 來發現爬取的URL。
其支持嵌套的sitemap,並能從 robots.txt 中獲取sitemap的url。
-
sitemap_urls
-
包含您要爬取的url的sitemap的url列表(list)。 您也可以指定為一個 robots.txt ,spider會從中分析並提取url。
-
sitemap_rules
-
一個包含
(regex, callback)
元組的列表(list):regex
是一個用於匹配從sitemap提供的url的正則表達式。regex
可以是一個字符串或者編譯的正則對象(compiled regex object)。- callback指定了匹配正則表達式的url的處理函數。
callback
可以是一個字符串(spider中方法的名字)或者是callable。
例如:
sitemap_rules = [('/product/', 'parse_product')]
規則按順序進行匹配,之后第一個匹配才會被應用。
如果您忽略該屬性,sitemap中發現的所有url將會被
parse
函數處理。
-
sitemap_follow
-
一個用於匹配要跟進的sitemap的正則表達式的列表(list)。其僅僅被應用在 使用 Sitemap index files 來指向其他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_links
設置時,兩個URL都會被獲取。 當sitemap_alternate_links
關閉時,只有http://example.com/
會被獲取。默認
sitemap_alternate_links
關閉。
-
SitemapSpider樣例
簡單的例子: 使用 parse
處理通過sitemap發現的所有url:
from scrapy.contrib.spiders import SitemapSpider class MySpider(SitemapSpider): sitemap_urls = ['http://www.example.com/sitemap.xml'] def parse(self, response): pass # ... scrape item here ...
用特定的函數處理某些url,其他的使用另外的callback:
from scrapy.contrib.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 文件定義的sitemap並只跟進包含有 ..sitemap_shop
的url:
from scrapy.contrib.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中使用其他url:
from scrapy.contrib.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 ...