爬虫器¶
Spiders 类定义了如何爬取一个站点(或者一组站点),包括如何执行抓取(即跟踪链接)以及如何从爬取 的页面中提取结构化的数据(即爬取 items)。换句话说,Spiders 字义了爬取和解析特定站点(或者一组站点)的行为。
对于爬虫器来说,抓取的过程类似这样运行的:
从放入的一批初始 url 开始抓取,然后指定一个回调函数来处理这个(这些)请求的响应。
第一批请求是调用
start_requests()方法,该方法(默认) 为start_urls中指定的 URLS 生成Request类, 并且parse方法作为这些请求的回调函数。在回调函数中,你解析了响应(网页)并返回了提取的(字典化)的数据、
Item对象、 或者这些对象的迭代器其中之王。这些请求也可以包含一个回调(也许是一个回调)并被 Scrapy 下载然后它们的响应被指定的 回调函数处理。在回调函数中, 你解析了页面内容, 主要用了 选择器 (但是你可以用BeautifulSoup、lxml或者你喜欢的 其它方法) 并用这些解析的数据生成 items.
最后,这些从爬虫器返回的 items 会被持久化到数据库(在一些 Item Pipeline)或者放入 一个用 Feed 导出 导出的文件。
尽管这个过程已经适用(或多或少)一些爬虫,不过 Scrapy 还附带了一些其它的爬虫器用于不同的爬取情况。下面我们来 了解一下它们。
scrapy.Spider¶
-
class
scrapy.spiders.Spider¶ 最简单的爬虫器,其它所有的爬虫器都继承了它(包括 Scrapy 附带的爬虫器,以及你自定义的 爬虫器)。它没有提供任何特殊的功能。只有一个默认的
start_requests()启动方法用于 发送start_urls属性中的请求并把返回的响应作为参数调用爬虫器的parse方法。-
name¶ 定义了爬虫器的名字,Scrapy通过爬虫器的名字来识别不同的爬虫器,所以必需提供。然而, 你当然可以定义多个一样的爬虫器,这是一个爬虫器必需提供的属性。
如果爬虫器抓取一个单独的站点, 通常的做法是根据域名定义爬虫器 name, 带或者不带 TLD 。 比如一个爬取
mywebsite.com爬虫器可以被称作mywebsite。注解
在 Python2 中只能是ASCII码。
-
allowed_domains¶ 一个可选的列表,包含了爬虫器可以抓取的域名。 这个列表指定的域名(包含子域名)将不会以被请求 如果
OffsiteMiddleware被启用。假设目标url是
https://www.example.com/1.html, 那么就添加'example.com'到这个列表中。
-
custom_settings¶ 字典类型的设置配置,当爬虫启动的时候会被项目中的配置重写。它必需作为一个类属性 被定义,然后设置会在实例化前更新。
可用的内置设置列表详见: Built-in settings reference.
-
crawler¶ 该属性会在
from_crawler()初始化类后被类方法设置,然后与这个爬虫器的 实例绑定的Crawler对象关联。爬虫程序在项目入口中封装了许多组件(比如 extensions, middlewares, signals managers, etc). 了解更多详见 Crawler API 。
-
logger¶ Python的日志用爬虫器的
name创建。你可以按照 Logging from Spiders 里 的描述来用它发送日志信息。
-
from_crawler(crawler, *args, **kwargs)¶ Scrapy 用这个类方法创建你的爬虫器。
你可能不需要直接覆盖它,因为默认的启动行为作为
__init__()方法的一个代理, 通过 传递args和kwargs参数来调用它。尽管如此, 这个方法在新的实例中设置了
crawler和settings属性以便在后来的 爬虫程序中可以被使用。参数: - crawler (
Crawler实例) – crawler to which the spider will be bound - args (列表) –
__init__()方法传递过来的参数 - kwargs (字典) –
__init__()方法传递过来的关键字参数。
- crawler (
-
start_requests()¶ 此方法必需返回一个迭代器,其中包含这个爬虫器要爬取的第一个请求。 当爬虫器开始爬虫的时候它被Scrapy调用。Scrapy只会调用它一次, 所以作为生成器执行
start_requests()是安全的。对于每个
start_urls里的url默认使用Request(url, dont_filter=True)执行的。如果你想要修改最初爬取某个网站的Request对象, 你可以重写这个方法。比如,你需要在登录时 使用 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
-
parse(response)¶ 当没有指定回调函数时,Scrapy默认使用该方法作为回调函数来处理响应。
parse方法负责处理响应并返回抓取到的数据以及/或者跟进的URL。Spider对其他的Request的回调函数也有相同的要求。此方法以及其他回调函数必需返回一个包含
Request、 字典 或者Item的可迭代对象。参数: response ( Response) – 用于解析response
-
log(message[, level, component])¶ 使用爬虫器的
logger发送日志消息的装饰器,保持身后的兼容性。更多内容 请参考 Logging from Spiders。
-
closed(reason)¶ 当爬虫器关闭时调用。 该方法提供了一个替代调用signals.connect()来 监听
spider_closed信号的快捷方式。
-
让我们来看一个例子:
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.logger.info('A response from %s just arrived!', response.url)
R在单个回调函数中返回多个Request以及Item的例子:
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').getall():
yield {"title": h3}
for href in response.xpath('//a/@href').getall():
yield scrapy.Request(response.urljoin(href), self.parse)
除了 start_urls ,你也可以直接使用 start_requests() ;
也可以使用 Items 来给予数据更多的结构性(give data more structure):
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').getall():
yield MyItem(title=h3)
for href in response.xpath('//a/@href').getall():
yield scrapy.Request(response.urljoin(href), self.parse)
爬虫器参数¶
爬虫器可以接收一些参数用来改变它的行为。 spider参数的一些常见用途是定义开始url或 将限制爬取站点的某些部分, 但是它们可以用来配置爬行器的任何功能。
爬虫顺口参数通过 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。
一个有效的用例是使用 HttpAuthMiddleware 设置http用户证书。
或者使用 UserAgentMiddleware 设置用户代理:
scrapy crawl myspider -a http_user=myuser -a http_pass=mypassword -a user_agent=mybot
爬虫器参数也可以使用Scrapyd schedule.json API传递。查看 Scrapyd documentation.
常见的爬虫器¶
Scrapy附带了一些有用的通用爬行器,您可以使用它们对爬行器进行子类化。 他们的目标是为一些常见的抓取情况提供方便的功能, 比如根据特定的规则跟进站点上的所有链接,
从 Sitemaps 爬取, 或者解析成 XML/CSV feed.
例如使用下面的爬虫器,我们假设你已经新建了一个项目并在 myproject.items 文件明声明
了一个 TestItem
import scrapy
class TestItem(scrapy.Item):
id = scrapy.Field()
name = scrapy.Field()
description = scrapy.Field()
CrawlSpider¶
-
class
scrapy.spiders.CrawlSpider¶ 爬取一般网站常用的爬虫器。它有一个方便的机制用来跟进一些规则定义的links。 也许该爬虫器并不是完全适合你的特定网站或项目,但其对很多情况都使用, 因此你可以以其为起点,根据需求修改部分方法。当然您也可以实现自己的爬虫器。
除了从Spider继承过来的(您必须提供的)属性外,其提供了一个新的属性:
-
rules¶ 一个包含一个(或多个)
Rule对象的集合。每个Rule都定义了 某些行为来爬取网站。Rule对象在下边会介绍。如果多个rule匹配了相同的链接, 则根据他们在本属性中被定义的顺序,第一个会被使用。
该爬虫器也提供了一个可复写的方法:
.. method:: parse_start_url(response)
-
爬取规则(Crawling rules)¶
-
class
scrapy.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以及(或)RequestRequest 对象(或者这两者的子类)的列表(list)。警告
当编写爬虫规则时,请避免使用
parse作为回调函数。由于CrawlSpider使用parse方法 来实现其逻辑,如果你覆盖了parse方法,爬虫器将会运行失败。cb_kwargs包含传递给回调函数的参数(keyword argument)的字典。follow是一个布尔(boolean)值,指定了根据该规则从response提取的链接是否需要跟进。 如果callback为Nonefollow默认为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.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()').get()
item['description'] = response.xpath('//td[@id="item_description"]/text()').get()
return item
这个爬虫器将开始爬取 example.com’s 的首页,获取category以及item的链接并对后者使用 ``parse_item``方法。 当item获得返回(response)时,将使用XPath处理HTML并生成一些数据填入 :class:`~scrapy.item.Item`中。
XMLFeedSpider¶
-
class
scrapy.spiders.XMLFeedSpider¶ XMLFeedSpider被设计用于通过迭代各个节点来分析XML feed。迭代器可以从 :
iternodes,xml和html中选择。鉴于xml和html迭代器需要先读取所有DOM再分析而引起的性能问题。 一般还是推荐使用iternodes。不过使用html作为迭代器能有效应对错误的XML。您必须定义下列类属性来设置迭代器以及 tag name:
-
iterator¶ 用于确定使用哪个迭代器的string。可选项有:
'iternodes'一个高性能的基于正则表达式的迭代器'html'- 使用Selector的迭代器。 需要注意的是该迭代器使用DOM进行分析,其需要将所有的DOM载入内存, 当数据量大的时候会产生问题。'xml'- 使用Selector的迭代器。 需要注意的是该迭代器使用DOM进行分析,其需要将所有的DOM载入内存, 当数据量大的时候会产生问题。
默认值为:
'iternodes'.
-
itertag¶ 一个包含开始迭代的节点名的字符串。例如:
itertag = 'product'
-
namespaces¶ 一个由
(prefix, uri)元组所组成的列表。 其定义了在该文档中会被爬虫器处理的可用的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 例子¶
这些爬虫器用起来都非常简单,让我们看一个例子:
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.getall()))
item = TestItem()
item['id'] = node.xpath('@id').get()
item['name'] = node.xpath('name').get()
item['description'] = node.xpath('description').get()
return item
基本上,我们在上面所做的就是创建一个爬虫器用来从给定的 start_urls 下载feed。
,并迭代feed中每个 item 标签。输出,并在 Item 中存储有些随机数据。
CSVFeedSpider¶
-
class
scrapy.spiders.CSVFeedSpider¶ 该spider除了其按行遍历而不是节点之外其他和XMLFeedSpider十分类似。而其在每次迭代时调用的是
parse_row()。-
delimiter¶ 在CSV文件中用于区分字段的分隔符。类型为string默认是
','(逗号)。
-
quotechar¶ A string with the enclosure character for each field in the CSV file Defaults to
'"'(quotation mark).
-
headers¶ 在CSV文件中包含的用来提取字段的行的列表。
-
parse_row(response, row)¶ 接收一个response对象及一个以提供或检测出来的header为键的字典从一个CSV文件中。也可以覆盖
adapt_response和process_results方法来进行预处理(pre-processing)及后(post-processing)处理。
-
CSVFeedSpider example¶
下面的例子和之前的例子很像,但使用了
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 来发现爬取的URL。
其支持嵌套的sitemap,并能从 robots.txt 中获取sitemap的url。
-
sitemap_urls¶ 包含你要爬取的url的sitemap的url列表(list)。
你也可以指定一个 robots.txt 文件,爬虫器会从中分析并提取url。
-
sitemap_rules¶ A list of tuples
(regex, callback)where: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。
-
sitemap_filter(entries)¶ 这是一个过滤功能的数据,可以重写它来根据网站标签的值来选择网站标签。
例如:
<url> <loc>http://example.com/</loc> <lastmod>2005-01-01</lastmod> </url>
我们可以定义一个
sitemap_filter方法来根据日期过滤entriesfrom datetime import datetime from scrapy.spiders import SitemapSpider class FilteredSitemapSpider(SitemapSpider): name = 'filtered_sitemap_spider' allowed_domains = ['example.com'] sitemap_urls = ['http://example.com/sitemap.xml'] def sitemap_filter(self, entries): for entry in entries: date_time = datetime.strptime(entry['lastmod'], '%Y-%m-%d') if date_time.year >= 2005: yield entry
这将只检索2005年及以后年份修改过的
entries。条目是从 sitemap 文档中提取的字典对象。通常,键是标记名称,值是其中的文本。
注意下面的几点:
- 因为loc属性是必需的, 没有此标记的条目将被丢弃
- 备用链接使用
alternate键存储在列表中 (seesitemap_alternate_links) - 命名空间被移除了,因此命名 lxml tags的
{namespace}tagname变成了tagname
如果忽略这个方法,在 sitemaps 找到的所有条目都会被处理,遵守其他属性及其设置。
-
SitemapSpider 例子¶
简单的例子: 使用 parse 处理通过sitemap发现的所有url:
from scrapy.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ['http://www.example.com/sitemap.xml']
def parse(self, response):
pass # ... scrape item here ...
用特定的函数处理某些url,其他的使用另外的回调:
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 定义的文件,并只跟进包含有 /sitemap_shop 的url:
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中使用其他url:
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 ...