Scrapy学习笔记5——Spiders
Spiders
Spider類定義了如何爬取某個(gè)(或某些)網(wǎng)站。包括了爬取的動(dòng)作(例如:是否跟進(jìn)鏈接)以及如何從網(wǎng)頁的內(nèi)容中提取結(jié)構(gòu)化數(shù)據(jù)(爬取item)。 換句話說,Spider就是您定義爬取的動(dòng)作及分析某個(gè)網(wǎng)頁(或者是有些網(wǎng)頁)的地方。
對(duì)spider來說,爬取的循環(huán)類似下文:
以初始的URL初始化Request,并設(shè)置回調(diào)函數(shù)。 當(dāng)該request下載完畢并返回時(shí),將生成response,并作為參數(shù)傳給該回調(diào)函數(shù)。
spider中初始的request是通過調(diào)用?start_requests()?來獲取的。?start_requests()?讀取?start_urls?中的URL, 并以?parse?為回調(diào)函數(shù)生成?Request?。
在回調(diào)函數(shù)內(nèi)分析返回的(網(wǎng)頁)內(nèi)容,返回?Item?對(duì)象、dict、?Request?或者一個(gè)包括三者的可迭代容器。 返回的Request對(duì)象之后會(huì)經(jīng)過Scrapy處理,下載相應(yīng)的內(nèi)容,并調(diào)用設(shè)置的callback函數(shù)(函數(shù)可相同)。
在回調(diào)函數(shù)內(nèi),您可以使用?選擇器(Selectors)?(您也可以使用BeautifulSoup, lxml 或者您想用的任何解析器) 來分析網(wǎng)頁內(nèi)容,并根據(jù)分析的數(shù)據(jù)生成item。
最后,由spider返回的item將被存到數(shù)據(jù)庫(由某些?Item Pipeline?處理)或使用?Feed exports?存入到文件中。
雖然該循環(huán)對(duì)任何類型的spider都(多少)適用,但Scrapy仍然為了不同的需求提供了多種默認(rèn)spider。 之后將討論這些spider。
scrapy.Spider
classscrapy.spiders.SpiderSpider是最簡(jiǎn)單的spider。每個(gè)其他的spider必須繼承自該類(包括Scrapy自帶的其他spider以及您自己編寫的spider)。 Spider并沒有提供什么特殊的功能。 其僅僅提供了?start_requests()?的默認(rèn)實(shí)現(xiàn),讀取并請(qǐng)求spider屬性中的?start_urls, 并根據(jù)返回的結(jié)果(resulting responses)調(diào)用spider的?parse?方法。
name定義spider名字的字符串(string)。spider的名字定義了Scrapy如何定位(并初始化)spider,所以其必須是唯一的。 不過您可以生成多個(gè)相同的spider實(shí)例(instance),這沒有任何限制。 name是spider最重要的屬性,而且是必須的。
如果該spider爬取單個(gè)網(wǎng)站(single domain),一個(gè)常見的做法是以該網(wǎng)站(domain)(加或不加?后綴?)來命名spider。 例如,如果spider爬取?mywebsite.com?,該spider通常會(huì)被命名為?mywebsite?。
可選。包含了spider允許爬取的域名(domain)列表(list)。 當(dāng)?OffsiteMiddleware?啟用時(shí), 域名不在列表中的URL不會(huì)被跟進(jìn)。
URL列表。當(dāng)沒有制定特定的URL時(shí),spider將從該列表中開始進(jìn)行爬取。 因此,第一個(gè)被獲取到的頁面的URL將是該列表之一。 后續(xù)的URL將會(huì)從獲取到的數(shù)據(jù)中提取。
該設(shè)置是一個(gè)dict.當(dāng)啟動(dòng)spider時(shí),該設(shè)置將會(huì)覆蓋項(xiàng)目級(jí)的設(shè)置. 由于設(shè)置必須在初始化(instantiation)前被更新,所以該屬性 必須定義為class屬性.
請(qǐng)通過?內(nèi)置設(shè)定參考手冊(cè)?查看支持的設(shè)置.
該屬性在初始化class后,由類方法?from_crawler()?設(shè)置, 并且鏈接了本spider實(shí)例對(duì)應(yīng)的Crawler?對(duì)象.
Crawler包含了很多項(xiàng)目中的組件,作為單一的入口點(diǎn) (例如插件,中間件,信號(hào)管理器等). 請(qǐng)查看?Crawler API?來了解更多.
Configuration on which this spider is been ran. This is a?Settings?instance, see the?Settingstopic for a detailed introduction on this subject.
Python logger created with the Spider’s?name. You can use it to send log messages through it as described on?Logging from Spiders.
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?and?settings?attributes in the new instance, so they can be accessed later inside the spider’s code.
|
該方法必須返回一個(gè)可迭代對(duì)象(iterable)。該對(duì)象包含了spider用于爬取的第一個(gè)Request。
當(dāng)spider啟動(dòng)爬取并且未制定URL時(shí),該方法被調(diào)用。 當(dāng)指定了URL時(shí),make_requests_from_url()?將被調(diào)用來創(chuàng)建Request對(duì)象。 該方法僅僅會(huì)被Scrapy調(diào)用一次,因此您可以將其實(shí)現(xiàn)為生成器。
該方法的默認(rèn)實(shí)現(xiàn)是使用?start_urls?的url生成Request。
如果您想要修改最初爬取某個(gè)網(wǎng)站的Request對(duì)象,您可以重寫(override)該方法。 例如,如果您需要在啟動(dòng)時(shí)以POST登錄某個(gè)網(wǎng)站,你可以這么寫:
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 callbackpass該方法接受一個(gè)URL并返回用于爬取的?Request?對(duì)象。 該方法在初始化request時(shí)被?start_requests()?調(diào)用,也被用于轉(zhuǎn)化url為request。
默認(rèn)未被復(fù)寫(overridden)的情況下,該方法返回的Request對(duì)象中,?parse()?作為回調(diào)函數(shù),dont_filter參數(shù)也被設(shè)置為開啟。 (詳情參見?Request).
當(dāng)response沒有指定回調(diào)函數(shù)時(shí),該方法是Scrapy處理下載的response的默認(rèn)方法。
parse?負(fù)責(zé)處理response并返回處理的數(shù)據(jù)以及(/或)跟進(jìn)的URL。?Spider?對(duì)其他的Request的回調(diào)函數(shù)也有相同的要求。
該方法及其他的Request回調(diào)函數(shù)必須返回一個(gè)包含?Request、dict 或?Item?的可迭代的對(duì)象。
| response?(Response) – 用于分析的response |
使用?scrapy.log.msg()?方法記錄(log)message。 log中自動(dòng)帶上該spider的?name?屬性。 更多數(shù)據(jù)請(qǐng)參見?Logging?。 封裝了通過Spiders的?logger?來發(fā)送log消息的方法,并且保持了向后兼容性。 更多內(nèi)容請(qǐng)參考?Logging from Spiders.
當(dāng)spider關(guān)閉時(shí),該函數(shù)被調(diào)用。 該方法提供了一個(gè)替代調(diào)用signals.connect()來監(jiān)聽?spider_closed?信號(hào)的快捷方式。
讓我們來看一個(gè)例子:
import scrapyclass 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.logger.info('A response from %s just arrived!', response.url)在單個(gè)回調(diào)函數(shù)中返回多個(gè)Request以及Item的例子:
import scrapyclass 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 {"title": h3}for url in response.xpath('//a/@href').extract():yield scrapy.Request(url, callback=self.parse)除了?start_urls?,你也可以直接使用?start_requests()?; 您也可以使用?Items?來給予數(shù)據(jù)更多的結(jié)構(gòu)性(give data more structure):
import scrapy from myproject.items import MyItemclass 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
Spiders can receive arguments that modify their behaviour. Some common uses for spider arguments are to define the start URLs or to restrict the crawl to certain sections of the site, but they can be used to configure any functionality of the spider.
Spider arguments are passed through the?crawl?command using the?-a?option. For example:
scrapy crawl myspider -a category=electronicsSpiders receive arguments in their constructors:
import scrapyclass 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]# ...Spider arguments can also be passed through the Scrapyd?schedule.json?API. See?Scrapyd documentation.
Generic Spiders
Scrapy comes with some useful generic spiders that you can use, to subclass your spiders from. Their aim is to provide convenient functionality for a few common scraping cases, like following all links on a site based on certain rules, crawling from?Sitemaps, or parsing a XML/CSV feed.
For the examples used in the following spiders, we’ll assume you have a project with a?TestItemdeclared in a?myproject.items?module:
import scrapyclass TestItem(scrapy.Item):id = scrapy.Field()name = scrapy.Field()description = scrapy.Field()CrawlSpider
classscrapy.spiders.CrawlSpider爬取一般網(wǎng)站常用的spider。其定義了一些規(guī)則(rule)來提供跟進(jìn)link的方便的機(jī)制。 也許該spider并不是完全適合您的特定網(wǎng)站或項(xiàng)目,但其對(duì)很多情況都使用。 因此您可以以其為起點(diǎn),根據(jù)需求修改部分方法。當(dāng)然您也可以實(shí)現(xiàn)自己的spider。
除了從Spider繼承過來的(您必須提供的)屬性外,其提供了一個(gè)新的屬性:
rules一個(gè)包含一個(gè)(或多個(gè))?Rule?對(duì)象的集合(list)。 每個(gè)?Rule?對(duì)爬取網(wǎng)站的動(dòng)作定義了特定表現(xiàn)。 Rule對(duì)象在下邊會(huì)介紹。 如果多個(gè)rule匹配了相同的鏈接,則根據(jù)他們?cè)诒緦傩灾斜欢x的順序,第一個(gè)會(huì)被使用。
該spider也提供了一個(gè)可復(fù)寫(overrideable)的方法:
parse_start_url(response)當(dāng)start_url的請(qǐng)求返回時(shí),該方法被調(diào)用。 該方法分析最初的返回值并必須返回一個(gè)?Item對(duì)象或者 一個(gè)?Request?對(duì)象或者 一個(gè)可迭代的包含二者對(duì)象。
爬取規(guī)則(Crawling rules)
classscrapy.spiders.Rule(link_extractor,?callback=None,?cb_kwargs=None,?follow=None,?process_links=None,?process_request=None)link_extractor?是一個(gè)?Link Extractor?對(duì)象。 其定義了如何從爬取到的頁面提取鏈接。
callback?是一個(gè)callable或string(該spider中同名的函數(shù)將會(huì)被調(diào)用)。 從link_extractor中每獲取到鏈接時(shí)將會(huì)調(diào)用該函數(shù)。該回調(diào)函數(shù)接受一個(gè)response作為其第一個(gè)參數(shù), 并返回一個(gè)包含?Item?以及(或)?Request?對(duì)象(或者這兩者的子類)的列表(list)。
警告
當(dāng)編寫爬蟲規(guī)則時(shí),請(qǐng)避免使用?parse?作為回調(diào)函數(shù)。 由于?CrawlSpider?使用?parse?方法來實(shí)現(xiàn)其邏輯,如果 您覆蓋了?parse?方法,crawl spider 將會(huì)運(yùn)行失敗。
cb_kwargs?包含傳遞給回調(diào)函數(shù)的參數(shù)(keyword argument)的字典。
follow?是一個(gè)布爾(boolean)值,指定了根據(jù)該規(guī)則從response提取的鏈接是否需要跟進(jìn)。 如果?callback?為None,?follow?默認(rèn)設(shè)置為?True?,否則默認(rèn)為?False?。
process_links?是一個(gè)callable或string(該spider中同名的函數(shù)將會(huì)被調(diào)用)。 從link_extractor中獲取到鏈接列表時(shí)將會(huì)調(diào)用該函數(shù)。該方法主要用來過濾。
process_request?是一個(gè)callable或string(該spider中同名的函數(shù)將會(huì)被調(diào)用)。 該規(guī)則提取到每個(gè)request時(shí)都會(huì)調(diào)用該函數(shù)。該函數(shù)必須返回一個(gè)request或者None。 (用來過濾request)
CrawlSpider樣例
接下來給出配合rule使用CrawlSpider的例子:
import scrapy from scrapy.spiders import CrawlSpider, Rule from scrapy.linkextractors import LinkExtractorclass MySpider(CrawlSpider):name = 'example.com'allowed_domains = ['example.com']start_urls = ['http://www.example.com']rules = (# 提取匹配 'category.php' (但不匹配 'subsection.php') 的鏈接并跟進(jìn)鏈接(沒有callback意味著follow默認(rèn)為True)Rule(LinkExtractor(allow=('category\.php', ), deny=('subsection\.php', ))),# 提取匹配 'item.php' 的鏈接并使用spider的parse_item方法進(jìn)行分析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該spider將從example.com的首頁開始爬取,獲取category以及item的鏈接并對(duì)后者使用?parse_item方法。 當(dāng)item獲得返回(response)時(shí),將使用XPath處理HTML并生成一些數(shù)據(jù)填入?Item?中。
XMLFeedSpider
classscrapy.spiders.XMLFeedSpiderXMLFeedSpider被設(shè)計(jì)用于通過迭代各個(gè)節(jié)點(diǎn)來分析XML源(XML feed)。 迭代器可以從?iternodes?,?xml?,?html?選擇。 鑒于?xml?以及?html?迭代器需要先讀取所有DOM再分析而引起的性能問題, 一般還是推薦使用?iternodes?。 不過使用?html?作為迭代器能有效應(yīng)對(duì)錯(cuò)誤的XML。
您必須定義下列類屬性來設(shè)置迭代器以及標(biāo)簽名(tag name):
iterator用于確定使用哪個(gè)迭代器的string。可選項(xiàng)有:
- 'iternodes'?- 一個(gè)高性能的基于正則表達(dá)式的迭代器
- 'html'?- 使用?Selector?的迭代器。 需要注意的是該迭代器使用DOM進(jìn)行分析,其需要將所有的DOM載入內(nèi)存, 當(dāng)數(shù)據(jù)量大的時(shí)候會(huì)產(chǎn)生問題。
- 'xml'?- 使用?Selector?的迭代器。 需要注意的是該迭代器使用DOM進(jìn)行分析,其需要將所有的DOM載入內(nèi)存, 當(dāng)數(shù)據(jù)量大的時(shí)候會(huì)產(chǎn)生問題。
默認(rèn)值為?iternodes?。
一個(gè)包含開始迭代的節(jié)點(diǎn)名的string。例如:
itertag = 'product'一個(gè)由?(prefix,?url)?元組(tuple)所組成的list。 其定義了在該文檔中會(huì)被spider處理的可用的namespace。?prefix?及?uri?會(huì)被自動(dòng)調(diào)用?register_namespace()?生成namespace。
您可以通過在?itertag?屬性中制定節(jié)點(diǎn)的namespace。
例如:
class YourSpider(XMLFeedSpider):namespaces = [('n', 'http://www.sitemaps.org/schemas/sitemap/0.9')]itertag = 'n:url'# ...除了這些新的屬性之外,該spider也有以下可以覆蓋(overrideable)的方法:
adapt_response(response)該方法在spider分析response前被調(diào)用。您可以在response被分析之前使用該函數(shù)來修改內(nèi)容(body)。 該方法接受一個(gè)response并返回一個(gè)response(可以相同也可以不同)。
當(dāng)節(jié)點(diǎn)符合提供的標(biāo)簽名時(shí)(itertag)該方法被調(diào)用。 接收到的response以及相應(yīng)的?Selector?作為參數(shù)傳遞給該方法。 該方法返回一個(gè)?Item?對(duì)象或者?Request?對(duì)象 或者一個(gè)包含二者的可迭代對(duì)象(iterable)。
當(dāng)spider返回結(jié)果(item或request)時(shí)該方法被調(diào)用。 設(shè)定該方法的目的是在結(jié)果返回給框架核心(framework core)之前做最后的處理, 例如設(shè)定item的ID。其接受一個(gè)結(jié)果的列表(list of results)及對(duì)應(yīng)的response。 其結(jié)果必須返回一個(gè)結(jié)果的列表(list of results)(包含Item或者Request對(duì)象)。
XMLFeedSpider例子
該spider十分易用。下邊是其中一個(gè)例子:
from scrapy.spiders import XMLFeedSpider from myproject.items import TestItemclass 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 valueitertag = '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簡(jiǎn)單來說,我們?cè)谶@里創(chuàng)建了一個(gè)spider,從給定的?start_urls?中下載feed, 并迭代feed中每個(gè)?item?標(biāo)簽,輸出,并在?Item?中存儲(chǔ)有些隨機(jī)數(shù)據(jù)。
CSVFeedSpider
classscrapy.spiders.CSVFeedSpider該spider除了其按行遍歷而不是節(jié)點(diǎn)之外其他和XMLFeedSpider十分類似。 而其在每次迭代時(shí)調(diào)用的是?parse_row()?。
delimiter在CSV文件中用于區(qū)分字段的分隔符。類型為string。 默認(rèn)為?','?(逗號(hào))。
A string with the enclosure character for each field in the CSV file Defaults to?'"'(quotation mark).
在CSV文件中包含的用來提取字段的行的列表。參考下邊的例子。
該方法接收一個(gè)response對(duì)象及一個(gè)以提供或檢測(cè)出來的header為鍵的字典(代表每行)。 該spider中,您也可以覆蓋?adapt_response?及?process_results?方法來進(jìn)行預(yù)處理(pre-processing)及后(post-processing)處理。
CSVFeedSpider例子
下面的例子和之前的例子很像,但使用了?CSVFeedSpider:
from scrapy.spiders import CSVFeedSpider from myproject.items import TestItemclass 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 itemSitemapSpider
classscrapy.spiders.SitemapSpiderSitemapSpider使您爬取網(wǎng)站時(shí)可以通過?Sitemaps?來發(fā)現(xiàn)爬取的URL。
其支持嵌套的sitemap,并能從?robots.txt?中獲取sitemap的url。
sitemap_urls包含您要爬取的url的sitemap的url列表(list)。 您也可以指定為一個(gè)?robots.txt?,spider會(huì)從中分析并提取url。
一個(gè)包含?(regex,?callback)?元組的列表(list):
- regex?是一個(gè)用于匹配從sitemap提供的url的正則表達(dá)式。?regex?可以是一個(gè)字符串或者編譯的正則對(duì)象(compiled regex object)。
- callback指定了匹配正則表達(dá)式的url的處理函數(shù)。?callback?可以是一個(gè)字符串(spider中方法的名字)或者是callable。
例如:
sitemap_rules = [('/product/', 'parse_product')]規(guī)則按順序進(jìn)行匹配,之后第一個(gè)匹配才會(huì)被應(yīng)用。
如果您忽略該屬性,sitemap中發(fā)現(xiàn)的所有url將會(huì)被?parse?函數(shù)處理。
一個(gè)用于匹配要跟進(jìn)的sitemap的正則表達(dá)式的列表(list)。其僅僅被應(yīng)用在 使用?Sitemap index files?來指向其他sitemap文件的站點(diǎn)。
默認(rèn)情況下所有的sitemap都會(huì)被跟進(jìn)。
指定當(dāng)一個(gè)?url?有可選的鏈接時(shí),是否跟進(jìn)。 有些非英文網(wǎng)站會(huì)在一個(gè)?url?塊內(nèi)提供其他語言的網(wǎng)站鏈接。
例如:
<url><loc>http://example.com/</loc><xhtml:link rel="alternate" hreflang="de" href="http://example.com/de"/> </url>當(dāng)?sitemap_alternate_links?設(shè)置時(shí),兩個(gè)URL都會(huì)被獲取。 當(dāng)?sitemap_alternate_links?關(guān)閉時(shí),只有?http://example.com/?會(huì)被獲取。
默認(rèn)?sitemap_alternate_links?關(guān)閉。
SitemapSpider樣例
簡(jiǎn)單的例子: 使用?parse?處理通過sitemap發(fā)現(xiàn)的所有url:
from scrapy.spiders import SitemapSpiderclass MySpider(SitemapSpider):sitemap_urls = ['http://www.example.com/sitemap.xml']def parse(self, response):pass # ... scrape item here ...用特定的函數(shù)處理某些url,其他的使用另外的callback:
from scrapy.spiders import SitemapSpiderclass 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 ...跟進(jìn)?robots.txt?文件定義的sitemap并只跟進(jìn)包含有?..sitemap_shop?的url:
from scrapy.spiders import SitemapSpiderclass 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.spiders import SitemapSpiderclass 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 requestsdef parse_shop(self, response):pass # ... scrape shop here ...def parse_other(self, response):pass # ... scrape other here ...總結(jié)
以上是生活随笔為你收集整理的Scrapy学习笔记5——Spiders的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 正则表达式(只能操作字符串类型)
- 下一篇: PHP OOP(面向对象)进阶知识之“T