爬虫

Spider 是类,它们定义了如何抓取某个网站(或一组网站),包括如何执行抓取(即跟随链接)以及如何从页面中提取结构化数据(即抓取 Item)。换句话说,Spider 是定义特定网站(或在某些情况下,一组网站)的爬取和解析页面自定义行为的地方。

对于爬虫而言,抓取周期大致如下:

  1. 你首先生成初始请求来爬取第一个 URL,并指定一个回调函数来处理从这些请求下载到的响应。

    要执行的第一个请求是通过迭代 start() 方法获得的,该方法默认会为 start_urls spider 属性中的每个 URL 生成一个 Request 对象,并将 parse 方法设置为 callback 函数来处理每个 Response

  2. 在回调函数中,你解析响应(网页)并返回 Item 对象Request 对象,或者这些对象的迭代器。这些 Request 也将包含一个回调(可能是同一个),然后由 Scrapy 下载,其响应再由指定的回调处理。

  3. 在回调函数中,你解析页面内容,通常使用 Selector(但你也可以使用 BeautifulSoup、lxml 或任何你喜欢的机制),并用解析后的数据生成 Item。

  4. 最后,从 spider 返回的 Item 通常会持久化到数据库(在某些 Item Pipeline 中)或使用 Feed 导出 写入文件。

尽管这个周期(或多或少)适用于任何类型的 spider,但 Scrapy 内置了不同类型的默认 spider,用于不同的目的。我们将在这里讨论这些类型。

scrapy.Spider

class scrapy.spiders.Spider
class scrapy.Spider(*args: Any, **kwargs: Any)[source]

所有 spider 都必须继承的基类。

它提供了一个默认的 start() 实现,根据 start_urls 类属性发送请求,并为每个响应调用 parse() 方法。

name

一个字符串,定义了该 spider 的名称。spider 名称是 Scrapy 定位(并实例化) spider 的方式,因此它必须是唯一的。但是,这并不妨碍你实例化同一个 spider 的多个实例。这是最重要的 spider 属性,并且是必需的。

如果 spider 抓取单个域,一个常见的做法是将 spider 命名为该域的名称,可以包含或不包含 TLD (顶级域名)。例如,一个抓取 mywebsite.com 的 spider 通常会被命名为 mywebsite

allowed_domains

一个可选的字符串列表,包含此 spider 允许爬取的域名。如果启用了 OffsiteMiddleware,则属于此列表中指定域名(或其子域名)之外的 URL 请求将不会被跟随。

假设你的目标 url 是 https://www.example.com/1.html,那么将 'example.com' 添加到此列表。

start_urls: list[str]

起始 URL。参见 start()

custom_settings

一个设置字典,运行此 spider 时会覆盖项目范围的配置。它必须被定义为类属性,因为设置是在实例化之前更新的。

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

crawler

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

Crawler 封装了项目中的许多组件,以便进行单一入口访问(例如扩展、中间件、信号管理器等)。要了解更多信息,请参见 Crawler API

settings

运行此 spider 的配置。这是一个 Settings 实例,关于此主题的详细介绍,请参见 设置 主题。

logger

使用 Spider 的 name 创建的 Python logger。你可以使用它来发送日志消息,详见 从 Spider 记录日志

state

一个字典,你可以用来在批次之间持久化一些 spider 状态。要了解更多信息,请参见 在批次之间保持持久状态

from_crawler(crawler, *args, **kwargs)[source]

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

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

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

版本 2.11 中的变化: crawler.settings 中的设置现在可以在此方法中修改,这对于你想基于参数修改设置时很方便。因此,这些设置不是最终值,因为它们可能在之后被例如 附加组件 修改。出于同样的原因,此时大多数 Crawler 属性尚未初始化。

最终设置和初始化的 Crawler 属性可在 start() 方法、engine_started 信号的处理程序以及更晚的时候使用。

参数:
  • crawler (Crawler 实例) – spider 将绑定到的 crawler

  • args (list) – 传递给 __init__() 方法的参数

  • kwargs (dict) – 传递给 __init__() 方法的关键字参数

classmethod update_settings(settings)[source]

update_settings() 方法用于修改 spider 的设置,并在初始化 spider 实例时调用。

它接受一个 Settings 对象作为参数,可以添加或更新 spider 的配置值。此方法是一个类方法,意味着它在 Spider 类上调用,并允许所有 spider 实例共享相同的配置。

虽然可以在 custom_settings 中设置每个 spider 的设置,但使用 update_settings() 可以让你根据其他设置、spider 属性或其他因素动态添加、删除或更改设置,并使用 'spider' 之外的设置优先级。此外,在子类中通过覆盖 update_settings() 可以轻松地扩展它,而对 custom_settings 做同样的事情可能会很困难。

例如,假设一个 spider 需要修改 FEEDS

import scrapy


class MySpider(scrapy.Spider):
    name = "myspider"
    custom_feed = {
        "/home/user/documents/items.json": {
            "format": "json",
            "indent": 4,
        }
    }

    @classmethod
    def update_settings(cls, settings):
        super().update_settings(settings)
        settings.setdefault("FEEDS", {}).update(cls.custom_feed)
async start() AsyncIterator[Any][source]

生成要发送的初始 Request 对象。

版本 2.13 中新增。

例如

from scrapy import Request, Spider


class MySpider(Spider):
    name = "myspider"

    async def start(self):
        yield Request("https://toscrape.com/")

默认实现从 start_urls 读取 URL,并为每个 URL 生成一个启用 dont_filter 的请求。它在功能上等同于

async def start(self):
    for url in self.start_urls:
        yield Request(url, dont_filter=True)

你也可以生成 Item。例如

async def start(self):
    yield {"foo": "bar"}

要编写适用于 Scrapy 2.13 之前版本的 spider,还需要定义一个返回可迭代对象的同步 start_requests() 方法。例如

def start_requests(self):
    yield Request("https://toscrape.com/")

另请参阅

起始请求

parse(response)[source]

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

parse 方法负责处理响应并返回抓取的数据和/或更多要跟随的 URL。其他 Request 回调的要求与 Spider 类相同。

此方法以及任何其他 Request 回调,必须返回一个 Request 对象、一个 Item 对象、包含任何这些对象的迭代器,或者 None

参数:

response (Response) – 要解析的响应

log(message[, level, component])[source]

通过 Spider 的 logger 发送日志消息的包装器,保留用于向后兼容。要了解更多信息,请参见 从 Spider 记录日志

closed(reason)

在 spider 关闭时调用。此方法为 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)

从单个回调返回多个 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() 而不是 start_urls;为了给数据更多结构,你可以使用 Item 对象

import scrapy
from myproject.items import MyItem


class MySpider(scrapy.Spider):
    name = "example.com"
    allowed_domains = ["example.com"]

    async def start(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 参数

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

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

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 = [f"http://www.example.com/categories/{category}"]
        # ...

默认的 __init__ 方法将接受任何 spider 参数并将它们作为属性复制到 spider 中。上面的例子也可以写成如下形式

import scrapy


class MySpider(scrapy.Spider):
    name = "myspider"

    async def start(self):
        yield scrapy.Request(f"http://www.example.com/categories/{self.category}")

如果你是从脚本运行 Scrapy,可以在调用 CrawlerProcess.crawlCrawlerRunner.crawl 时指定 spider 参数

process = CrawlerProcess()
process.crawl(MySpider, category="electronics")

请记住,spider 参数仅为字符串。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

Spider 参数也可以通过 Scrapyd schedule.json API 传递。参见 Scrapyd documentation

起始请求

起始请求是 从 spider 的 start() 方法或 spider 中间件process_start() 方法生成的 Request 对象。

另请参阅

起始请求顺序

延迟起始请求迭代

你可以按如下方式覆盖 start() 方法,以便在有计划中的请求时暂停其迭代

async def start(self):
    async for item_or_request in super().start():
        if self.crawler.engine.needs_backoff():
            await self.crawler.signals.wait_for(signals.scheduler_empty)
        yield item_or_request

这有助于在任何给定时间最小化调度器中的请求数量,从而最小化资源使用(内存或磁盘,取决于 JOBDIR)。

通用爬虫

Scrapy 内置了一些有用的通用爬虫,你可以继承它们来创建你的 spider。它们旨在为一些常见的抓取场景提供便利功能,例如根据某些规则跟随网站上的所有链接,从 Sitemaps 爬取,或解析 XML/CSV feed。

对于以下 spider 中使用的示例,我们假设你有一个项目,其中在 myproject.items 模块中声明了一个 TestItem

import scrapy


class TestItem(scrapy.Item):
    id = scrapy.Field()
    name = scrapy.Field()
    description = scrapy.Field()

CrawlSpider

class scrapy.spiders.CrawlSpider[source]

这是最常用的爬虫,用于抓取常规网站,因为它通过定义一组规则提供了一种方便的跟随链接机制。它可能不是最适合你的特定网站或项目,但它对于多种情况来说足够通用,因此你可以从它开始,并根据需要覆盖它以实现更自定义的功能,或者直接实现你自己的 spider。

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

rules

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

此 spider 还公开了一个可覆盖的方法

parse_start_url(response, **kwargs)

此方法会对 spider 的 start_urls 属性中 URL 生成的每个响应调用。它允许解析初始响应,并且必须返回一个 Item 对象、一个 Request 对象,或包含其中任何一个的迭代器。

爬取规则

class scrapy.spiders.Rule(link_extractor: LinkExtractor | None = None, callback: CallbackT | str | None = None, cb_kwargs: dict[str, Any] | None = None, follow: bool | None = None, process_links: ProcessLinksT | str | None = None, process_request: ProcessRequestT | str |None = None, errback: Callable[[Failure], Any] | str |None = None)[source]

link_extractor 是一个 Link Extractor (链接提取器) 对象,它定义了如何从每个爬取的页面提取链接。每个提取的链接都将用于生成一个 Request 对象,该对象将在其 meta 字典中包含链接的文本(键为 link_text)。如果省略,将使用一个不带参数创建的默认链接提取器,这将提取所有链接。

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

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

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

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

process_request 是一个可调用对象(或一个字符串,在这种情况下,将使用 spider 对象中同名的方法),它将针对此规则提取的每个 Request 调用。此可调用对象应将该请求作为第一个参数,并将生成该请求的 Response 作为第二个参数。它必须返回一个 Request 对象或 None(以过滤掉请求)。

errback 是一个可调用对象或一个字符串(在这种情况下,将使用 spider 对象中同名的方法),用于在处理规则生成的请求时发生任何异常时调用。它接收一个 Twisted Failure 实例作为第一个参数。

警告

由于其内部实现,在编写基于 CrawlSpider 的 spider 时,您必须为新请求显式设置回调函数;否则可能发生意外行为。

添加到 2.0 版本: errback 参数。

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=(r"category\.php",), deny=(r"subsection\.php",))),
        # Extract links matching 'item.php' and parse them with the spider's method parse_item
        Rule(LinkExtractor(allow=(r"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"]
        url = response.xpath('//td[@id="additional_data"]/@href').get()
        return response.follow(
            url, self.parse_additional_page, cb_kwargs=dict(item=item)
        )

    def parse_additional_page(self, response, item):
        item["additional_data"] = response.xpath(
            '//p[@id="additional_data"]/text()'
        ).get()
        return item

此 spider 将开始爬取 example.com 的主页,收集类别链接和商品链接,并使用 parse_item 方法解析商品链接。对于每个商品响应,将使用 XPath 从 HTML 中提取一些数据,并使用这些数据填充一个 Item 对象。

XMLFeedSpider

class scrapy.spiders.XMLFeedSpider[source]

XMLFeedSpider 设计用于通过按某个节点名称迭代来解析 XML feeds。迭代器可以选择:iternodesxmlhtml。建议出于性能原因使用 iternodes 迭代器,因为 xmlhtml 迭代器会一次性生成整个 DOM 以进行解析。但是,在解析标记错误的 XML 时,使用 html 作为迭代器可能会很有用。

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

iterator

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

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

  • 'html' - 使用 Selector 的迭代器。请记住,这使用 DOM 解析,必须将所有 DOM 加载到内存中,这对于大型 feed 可能是一个问题。

  • 'xml' - 使用 Selector 的迭代器。请记住,这使用 DOM 解析,必须将所有 DOM 加载到内存中,这对于大型 feed 可能是一个问题。

默认为:'iternodes'

itertag

一个字符串,包含要迭代的节点(或元素)的名称。示例:

itertag = 'product'
命名空间

namespaces

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

然后可以在 itertag 属性中指定带命名空间的节点。

class YourSpider(XMLFeedSpider):

    namespaces = [('n', 'http://www.sitemaps.org/schemas/sitemap/0.9')]
    itertag = 'n:url'
    # ...

示例:

除了这些新属性外,此 spider 还具有以下可覆盖的方法:

adapt_response(response)[source]

一个方法,在 spider 开始解析之前,收到响应后立即被调用。它可以用于在解析响应体之前修改它。此方法接收一个响应,并返回一个响应(可以是同一个或另一个)。

parse_node(response, selector)[source]

此方法针对匹配提供的标签名称 (itertag) 的节点调用。接收响应和每个节点的 Selector 对象。覆盖此方法是强制性的。否则,您的 spider 将无法工作。此方法必须返回一个 item 对象、一个 Request 对象,或包含它们中任何一个的可迭代对象。

process_results(response, results)[source]

警告

此方法针对 spider 返回的每个结果(item 或 request)调用,其目的是在将结果返回给框架核心之前执行任何最后的处理,例如设置 item ID。它接收结果列表和生成这些结果的响应。它必须返回结果(item 或 request)列表。

由于其内部实现,在编写基于 XMLFeedSpider 的 spider 时,您必须为新请求显式设置回调函数;否则可能发生意外行为。

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

这些 spider 非常易于使用,让我们看一个示例:

基本上,我们在上面做的是创建一个 spider,它从给定的 start_urls 下载一个 feed,然后迭代其每个 item 标签,将它们打印出来,并将一些随机数据存储在 Item 中。

CSVFeedSpider

class scrapy.spiders.CSVFeedSpider[source]

此 spider 与 XMLFeedSpider 非常相似,不同之处在于它迭代行,而不是节点。每次迭代中调用的方法是 parse_row()

delimiter

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

quotechar

一个字符串,表示 CSV 文件中每个字段的包围字符。默认为 '"'(引号)。

headers

CSV 文件中的列名列表。

parse_row(response, row)[source]

接收一个响应和一个字典(代表每一行),字典的每个键对应于 CSV 文件中提供的(或检测到的)每个头部。此 spider 还提供了覆盖 adapt_responseprocess_results 方法以进行预处理和后处理的机会。

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

让我们看一个与前面类似的示例,但使用 CSVFeedSpider

SitemapSpider

class scrapy.spiders.SitemapSpider[source]

SitemapSpider 允许您通过使用 Sitemaps (网站地图) 发现 URL 来爬取网站。

它支持嵌套的 sitemaps 和从 robots.txt 中发现 sitemap url。

sitemap_urls

指向您希望爬取的 sitemaps 的 URL 列表。

您也可以指向 robots.txt 文件,它将被解析以从中提取 sitemap url。

sitemap_rules

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

  • regex 是一个正则表达式,用于匹配从 sitemaps 中提取的 URL。regex 可以是字符串或已编译的正则表达式对象。

例如

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

callback 是用于处理与正则表达式匹配的 URL 的回调函数。callback 可以是字符串(表示 spider 方法的名称)或可调用对象。

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

如果省略此属性,则在 sitemaps 中找到的所有 URL 都将使用 parse 回调函数进行处理。

sitemap_follow

应该遵循的 sitemap 的正则表达式列表。这仅适用于使用 Sitemap 索引文件 指向其他 sitemap 文件的网站。

sitemap_alternate_links

例如

<url>
    <loc>http://example.com/</loc>
    <xhtml:link rel="alternate" hreflang="de" href="http://example.com/de"/>
</url>

指定是否应遵循同一 url 的备用链接。这些是同一网站的在其他语言的链接,在同一个 url 块中传递。

如果设置了 sitemap_alternate_links,将检索这两个 URL。如果禁用 sitemap_alternate_links,则只会检索 http://example.com/

默认情况下 sitemap_alternate_links 被禁用。

sitemap_filter(entries)[source]

例如

<url>
    <loc>http://example.com/</loc>
    <lastmod>2005-01-01</lastmod>
</url>

这是一个过滤器函数,可以被覆盖以根据其属性选择 sitemap 条目。

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

我们可以定义一个 sitemap_filter 函数来按日期过滤 entries

这将只检索在 2005 年及以后修改的 entries

Entries 是从 sitemap 文档中提取的字典对象。通常,键是标签名,值是标签内的文本。

  • 需要注意的重要事项:

  • 由于 loc 属性是必需的,没有此标签的条目将被丢弃。

  • 备用链接存储在键为 alternate 的列表中(参见 sitemap_alternate_links)。

命名空间被移除,因此 lxml 中名为 {namespace}tagname 的标签仅变为 tagname

如果省略此方法,将处理在 sitemaps 中找到的所有条目,同时遵循其他属性及其设置。

SitemapSpider 示例

from scrapy.spiders import SitemapSpider


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

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

最简单的示例:使用 parse 回调函数处理通过 sitemaps 发现的所有 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 ...

使用特定的回调函数处理某些 URL,使用不同的回调函数处理其他 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 ...

遵循 robots.txt 文件中定义的 sitemaps,并且只遵循 URL 中包含 /sitemap_shop 的 sitemaps:

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"]

    async def start(self):
        async for item_or_request in super().start():
            yield item_or_request
        for url in self.other_urls:
            yield Request(url, self.parse_other)

    def parse_shop(self, response):
        pass  # ... scrape shop here ...

    def parse_other(self, response):
        pass  # ... scrape other here ...