爬虫器

Spiders 类定义了如何爬取一个站点(或者一组站点),包括如何执行抓取(即跟踪链接)以及如何从爬取 的页面中提取结构化的数据(即爬取 items)。换句话说,Spiders 字义了爬取和解析特定站点(或者一组站点)的行为。

对于爬虫器来说,抓取的过程类似这样运行的:

  1. 从放入的一批初始 url 开始抓取,然后指定一个回调函数来处理这个(这些)请求的响应。

    第一批请求是调用 start_requests() 方法,该方法(默认) 为 start_urls 中指定的 URLS 生成 Request 类, 并且 parse 方法作为这些请求的回调函数。

  2. 在回调函数中,你解析了响应(网页)并返回了提取的(字典化)的数据、 Item 对象、 或者这些对象的迭代器其中之王。这些请求也可以包含一个回调(也许是一个回调)并被 Scrapy 下载然后它们的响应被指定的 回调函数处理。

  3. 在回调函数中, 你解析了页面内容, 主要用了 选择器 (但是你可以用BeautifulSoup、lxml或者你喜欢的 其它方法) 并用这些解析的数据生成 items.

  4. 最后,这些从爬虫器返回的 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' 到这个列表中。

start_urls

一个URL列表,如果没有定义特殊的URL,爬虫器将会从这里开始爬取。所以,第一批页面被从这个列表 开始下载。后来的 Request 会从这个 url 列表中的数据里相继生成。

custom_settings

字典类型的设置配置,当爬虫启动的时候会被项目中的配置重写。它必需作为一个类属性 被定义,然后设置会在实例化前更新。

可用的内置设置列表详见: Built-in settings reference.

crawler

该属性会在 from_crawler() 初始化类后被类方法设置,然后与这个爬虫器的 实例绑定的 Crawler 对象关联。

爬虫程序在项目入口中封装了许多组件(比如 extensions, middlewares, signals managers, etc). 了解更多详见 Crawler API

settings

运行爬虫器的配置。 是一个 Settings 实例, 从 设置 主题了解这个主题的详细配置。

logger

Python的日志用爬虫器的 name 创建。你可以按照 Logging from Spiders 里 的描述来用它发送日志信息。

from_crawler(crawler, *args, **kwargs)

Scrapy 用这个类方法创建你的爬虫器。

你可能不需要直接覆盖它,因为默认的启动行为作为 __init__() 方法的一个代理, 通过 传递 argskwargs 参数来调用它。

尽管如此, 这个方法在新的实例中设置了 crawlersettings 属性以便在后来的 爬虫程序中可以被使用。

参数:
  • crawler (Crawler 实例) – crawler to which the spider will be bound
  • args (列表) – __init__() 方法传递过来的参数
  • kwargs (字典) – __init__() 方法传递过来的关键字参数。
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)
当start_url的请求返回时,该方法被调用。该方法解析最初的返回值并必须返回 一个 Item 对象或者一个 Request 对象, 或者是一个包含它们任何一个的可迭代对象。

爬取规则(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 以及(或) Request Request 对象(或者这两者的子类)的列表(list)。

警告

当编写爬虫规则时,请避免使用 parse 作为回调函数。由于 CrawlSpider 使用 parse 方法 来实现其逻辑,如果你覆盖了 parse 方法,爬虫器将会运行失败。

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.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, xmlhtml 中选择。鉴于 xmlhtml 迭代器需要先读取所有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。 prefixuri 会被自动调用 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_responseprocess_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都会被跟进。

当一个 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 方法来根据日期过滤 entries

from 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 键存储在列表中 (see sitemap_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 ...