首页
/ Scrapy Spiders 深度指南:Spider 基类、Spider 参数、Start Requests 与内置通用 Spiders

Scrapy Spiders 深度指南:Spider 基类、Spider 参数、Start Requests 与内置通用 Spiders

2026-09-06 13:02:39作者:邵娇湘

Spider(爬虫)是 Scrapy 中定义“如何抓取一个站点或一组站点”的核心类:它决定发送哪些请求、如何解析响应以提取数据,并据此发出后续请求。理解 Spider 的基类属性(nameallowed_domainsstart_urlscustom_settings)、Spider 参数传递机制(-a 选项)、Start requests 的生命周期(惰性迭代、异常处理),以及 CrawlSpiderXMLFeedSpiderCSVFeedSpiderSitemapSpider 四类内置通用 Spider,是编写任何 Scrapy 爬虫项目的基础。本文基于 Scrapy 仓库的官方文档 docs/topics/spiders.rst 展开,并结合 scrapy/spiders/ 下的源码实现给出纵深解析。

Scrapy 架构图中 Spiders 组件位于 Engine 之下,与 Scheduler、Downloader、Item Pipeline 协同工作

Spider 与抓取流程

Spider 是 Scrapy 架构中位于 Spider Middlewares 之下的组件,由引擎(Engine)驱动。一个完整的抓取流程如下(与 docs/topics/spiders.rst 中的描述一致):

  1. Scrapy 迭代 Spider 的 start() 方法来获取初始请求。默认实现会为 start_urls 中的每个 URL 产出一个 scrapy.Request 对象,并以 parse 方法作为回调(callback)。
  2. Scrapy 下载每个请求,并用产生的 scrapy.http.Response 调用其回调。
  3. 回调解析响应(通常使用 Selector),返回或 yield Item 对象(携带提取的数据)以及用于继续抓取的 scrapy.Request,这些请求回到第 2 步。
  4. Item 经过 Item Pipeline,通常最终通过 Feed Exports 落盘存储。

scrapy.Spider 基类

所有 Spider 都必须继承 Spider 基类。从源码看,其定义位于 scrapy/spiders/init.py#L33,文档中的完整 API 描述在 docs/topics/spiders.rst

类属性

name:定义 Spider 名称的字符串。Spider 名称是 Scrapy 定位(并实例化)Spider 的依据,因此必须唯一——这是 Spider 最重要且必填的属性。如果 Spider 只抓取单个域名,常见做法是用域名(带或不带 TLD)命名,例如抓取 mywebsite.com 的 Spider 常命名为 mywebsite。从源码看,名称校验发生在构造函数中(scrapy/spiders/init.py#L47-L54):若既没有传入 name 也没有类属性 name,会抛出 ValueError

allowed_domains:可选的域名列表,限定该 Spider 允许抓取的域名。只要 OffsiteMiddleware 处于启用状态,不在此列表(或其子域)中的 URL 对应的请求就不会被跟随。以目标 URL https://www.example.com/1.html 为例,列表中只需加入 'example.com'。自 2.18.0 起,抓取运行期间修改该属性也会生效,变更会影响之后调度的请求——例如可以动态允许“只从先前响应中才得知”的域名。

从源码看,OffsiteMiddleware 通过监听 request_scheduled 信号逐请求过滤(scrapy/downloadermiddlewares/offsite.py#L79-L101):每个请求调度时触发 process_request,若请求未设 dont_filter、meta 中未设 allow_offsite,且 should_follow() 判定为站外,则记录 offsite/filtered 统计并抛出 IgnoreRequest。注意子域匹配语义是精确的后缀匹配:www.example.org 允许 bob.www.example.org,但不允许 www2.example.org

start_urls:起始 URL 列表。默认的 start() 实现会为其中每个 URL 发出请求。

custom_settings:一个字典,在运行该 Spider 时覆盖项目级配置。由于设置在实例化之前就已更新,它必须定义为类属性。内置设置完整清单见 Settings 参考

crawler:由 from_crawler 类方法在类初始化后设置的属性,指向该 Spider 实例所绑定的 scrapy.crawler.Crawler 对象。Crawler 封装了项目的大量组件(extensions、middlewares、信号管理器等)供单一入口访问,详见 Crawler API

settings:运行该 Spider 的配置,是一个 scrapy.settings.Settings 实例,详细介绍见 Settings 主题

logger:用 Spider 的 name 创建的 Python logger,可通过它发送日志(见 日志文档)。从源码看,logger 实际是一个 property(scrapy/spiders/init.py#L56-L62),每次访问都会返回一个绑定了 spider 字段的 SpiderLoggerAdapter,因此日志中会自动带上 spider 上下文。

state:一个 dict,可用于在不同批次(batch)之间持久化 Spider 状态(与 JOBDIR 断点续爬机制配合,见 jobs 文档)。

from_crawler 类方法

from_crawler(crawler, *args, **kwargs) 是 Scrapy 用来创建你 Spider 的类方法。默认实现是 __init__ 的代理:用给定的 argskwargs 调用 __init__,但会在新实例上设置 crawlersettings 属性,以便之后在 Spider 代码中访问。实现见 scrapy/spiders/init.py#L78-L87

@classmethod
def from_crawler(cls, crawler, *args, **kwargs):
    spider = cls(*args, **kwargs)
    spider._set_crawler(crawler)
    return spider

def _set_crawler(self, crawler):
    self.crawler = crawler
    self.settings = crawler.settings
    crawler.signals.connect(self.close, signals.spider_closed)

自 2.11 起,from_crawler 中可以修改 crawler.settings(例如基于参数动态调整),方便按参数定制配置。但要注意:此时这些设置不是最终值,之后仍可能被 add-ons(add-ons 文档)修改;同理,Crawler 的大部分属性在此时还未初始化。最终设置和已初始化的 Crawler 属性要到 start 方法、engine_started 信号处理器及之后才可用。

update_settings 类方法

update_settings(settings) 用于修改 Spider 的设置,在 Spider 实例初始化期间被调用。它接收一个 Settings 对象,可以添加或更新该 Spider 的配置值。作为类方法,它作用于 Spider 类,允许该 Spider 的所有实例共享同一份配置。

custom_settings 相比,update_settings() 的优势是:可以基于其他设置、Spider 属性或其他因素动态地添加、删除、修改设置,并使用 'spider' 之外的设置优先级;在子类中扩展 update_settings() 也比覆盖 custom_settings 更容易。源码实现非常简洁(scrapy/spiders/init.py#L166-L168):

@classmethod
def update_settings(cls, settings):
    settings.setdict(cls.custom_settings or {}, priority="spider")

即默认行为就是把 custom_settings'spider' 优先级写入。示例——假设 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)

start 与 parse

start():异步生成器方法,yield 出初始的 Request 对象(也可以 yield Item)。文档给出的默认实现说明与源码完全一致(scrapy/spiders/init.py#L135-L136):

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

也就是说默认请求带有 dont_filter=True,起始 URL 不会被去重过滤器拦截。

parse(response, **kwargs):默认的回调方法。start() 产出的请求未显式指定 callback 时,响应会交给 parse 处理;请求的 cb_kwargs 会作为关键字参数传入。除非该 Spider 发出的每个请求都定义了 callback,否则必须定义 parse。源码中它直接抛出 NotImplementedErrorscrapy/spiders/init.py#L145-L164),提示 parse callback is not defined

closed(reason):Spider 关闭时被调用,等价于为 spider_closed 信号注册处理器。源码中 close 静态方法在 spider_closed 信号触发时查找并调用 Spider 上定义的 closed 可调用对象(scrapy/spiders/init.py#L174-L179)。

基础示例

一个最基础的 Spider(同时展示了 nameallowed_domainsstart_urlsparse):

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_urls 而直接重写 start();为了给数据更强的结构,可以使用 scrapy.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 arguments)

Spider 可以接收修改其行为的外部参数。常见用途包括定义起始 URL、把抓取限制到站点的某些板块,但也可以用来配置 Spider 的任何功能。

命令行 -a 选项

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().__init__(*args, **kwargs)
        self.start_urls = [f"http://www.example.com/categories/{category}"]
        # ...

默认的 __init__ 方法会把任意 Spider 参数作为属性拷贝到 Spider 实例上(见 scrapy/spiders/init.py#L52self.__dict__.update(kwargs)),因此上面的示例也可以不重写 __init__ 而直接写:

import scrapy


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

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

从源码看,-a 选项在 BaseRunSpiderCommand 中定义并可重复使用(scrapy/commands/init.py#L189-L196),process_optionsarglist_to_dictNAME=VALUE 列表解析成字典(scrapy/commands/init.py#L214-L221),随后 scrapy/commands/crawl.py#L31 将其作为关键字参数展开传给 crawler_process.crawl()

self.crawler_process.crawl(self._create_crawler(spname), **opts.spargs)

最终这些参数经 from_crawler 进入 __init__**kwargs

从脚本运行时传参

如果通过脚本运行 Scrapy,可以在调用 CrawlerProcess.crawl()CrawlerRunner.crawl() 时指定 Spider 参数:

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

只接收字符串

务必记住:Spider 参数只可能是字符串。Spider 本身不会做任何解析。如果你想在命令行设置 start_urls 属性,必须自己用 ast.literal_evaljson.loads 之类的工具把它解析成列表再赋给属性;否则会导致对 start_urls 字符串做逐字符迭代(一个常见的 Python 陷阱),每个字符都被当作一个单独的 URL。

Spider 参数也可以经 Scrapyd 的 schedule.json API 传递。

scrapy-spider-metadata 参数

另一种传递 Spider 参数的选择是第三方库 scrapy-spider-metadata:它允许 Scrapy Spider 以 Pydantic 模型的方式定义、校验、记录并预处理参数。示例展示了带类型的参数——字符串参数会自动转换为整数:

import scrapy
from pydantic import BaseModel
from scrapy_spider_metadata import Args


class MyParams(BaseModel):
    pages: int


class BookSpider(Args[MyParams], scrapy.Spider):
    name = "bookspider"
    start_urls = ["http://books.toscrape.com/catalogue"]

    async def start(self):
        for start_url in self.start_urls:
            for index in range(1, self.args.pages + 1):
                yield scrapy.Request(f"{start_url}/page-{index}.html")

    def parse(self, response):
        book_links = response.css("article.product_pod h3 a::attr(href)").getall()
        for book_link in book_links:
            yield response.follow(book_link, self.parse_book)

    def parse_book(self, response):
        yield {
            "title": response.css("h1::text").get(),
            "price": response.css("p.price_color::text").get(),
        }

该 Spider 可以从命令行这样调用:

scrapy crawl bookspider -a pages=2

Start requests

Start requests 是从 Spider 的 start() 方法,或从 Spider 中间件的 process_start 方法(见 Spider Middleware)yield 出来的 Request 对象。

延迟 start 请求的迭代

Scrapy 以 yield 的速度尽可能快地迭代 start(),所以无论起始请求有多少,它们都会尽早全部进入调度器(Scheduler)。为了在任何时刻最小化调度器中的请求数量、以及由此带来的资源占用(内存,或使用 JOBDIR 时的磁盘占用),可以重写 start(),在存在已调度请求时暂停迭代:

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

源码级佐证:needs_backout() 定义于 scrapy/core/engine.py#L362,它汇总 Scraper 与 Downloader 两层的背压状态——Scraper 侧的判断是 active_size > max_active_sizescrapy/core/scraper.py#L102-L103),Downloader 侧则是总并发已满(scrapy/core/downloader/init.py#L127-L129)。当调度器取不出下一个请求时,引擎会发送 scheduler_empty 信号(scrapy/core/engine.py#L385-L388),signals.wait_for() 的实现见 scrapy/signalmanager.py#L104。两者配合即可实现“调度器空了才继续喂请求”的惰性流。

start 错误处理

start() 中抛出的异常会终止整个迭代,因此剩余的 start items 和 requests 永远不会被发送。Scrapy 会记录该异常日志、发送 spider_error 信号,并在已调度请求处理完毕后以 start_error 作为 finish_reason 统计值关闭 Spider。从源码看,引擎捕获 start 迭代中的异常后设置 _start_error = Truescrapy/core/engine.py#L292-L298),关闭时据此选择关闭原因(scrapy/core/engine.py#L612-L616):

default_reason = "start_error" if self._start_error else "finished"

(自 2.18.0 起行为如此:此前关闭原因是 finished,且既不发送 spider_error 信号也不上报 spider_exceptions/count 统计。)

要保持迭代继续,就自己捕获异常:

async def start(self):
    for url in self.start_urls:
        try:
            request = Request(url)
        except ValueError:
            self.logger.exception(f"Skipping start URL {url}")
        else:
            yield request

若想直接停止抓取、并自定义 finish_reason,则抛出 scrapy.exceptions.CloseSpider

内置通用 Spiders(Generic Spiders)

Scrapy 内置了几类通用 Spider,目标是针对几种常见抓取场景提供便利功能:按规则跟随站点所有链接、从 Sitemap 抓取、解析 XML/CSV feed。它们的源码分别位于 scrapy/spiders/crawl.pyscrapy/spiders/feed.pyscrapy/spiders/sitemap.py,并通过 scrapy/spiders/init.py#L186-L196scrapy.spiders 统一导出。

以下示例假设项目中有一个 myproject.items 模块,声明了 TestItem

from dataclasses import dataclass


@dataclass
class TestItem:
    id: str | None = None
    name: str | None = None
    description: str | None = None

CrawlSpider

CrawlSpider 是抓取常规网站最常用的 Spider,它通过定义一组规则(rules)来提供便捷的链接跟随机制。它未必最适合你的特定网站或项目,但足够通用,你可以从它开始并按需覆盖,也可以直接自己实现 Spider。

除继承自 Spider 的属性外,它新增一个属性:

  • rules:一个由一个或多个 Rule 对象组成的列表。每个 Rule 定义一种抓取行为,Rule 对象见下文。如果多条规则匹配同一个链接,按在 rules 中定义的顺序使用第一条匹配的规则

Rule meta 键:从 rules 生成的请求会将其匹配规则在 rules 中的索引存入 Request.metarule 键。CrawlSpider 依赖该键把响应派发到正确的规则,因此把 rule 键复制到由其他规则生成的请求上,会导致响应被派发到错误的 callback。源码中这一机制一目了然(scrapy/spiders/crawl.py#L133-L139):

def _build_request(self, rule_index, link):
    return Request(
        url=link.url,
        callback=self._callback,
        errback=self._errback,
        meta={"rule": rule_index, "link_text": link.text},
    )

响应回来时 _callbackresponse.meta["rule"] 反查规则,再调用 parse_with_rules()scrapy/spiders/crawl.py#L156-L163)。

此外 CrawlSpider 暴露一个可覆盖的方法:

  • parse_start_url(response, **kwargs):对 start_urls 中每个 URL 产生的响应调用,用于解析初始响应,必须返回 Item、Request 或包含它们的可迭代对象。

抓取规则(Rule)各参数

Rule 构造函数签名见 scrapy/spiders/crawl.py#L64-L82,各参数含义:

  • link_extractor:一个 Link Extractor 对象,定义如何从每个抓取的页面提取链接。每个产出的链接都会生成一个 Request,其中包含链接文本(存于 meta 字典的 link_text 键)。省略时使用无参创建的默认 Link Extractor,即提取所有链接(源码中为模块级单例 _default_link_extractor)。
  • callback:可调用对象或字符串(字符串表示 Spider 对象上同名方法),用于处理该链接提取器提取的每个链接,接收 Response 作为第一个参数,必须返回单个或可迭代的 Item 和/或 Request(或其子类)对象。收到的 Responsemeta 中带有产生该请求的链接文本(link_text 键)。
  • cb_kwargs:传给回调函数的关键字参数字典。
  • follow:布尔值,指定是否跟随从该规则提取的每个响应中的链接。若 callbackNonefollow 默认为 True,否则默认为 False——源码中即 follow if follow is not None else not callbackscrapy/spiders/crawl.py#L82)。
  • process_links:可调用对象或字符串(字符串表示 Spider 方法),对每个响应用指定 link_extractor 提取的链接列表调用,主要用于过滤目的。
  • process_request:可调用对象(或字符串),对该规则提取的每个 Request 调用;第一个参数是该请求,第二个是产生它的 Response;必须返回 RequestNone(用于过滤掉该请求)。
    • 可以用 process_request 为规则生成的请求设置 priority,例如 process_request=lambda request, response: request.replace(priority=10)
  • errback:可调用对象或字符串,当规则生成的请求处理过程中抛出异常时被调用,接收 Twisted Failure 实例作为第一个参数。

警告:由于内部实现的原因,编写基于 CrawlSpider 的 Spider 时,必须为新请求显式设置 callback,否则可能出现意外行为。

从源码看,规则在实例化时会被 _compile_rules 逐个浅拷贝并编译(把方法名字符串替换为真实方法,scrapy/spiders/crawl.py#L215-L220);链接去重逻辑在 _requests_to_follow 中用 seen 集合实现——同一个链接只会被最先匹配的规则处理(scrapy/spiders/crawl.py#L141-L154)。

CrawlSpider 示例

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 = {}
        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 首页开始抓取,收集 category 链接与 item 链接,并用 parse_item 方法解析后者。对每个 item 响应,用 XPath 从 HTML 提取数据填充字典。

XMLFeedSpider

XMLFeedSpider 用于解析 XML feed,按特定节点名迭代。迭代器可选:iternodesxmlhtml。出于性能考虑推荐使用 iternodes,因为 xmlhtml 迭代器会先生成整个 DOM 再解析。不过对标记不良(bad markup)的 XML,使用 html 迭代器可能有用。

必须定义的类属性:

  • iterator:定义使用的迭代器:

    • 'iternodes' —— 基于 lxml 的快速迭代器;
    • 'html' —— 使用 scrapy.Selector 的迭代器。注意它使用 DOM 解析、必须把全部 DOM 载入内存,大 feed 上可能是问题。它还会用 HTML 解析器解析 feed,可能静默破坏 HTML 视为空元素(void element)的标签(如 <link>),丢弃其内容与结束标签——受此影响的 feed 请改用 xmliternodes
    • 'xml' —— 使用 scrapy.Selector 的迭代器。同样使用 DOM 解析、全部 DOM 载入内存,大 feed 上可能是问题。

    默认值:'iternodes'(源码默认值见 scrapy/spiders/feed.py#L33)。

  • itertag:要迭代的节点(元素)名字符串,例如 itertag = "product"

  • namespaces(prefix, uri) 元组列表,定义文档中可用、将被本 Spider 处理的命名空间。prefixuri 会通过 Selector.register_namespace 方法自动注册命名空间,之后即可在 itertag 属性中指定带命名空间的节点:

from scrapy.spiders import XMLFeedSpider


class YourSpider(XMLFeedSpider):

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

除新属性外,该 Spider 还有以下可覆盖方法(实现见 scrapy/spiders/feed.py#L37-L103):

  • adapt_response(response):响应从 Spider 中间件到达、Spider 开始解析之前调用。可用于解析前修改响应 body。接收一个响应并返回一个响应(可以相同,也可以是另一个)。
  • parse_node(response, selector):对匹配 itertag 的每个节点调用,接收响应和该节点的 scrapy.Selector必须覆盖此方法,否则 Spider 不工作。必须返回 Item、Request 或包含它们的可迭代对象。
  • process_results(response, results):对 Spider 返回的每个结果(item 或 request)调用,用于在把结果交回框架核心前做最后的处理,例如设置 item ID。接收结果列表和产生这些结果的响应,必须返回结果列表。

警告:由于内部实现的原因,编写基于 XMLFeedSpider 的 Spider 时,必须为新请求显式设置 callback,否则可能出现意外行为。

从源码看,三种迭代器分支在 _parse 中分发(scrapy/spiders/feed.py#L78-L98):iternodesxmliter_lxml 流式迭代;xml/html 则构建完整 Selector 后执行 xpath(f"//{self.itertag}"),这解释了“大 feed 内存开销”警告的来源。xmlhtml 迭代器要求响应是 TextResponse,否则抛出 ValueError

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 从给定的 start_urls 下载 feed,然后迭代其中的每个 item 标签,打印并保存一些数据到 Item 中。相关测试可参考 tests/test_spider.py 中的 TestXMLFeedSpider(包括命名空间注册的用例)。

CSVFeedSpider

CSVFeedSpiderXMLFeedSpider 非常相似,只是迭代行(rows)而不是节点;每次迭代调用的方法是 parse_row。属性(见 scrapy/spiders/feed.py#L110-L157):

  • delimiter:CSV 文件中每个字段的分隔符,默认 ','(逗号)。
  • quotechar:CSV 文件中每个字段的包裹(enclosure)字符,默认 '"'(引号)。
  • headers:CSV 文件列名列表。
  • parse_row(response, row):接收响应和一个 dict(代表每一行),该 dict 以 CSV 文件提供(或检测到)的每个 header 为键。该 Spider 同样可以覆盖 adapt_responseprocess_results 做预处理与后处理。

从源码看,行迭代由 csviter 工具完成,parse_row 的返回值统一经 process_results 后交回框架(scrapy/spiders/feed.py#L148-L153):

def parse_rows(self, response):
    for row in csviter(response, self.delimiter, self.headers, quotechar=self.quotechar):
        ret = iterate_spider_output(self.parse_row(response, row))
        yield from self.process_results(response, ret)

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

SitemapSpider 允许通过发现 Sitemap 中的 URL 来抓取站点。它支持嵌套 Sitemap,并能从 robots.txt 中发现 Sitemap URL。

  • sitemap_urls:指向要抓取其 URL 的 Sitemap 的 URL 列表。也可以指向 robots.txt,此时会解析它以提取 Sitemap URL。
  • sitemap_rules(regex, callback) 元组列表,其中 regex 是匹配从 Sitemap 提取的 URL 的正则(str 或编译后的 regex 对象),callback 是处理匹配 URL 的回调(方法名字符串或可调用对象)。例如:
sitemap_rules = [("/product/", "parse_product")]

规则按顺序应用,只有第一条匹配的规则生效。省略该属性时,Sitemap 中发现的所有 URL 都会用 parse 回调处理(源码默认值即 [("", "parse")],见 scrapy/spiders/sitemap.py#L28-L30)。

  • sitemap_follow:应跟随的 Sitemap 的正则列表,仅针对使用 Sitemap index 文件(指向其他 Sitemap 文件)的站点。默认跟随所有 Sitemap(源码默认 [""])。
  • sitemap_alternate_links:指定是否跟随一个 url 的 alternate 链接(同一网站其他语言的链接,在同一个 url 块中传递)。例如:
<url>
    <loc>http://example.com/</loc>
    <xhtml:link rel="alternate" hreflang="de" href="http://example.com/de"/>
</url>

启用 sitemap_alternate_links 时会同时抓取两个 URL;禁用时只抓取 http://example.com/。默认禁用。

  • sitemap_filter(entries):可覆盖的过滤函数,按属性筛选 Sitemap 条目。条目是从 Sitemap 文档提取的 dict 对象,键通常是标签名,值是其中的文本。例如按日期过滤:
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 年及以后修改的条目。注意:

  • loc 属性是必需的,没有该标签的条目会被丢弃;
  • alternate 链接以键 alternate 存为列表(见 sitemap_alternate_links);
  • 命名空间会被剥除,lxml 中形如 {namespace}tagname 的标签名只剩 tagname

省略该方法时,Sitemap 中发现的所有条目都会被处理(仍遵循其他属性及其设置)。

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,且只跟随 URL 包含 /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 与其他 URL 来源组合:

from scrapy import Request
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 ...

从源码看,SitemapSpiderstart() 只需对 sitemap_urls 逐条发出指向 _parse_sitemap 的请求(scrapy/spiders/sitemap.py#L56-L58);_parse_sitemap 会自动识别 robots.txt(提取其中的 Sitemap URL)、Sitemap index(递归请求子 Sitemap)与 urlset(按 sitemap_rules 匹配并派发回调)三种形态(scrapy/spiders/sitemap.py#L69-L101)。此外它还内建了 gzip Sitemap 的解压逻辑(含基于 DOWNLOAD_MAXSIZE/DOWNLOAD_WARNSIZE 的解压大小保护,见 scrapy/spiders/sitemap.py#L119-L150),相关解析工具 Sitemapsitemap_urls_from_robots 位于 scrapy/utils/sitemap.py

小结

  • spiders 包对外导出 SpiderCrawlSpiderRuleXMLFeedSpiderCSVFeedSpiderSitemapSpider 六类(scrapy/spiders/init.py#L190-L197),覆盖了从“自定义规则式站点遍历”到“Sitemap/feed 驱动”的主要抓取形态。
  • 写 Spider 时牢记三条主线:用 name/allowed_domains/start_urls(或重写 start())声明抓取范围;用 -a 参数或 from_crawler 注入运行期配置;用 parse 等回调产出 Item 与新 Request 驱动闭环。
  • 需要站点级配置时优先 custom_settings,需要按条件动态改设置时覆盖 update_settings();需要大规模起始请求时利用 needs_backout() + scheduler_empty 实现惰性迭代;start() 中会失败的 URL 要自行 try/except,否则整条 start 迭代会提前终止。
  • 更细粒度的行为(深度限制、Referer 处理、start 请求过滤等)由 Spider Middleware 层完成,见 Spider Middleware 文档;通用 Spider 的具体行为测试可参考 tests/test_spider.pytests/test_spider_crawl.pytests/test_spider_sitemap.py
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388