Skip to main content

蜘蛛

“蜘蛛”是定义如何抓取某个网站(或一组网站)的类,包括如何执行抓取(即关注链接)以及如何从其网页中提取结构化数据(即抓取项目)。换句话说,Spider是您定义用于为特定网站(或在某些情况下,一组网站)抓取和解析网页的自定义行为的位置。

对于蜘蛛,刮擦循环经历这样的事情:

  1. 您首先生成用于抓取第一个URL的初始请求,然后指定要使用从这些请求下载的响应调用的回调函数。

    通过调用 start_requests() 方法(默认情况下)为在 start_urls 中指定的URL生成 Request 并且将 parse 方法作为请求的回调函数来获得第一个执行请求。

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

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

  4. 最后,从蜘蛛返回的项目通常会持久保存到数据库(在某些 项目管道 中),或者使用 饲料出口 写入文件。

即使这个循环(或多或少)适用于任何种类的蜘蛛,有不同种类的默认蜘蛛捆绑到Scrapy中用于不同的目的。我们将在这里谈论这些类型。

scrapy.Spider

class scrapy.spiders.Spider

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

name

定义此蜘蛛名称的字符串。蜘蛛名称是蜘蛛如何由Scrapy定位(和实例化),因此它必须是唯一的。但是,没有什么能阻止你实例化同一个蜘蛛的多个实例。这是最重要的蜘蛛属性,它是必需的。

如果蜘蛛抓取单个域,通常的做法是在域后面命名蜘蛛,有或没有 TLD。因此,例如,爬行 mywebsite.com 的蜘蛛通常被称为 mywebsite

注解

在Python 2中,这必须是ASCII。

allowed_domains

包含允许此蜘蛛抓取的域的字符串的可选列表。如果启用了 OffsiteMiddleware,则不会遵循对不属于此列表(或其子域)中指定的域名的URL的请求。

start_urls

当没有指定特定网址时,蜘蛛将开始抓取的网址列表。所以,下载的第一页将是这里列出的那些。后续的URL将从起始URL中包含的数据开始连续生成。

custom_settings

运行此蜘蛛时将从项目宽配置覆盖的设置字典。它必须定义为类属性,因为设置在实例化之前更新。

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

crawler

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

Crawlers在项目中封装了很多组件,用于单个条目访问(例如扩展,中间件,信号管理器等)。请参阅 抓取工具API 了解更多。

settings

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

logger

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

from_crawler(crawler, *args, **kwargs)

这是Scrapy用来创建你的蜘蛛的类方法。

你可能不需要直接覆盖它,因为默认实现充当 __init__() 方法的代理,使用给定的参数 args 和命名参数 kwargs 来调用它。

尽管如此,此方法在新实例中设置 crawlersettings 属性,以便稍后在蜘蛛代码中访问它们。

参数:
  • crawler (Crawler instance) – 爬行器,蜘蛛将绑定
  • args (list) – 传递给 __init__() 方法的参数
  • kwargs (dict) – 传递给 __init__() 方法的关键字参数
start_requests()

此方法必须返回一个可迭代的第一个请求来抓取此蜘蛛。

当没有指定特定的URL时,当蜘蛛被打开以进行抓取时,这是由Scrapy调用的方法。如果指定了特定的URL,则使用 make_requests_from_url() 来创建请求。此方法也仅从Scrapy调用一次,因此将其作为生成器实现是安全的。

默认实现使用 make_requests_from_url()start_urls 中的每个网址生成请求。

如果您要更改用于开始删除域的请求,则这是要覆盖的方法。例如,如果您需要通过使用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
make_requests_from_url(url)

一种接收URL并返回 Request 对象(或 Request 对象列表)以进行抓取的方法。此方法用于在 start_requests() 方法中构造初始请求,并且通常用于将URL转换为请求。

除非重写,此方法返回具有 parse() 方法的Requests作为它们的回调函数,并启用dont_filter参数(有关详细信息,请参阅 Request 类)。

parse(response)

这是Scrapy用于处理下载的响应的默认回调,当它们的请求没有指定回调时。

parse 方法负责处理响应并返回所刮取的数据和/或更多的URL。其他请求回调与 Spider 类具有相同的要求。

此方法以及任何其他请求回调必须返回 Request 和/或dicts或 Item 对象的可迭代。

参数:response (Response) – 对解析的响应
log(message[, level, component])

包装器通过Spider的 logger 发送日志消息,保留向后兼容性。有关更多信息,请参阅 从蜘蛛记录

closed(reason)

当蜘蛛关闭时召唤。此方法为 spider_closed 信号的signals.connect()提供了一个快捷方式。

让我们看一个例子:

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').extract():
            yield {"title": h3}

        for url in response.xpath('//a/@href').extract():
            yield scrapy.Request(url, callback=self.parse)

而不是 start_urls,你可以直接使用 start_requests();给出数据更多的结构可以使用 项目:

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').extract():
            yield MyItem(title=h3)

        for url in response.xpath('//a/@href').extract():
            yield scrapy.Request(url, callback=self.parse)

蜘蛛参数

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

Spider参数通过使用 -a 选项的 crawl 命令传递。例如:

scrapy crawl myspider -a category=electronics

Spider可以在其 __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_evaljson.loads 的方法将它自己解析为列表,然后将其设置为属性。否则,你会导致迭代一个 start_urls 字符串(一个非常常见的python陷阱),导致每个字符被看作一个单独的url。

有效的用例是设置 HttpAuthMiddleware 使用的http验证凭据或 UserAgentMiddleware 使用的用户代理:

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

Spider参数也可以通过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

这是最常用的爬行常规网站的蜘蛛,因为它通过定义一组规则为下列链接提供了一种方便的机制。它可能不是最适合您的特定网站或项目,但它是通用的几种情况下,所以你可以从它开始,覆盖它需要更多的自定义功能,或只是实现自己的蜘蛛。

除了从Spider继承的属性(你必须指定),这个类支持一个新的属性:

rules

其中是一个(或多个) Rule 对象的列表。每个 Rule 定义了抓取网站的某种行为。规则对象如下所述。如果多个规则匹配相同的链接,则将根据它们在此属性中定义的顺序使用第一个。

这个蜘蛛还暴露了一个可覆盖的方法:

parse_start_url(response)

对于start_urls响应调用此方法。它允许解析初始响应,并且必须返回 Item 对象,Request 对象或包含它们中的任何一个的迭代。

抓取规则

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

link_extractor 是一个 链接提取程序 对象,它定义如何从每个爬网页面提取链接。

callback 是一个可调用的或字符串(在这种情况下,将使用具有该名称的蜘蛛对象的方法),以便为使用指定的link_extractor提取的每个链接调用。这个回调接收一个响应作为它的第一个参数,并且必须返回一个包含 Item 和/或 Request 对象(或它们的任何子类)的列表。

警告

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

cb_kwargs 是包含要传递到回调函数的关键字参数的dict。

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

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

process_request 是一个可调用的或一个字符串(在这种情况下,将使用具有该名称的蜘蛛对象的方法),它将被此规则提取的每个请求调用,并且必须返回请求或无(过滤出请求)。

CrawlSpider示例

让我们来看看一个带有规则的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()').extract()
        item['description'] = response.xpath('//td[@id="item_description"]/text()').extract()
        return item

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

XMLFeedSpider

class scrapy.spiders.XMLFeedSpider

XMLFeedSpider设计用于通过以特定节点名称迭代XML订阅源来解析XML订阅源。迭代器可以选自:iternodesxmlhtml。为了性能原因,建议使用 iternodes 迭代器,因为 xmlhtml 迭代器一次生成整个DOM以便对其进行解析。然而,使用 html 作为迭代器可能在解析带有坏标记的XML时很有用。

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

iterator

定义要使用的迭代器的字符串。它可以是:

  • 'iternodes' - 基于正则表达式的快速迭代器

  • 'html' - 使用 Selector 的迭代器。请记住,这使用DOM解析,并且必须加载所有DOM在内存中,这可能是一个大饲料的问题

  • 'xml' - 使用 Selector 的迭代器。请记住,这使用DOM解析,并且必须加载所有DOM在内存中,这可能是一个大饲料的问题

它默认为:'iternodes'

itertag

一个具有要迭代的节点(或元素)的名称的字符串:

itertag = 'product'
namespaces

一个 (prefix, uri) 元组的列表,它定义该文档中将使用此蜘蛛处理的命名空间。 prefixuri 将用于使用 register_namespace() 方法自动注册命名空间。

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

例:

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示例

这些蜘蛛很容易使用,让我们看看一个例子:

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.extract()))

        item = TestItem()
        item['id'] = node.xpath('@id').extract()
        item['name'] = node.xpath('name').extract()
        item['description'] = node.xpath('description').extract()
        return item

基本上我们做的是创建一个蜘蛛从给定的 start_urls 下载一个饲料,然后遍历每个 item 标签,打印出来,并存储一些随机数据在 Item

CSVFeedSpider

class scrapy.spiders.CSVFeedSpider

这个蜘蛛非常类似于XMLFeedSpider,除了它迭代行,而不是节点。在每次迭代中调用的方法是 parse_row()

delimiter

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

quotechar

CSV文件中每个字段的包含字符的字符串默认为 '"' (引号)。

headers

文件CSV Feed中包含的行的列表,用于从中提取字段。

parse_row(response, row)

使用CSV文件的每个提供(或检测到)标头的键接收响应和dict(表示每行)。这个蜘蛛还提供了机会来重写 adapt_responseprocess_results 方法用于预处理和后处理目的。

CSVFeedSpider示例

让我们看一个类似于前一个示例,但使用 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 发现网址来抓取网站。

它支持嵌套的Sitemap并从 robots.txt 发现Sitemap网址。

sitemap_urls

指向您要抓取的网址的网站的网址列表。

您还可以指向一个 robots.txt,它将解析从中提取站点地图网址。

sitemap_rules

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

  • regex 是一个正则表达式,用于匹配从站点地图提取的网址。 regex 可以是一个str或一个编译的正则表达式对象。

  • callback是用于处理与正则表达式匹配的url的回调。 callback 可以是字符串(指示蜘蛛方法的名称)或可调用的。

例如:

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

规则按顺序应用,只有匹配的第一个将被使用。

如果省略此属性,则会在 parse 回调中处理在站点地图中找到的所有网址。

sitemap_follow

应遵循的网站地图的正则表达式列表。这只适用于使用指向其他Sitemap档案的 Sitemap index files 的网站。

默认情况下,将跟踪所有网站地图。

指定是否应遵循一个 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 回调处理通过站点地图发现的所有网址:

from scrapy.spiders import SitemapSpider

class MySpider(SitemapSpider):
    sitemap_urls = ['http://www.example.com/sitemap.xml']

    def parse(self, response):
        pass # ... scrape item here ...

使用某个回调处理一些网址,并使用不同的回调处理其他网址:

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 文件中定义的sitemaps,并且只跟踪其网址包含 /sitemap_shop 的Sitemap:

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