Spiders

蜘蛛程序是定义如何刮除某个站点(或一组站点)的类,包括如何执行爬网(即,跟踪链接)以及如何从其页面中提取结构化数据(即,刮取项). 换句话说,Spider是您定义自定义行为的地方,该行为用于爬网和解析特定站点(或在某些情况下为一组站点)的页面.

对于蜘蛛,抓取周期经历如下过程:

  1. 首先,生成初始请求以爬网第一个URL,然后指定要调用的回调函数,并从这些请求中下载响应.

    要执行的第一个请求是通过调用start_requests()方法获得的,该方法(默认情况下)会生成对start_urls指定的URL的Request ,并使用parse方法作为Requests的回调函数.

  2. 在回调函数中,您解析响应(网页)并返回带有提取数据, Item对象, Request对象或这些对象的可迭代对象的字典. 这些请求还将包含一个回调(可能相同),然后由Scrapy下载,然后由指定的回调处理响应.

  3. 在回调函数中,通常使用选择器 (但也可以使用BeautifulSoup,lxml或您喜欢的任何机制)来解析页面内容,并使用已解析的数据生成项目.

  4. 最后,通常将从蜘蛛返回的项目保存到数据库(在某些项目管道中 )或使用Feed导出写入文件.

即使这个周期(或多或少)适用于任何一种蜘蛛,Scrapy还是捆绑了不同类型的默认蜘蛛以用于不同目的. 我们将在这里讨论这些类型.

scrapy.Spider

class scrapy.spiders.Spider

这是最简单的蜘蛛,也是所有其他蜘蛛都必须继承的蜘蛛(包括与Scrapy捆绑在一起的蜘蛛,以及您自己编写的蜘蛛). 它不提供任何特殊功能. 它只是提供了一个默认的start_requests()实现,该实现从start_urls蜘蛛属性发送请求,并为每个结果响应调用蜘蛛的方法parse .

name

一个字符串,用于定义此蜘蛛的名称. 蜘蛛名称是由Scrapy查找(并实例化)蜘蛛的方式,因此它必须是唯一的. 但是,没有什么可以阻止您实例化同一蜘蛛的一个以上实例. 这是最重要的蜘蛛属性,它是必需的.

如果蜘蛛抓取单个域,则通常的做法是使用该域命名蜘蛛,并带有或不带有TLD . 因此,例如,爬行mywebsite.com的蜘蛛通常被称为mywebsite .

Note

在Python 2中,这只能是ASCII.

allowed_domains

包含此蜘蛛可以爬网的域的字符串的可选列表. 如果启用了OffsiteMiddleware将不遵循对不属于此列表中指定的域名(或其子域)的URL的请求.

假设您的目标网址是https://www.example.com/1.html ,然后将'example.com'添加到列表中.

start_urls

未指定特定URL时,爬网程序将开始从其爬网的URL列表. 因此,下载的第一页将是此处列出的页面. 随后的Request将根据起始URL中包含的数据连续生成.

custom_settings

运行此Spider时将从项目范围的配置中覆盖的设置字典. 由于设置是在实例化之前更新的,因此必须将其定义为类属性.

有关可用内置设置的列表,请参阅: 内置设置参考 .

crawler

在初始化该类后,由from_crawler()类方法设置此属性,并链接到此Spider实例绑定到的Crawler对象.

爬网程序在项目中封装了许多组件以供其单项访问(例如扩展,中间件,信号管理器等). 请参阅Crawler API进一步了解它们.

settings

运行此蜘蛛的配置. 这是一个Settings实例,有关此主题的详细介绍,请参见" 设置"主题.

logger

使用Spider的name创建的Python记录器. 您可以使用它通过它发送日志消息,如从Spiders记录日志中所述.

from_crawler(crawler, *args, **kwargs)

这是Scrapy用于创建蜘蛛的类方法.

您可能不需要直接重写此方法,因为默认实现充当__init__()方法的代理,并使用给定参数args和命名参数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.

Parameters:
  • 搜寻器Crawler实例)–蜘蛛将绑定到的搜寻器
  • argslist )–传递给__init__()方法的参数
  • kwargsdict )–传递给__init__()方法的关键字参数
start_requests()

此方法必须返回带有第一个Request的Iterable,以对该蜘蛛进行爬网. 当蜘蛛被刮开时,Scrapy会调用它. Scrapy只调用一次,因此可以安全地将start_requests()实现为生成器.

默认实现为start_urls每个URL生成Request(url, dont_filter=True) .

如果要更改用于开始抓取域的请求,则可以使用此方法进行覆盖. 例如,如果您需要通过使用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和/或dict或Item对象的可迭代对象.

Parameters: responseResponse )–解析的响应
log(message[, level, component])

通过Spider的logger发送日志消息的包装程序,为向后兼容而保留. 有关更多信息,请参见从Spider记录 .

closed(reason)

在蜘蛛关闭时调用. 此方法为spider_closed信号提供了前往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)

从单个回调返回多个请求和项目:

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_requests()代替start_urls ; 为了给数据更多结构,您可以使用Items

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 arguments

蜘蛛可以接收修改其行为的参数. 蜘蛛形参数的一些常见用法是定义起始URL或将爬网限制为网站的某些部分,但是它们可用于配置蜘蛛形体的任何功能.

蜘蛛参数使用-a选项通过crawl命令传递. 例如:

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)

请记住,蜘蛛参数只是字符串. 蜘蛛不会自行进行任何解析. 如果要从命令行设置start_urls属性,则必须使用ast.literal_evaljson.loads之类将其自己解析为一个列表,然后将其设置为属性. 否则,您将导致在start_urls字符串(非常常见的python陷阱)上进行迭代,从而导致每个字符都被视为单独的url.

一个有效的用例是设置HttpAuthMiddlewareUserAgentMiddleware所使用的用户代理使用的http auth凭据:

scrapy crawl myspider -a http_user=myuser -a http_pass=mypassword -a user_agent=mybot

Spider arguments can also be passed through the Scrapyd schedule.json API. See Scrapyd documentation.

Generic Spiders

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

这是用于爬取常规网站的最常用的蜘蛛,因为它通过定义一组规则为跟踪链接提供了一种便捷的机制. 它可能不是最适合您的特定网站或项目的方法,但是它在某些情况下足够通用,因此您可以从中开始并根据需要覆盖它以获取更多自定义功能,或者只是实现自己的蜘蛛.

除了从Spider继承的属性(必须指定)之外,此类还支持一个新属性:

rules

这是一个(或多个) Rule对象的列表. 每个Rule定义了用于爬网的特定行为. 规则对象如下所述. 如果多个规则匹配同一链接,则将根据在此属性中定义的顺序使用第一个规则.

这个蜘蛛还公开了一个可重写的方法:

parse_start_url(response)

该方法用于start_urls响应. 它允许解析初始响应,并且必须返回Item对象, Request对象或包含其中任何一个的Iterable.

Crawling rules

class scrapy.spiders.Rule(link_extractor=None, callback=None, cb_kwargs=None, follow=None, process_links=None, process_request=None)

link_extractor is a Link Extractor object which defines how links will be extracted from each crawled page. Each produced link will be used to generate a Request object, which will contain the link’s text in its meta dictionary (under the link_text key). If omitted, a default link extractor created with no arguments will be used, resulting in all links being extracted.

callback是使用指定的链接提取器提取的每个链接所调用的可调用字符串或字符串(在这种情况下,将使用来自该名称的Spider对象的方法). 此回调将一个Response作为其第一个参数,并且必须返回Itemdict和/或Request对象(或其任何子类)的单个实例或可迭代. 如上所述,接收到的Response对象将在其meta字典中(在link_text键下)包含生成Request的链接的文本.

Warning

编写爬网蜘蛛规则时,请避免将parse用作回调,因为CrawlSpider使用parse方法本身来实现其逻辑. 因此,如果您覆盖parse方法,则爬网蜘蛛将不再起作用.

cb_kwargs是一个字典,其中包含要传递给回调函数的关键字参数.

follow是一个布尔值,它指定是否应从使用此规则提取的每个响应中跟随链接. 如果callback是无follow默认为True ,否则默认为False .

process_links是可调用的,或者是一个字符串(在这种情况下,将使用蜘蛛对象中具有该名称的方法),该字符串将针对使用指定的link_extractor从每个响应中提取的每个链接列表进行调用. 这主要用于过滤目的.

process_request是可调用的(或字符串,在这种情况下,将使用来自具有该名称的蜘蛛对象的方法),该调用将被此规则提取的每个Request调用. 该可调用对象应将所述请求作为第一个参数,并将请求源自的Response作为第二个参数. 它必须返回一个Request对象或None (以滤除该请求).

CrawlSpider example

现在,让我们看一个带有规则的示例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()
        item['link_text'] = response.meta['link_text']
        return item

该蜘蛛将开始爬网example.com的主页,收集类别链接和项目链接,并使用parse_item方法解析后者. 对于每个项目响应,将使用XPath从HTML中提取一些数据,并用它填充Item .

XMLFeedSpider

class scrapy.spiders.XMLFeedSpider

XMLFeedSpider设计用于解析XML提要,方法是通过某个特定的节点名称对其进行迭代. 可以从iternodesxmlhtml选择迭代器. 出于性能原因,建议使用iternodes迭代器,因为xmlhtml迭代器会立即生成整个DOM以便对其进行解析. 但是,在解析标记错误的XML时,使用html作为迭代器可能会很有用.

要设置迭代器和标记名称,必须定义以下类属性:

iterator

一个字符串,定义要使用的迭代器. 可以是:

  • 'iternodes'基于正则表达式的快速迭代器
  • 'html'使用Selector的迭代Selector . 请记住,这使用DOM解析,并且必须将所有DOM加载到内存中,这对于大型Feed可能是个问题
  • 'xml'使用Selector的迭代Selector . 请记住,这使用DOM解析,并且必须将所有DOM加载到内存中,这对于大型Feed可能是个问题

默认为: 'iternodes' .

itertag

A string with the name of the node (or element) to iterate in. Example:

itertag = 'product'
namespaces

(prefix, uri)元组的列表,这些元组定义了该蜘蛛中将处理的该文档中可用的命名空间. prefixuri将用于使用register_namespace()方法自动注册名称空间.

然后,您可以在itertag属性中指定具有名称空间的节点.

Example:

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对象或包含其中任何一个的Iterable.

process_results(response, results)

蜘蛛返回的每个结果(项目或请求)都会调用此方法,该方法旨在执行将结果返回到框架核心之前所需的任何最后一次处理,例如设置项目ID. 它接收结果列表和产生这些结果的响应. 它必须返回结果列表(项目或请求).

XMLFeedSpider example

These spiders are pretty easy to use, let’s have a look at one example:

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下载提要,然后遍历其每个item标签,将它们打印出来,并将一些随机数据存储在Item .

CSVFeedSpider

class scrapy.spiders.CSVFeedSpider

除了在整个行而不是节点上进行迭代之外,此蜘蛛与XMLFeedSpider非常相似. 在每次迭代中调用的方法是parse_row() .

delimiter

CSV文件中每个字段的分隔符字符串,默认为',' (逗号).

quotechar

CSV文件中每个字段的带有附件字符的字符串默认为'"' (引号).

headers

CSV文件中列名称的列表.

parse_row(response, row)

Receives a response and a dict (representing each row) with a key for each provided (or detected) header of the CSV file. This spider also gives the opportunity to override adapt_response and process_results methods for pre- and post-processing purposes.

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来爬网站点.

它支持嵌套站点地图并从robots.txt发现站点地图URL.

sitemap_urls

指向您要抓取其网址的站点地图的网址列表.

您也可以指向robots.txt,然后对其进行解析以从中提取站点地图网址.

sitemap_rules

元组列表(regex, callback) ,其中:

  • regex是一个正则表达式,用于匹配从站点地图提取的网址. regex可以是str或已编译的regex对象.
  • callback是用于处理与正则表达式匹配的url的回调. callback可以是字符串(指示Spider方法的名称)或可调用的.

例如:

sitemap_rules = [('/product/', 'parse_product')]

规则将按顺序应用,并且仅使用第一个匹配的规则.

如果您忽略此属性,则将使用parse回调处理在站点地图中找到的所有网址.

sitemap_follow

应遵循的站点地图正则表达式列表. 这仅适用于使用指向其他站点地图文件的站点地图索引文件的站点.

默认情况下,将遵循所有站点地图.

指定是否应遵循一个url备用链接. 这些是在同一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 .

条目是从站点地图文档中提取的字典对象. 通常,键是标签名称,值是其中的文本.

请务必注意:

  • 因为需要loc属性,所以将删除没有此标签的条目
  • 备用链接与alternate键一起存储在列表中(请参阅sitemap_alternate_links
  • 命名空间已删除,因此名为{namespace}tagname lxml标记仅成为tagname

如果省略此方法,将观察站点地图中找到的所有条目,并观察其他属性及其设置.

SitemapSpider examples

最简单的示例:使用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 ...

使用某些回调处理某些URL,并使用不同的回调处理其他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站点地图:

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 ...