首页
/ Scrapy 官方 FAQ 精解:从代理配置、内存优化到流式解析的实战答案与源码依据

Scrapy 官方 FAQ 精解:从代理配置、内存优化到流式解析的实战答案与源码依据

2026-09-03 16:06:59作者:平淮齐Percy

本篇技术指南完整覆盖 Scrapy 官方文档 FAQ 中的所有高频问题:Scrapy 与 BeautifulSoup/lxml 的定位差异、HTTP/SOCKS 代理与 Basic Auth 的接入方式、OffsiteMiddleware 的域名过滤机制与内存优化、爬取顺序、Cookie 调试、流式解析大型 XML/CSV 数据源、runspider 免项目运行、数据导出命令,以及状态码 999 限速、StopDownload、IPv6 支持等进阶话题。读完之后,你可以带着源码级依据(每个结论都标注了对应的实现文件路径)回答生产环境中遇到的绝大多数 Scrapy 疑问。

Scrapy 与 BeautifulSoup、lxml 是什么关系?

FAQ 的第一个经典问题就是:Scrapy 相比 BeautifulSoup 或 lxml 有什么优势?官方的答案是——二者不在同一个层次上,没有可比性

  • BeautifulSouplxmlHTML/XML 解析库
  • Scrapy 是编写 Web 爬虫、执行网站爬取并提取数据的应用框架

因此,把 BeautifulSoup 与 Scrapy 相比,就像把 jinja2Django 相比:一个是底层组件,一个是完整框架。Scrapy 内置了自己的数据提取机制——selectors(选择器);但如果你更习惯 BeautifulSoup 或 lxml,完全可以在 Scrapy 回调里直接导入使用——它们只是普通的 Python 解析库。

官方给出的示例 spider(见 docs/faq.rst):

from bs4 import BeautifulSoup
import scrapy


class ExampleSpider(scrapy.Spider):
    name = "example"
    allowed_domains = ["example.com"]
    start_urls = ("http://www.example.com/",)

    def parse(self, response):
        # use lxml to get decent HTML parsing speed
        soup = BeautifulSoup(response.text, "lxml")
        yield {"url": response.url, "title": soup.h1.string}

注意示例中指定 "lxml" 作为解析后端,目的是获得更接近 lxml 的解析速度。需要提醒的是,BeautifulSoup 支持多种 HTML/XML 解析器,具体可用哪些取决于你在运行环境中安装了哪些解析器包,选择哪种解析器请以 BeautifulSoup 官方文档为准。

一个顺带的说明:Scrapy 借鉴了 Django 的设计

FAQ 中“Did Scrapy steal X from Django?”一节的立场是:如果某个问题已经有人做得很好,就没必要重新发明轮子。Django 是 Scrapy 的设计灵感来源之一,这种“复用已被验证的开源设计”的思路同样适用于文档、流程与规范。

代理配置实战:HTTP 代理、Basic Auth 与 SOCKS

HTTP 代理

FAQ 确认 Scrapy 支持 HTTP 代理,能力由 HTTP Proxy 下载器中间件提供,实现位于 HttpProxyMiddleware。从源码可以看出它的完整行为:

  • 中间件在初始化时通过 urllib.request.getproxies() 读取操作系统级环境变量代理(如 http_proxy),并跳过无法解析的值(源码注释提到像 /var/run/docker.sock 这类值会被静默忽略);
  • 支持按请求指定代理:只要在 request.meta 中放入 "proxy" 键即可覆盖系统代理;
  • 代理 URL 中可以内嵌 user:password,中间件会将其编码为 Base64 的 Proxy-Authorization 头,即天然支持 HTTP Basic Authentication_basic_auth_header 方法)。FAQ 中“Can I use Basic HTTP Authentication in my spiders?”一节的正解就是 HttpAuthMiddleware 与这里对代理自身鉴权的处理;
  • 可通过设置 HTTPPROXY_ENABLED 整体禁用该中间件,HTTPPROXY_AUTH_ENCODING 可调整代理凭据的编码方式(默认 latin-1)。

SOCKS 代理

FAQ 明确:SOCKS 代理支持仅在 HttpxDownloadHandler 下可用。从源码看,当代理以 socks 开头而运行环境缺少 SOCKS 支持时,处理器会直接报错,提示需要安装 httpx2[socks] 扩展。因此实际使用 SOCKS 的前置条件是:切换到 httpx 下载处理器 + 安装 httpx2[socks] 依赖。

域名过滤:"Filtered offsite request" 到底意味着什么?

消息机制

日志里出现 "Filtered offsite request" 消息不一定代表出了问题——这些消息以 DEBUG 级别记录,来自默认启用的 OffsiteMiddleware。它的作用是过滤掉 spider 覆盖范围之外的域名请求。

源码揭示了几个 FAQ 没有明说的细节(见 scrapy/downloadermiddlewares/offsite.py):

  • 匹配规则:若 spider 定义了 allowed_domains,中间件会把域名列表拼接成正则 ^(.*\.)?(domain1|domain2)$。也就是说 www.example.org 会放行其子域名 bob.www.example.org,但不会放行 www2.example.org 或裸域 example.org;若 spider 未定义 allowed_domains,则所有请求都放行;
  • 日志去重:每个被过滤的域名只记录第一条,避免日志被刷屏;被过滤请求会计入统计项 offsite/filteredoffsite/domains
  • 豁免机制:设置了 request.dont_filter = Truerequest.meta["allow_offsite"] = True 的请求不受过滤;
  • 子类可覆写 should_follow 方法实现自定义策略(如只允许根域、不允许子域)。

allowed_domains 过长导致内存告警?

FAQ 专门解答:当 allowed_domains 列表非常长(例如 5 万+ 个域名)时,默认中间件把它们拼接成复杂正则再编译,会占用可观内存。官方建议替换为自定义下载器中间件

  1. 如果域名形态相似,直接自己维护一个正则表达式;
  2. 如果允许额外依赖,可以考虑用 pyre2 这类第三方正则引擎替代标准库 re 来编译 URL 过滤正则。

替换时必须显式禁用默认中间件,配置写法:

DOWNLOADER_MIDDLEWARES = {
    "scrapy.downloadermiddlewares.offsite.OffsiteMiddleware": None,
    "myproject.middlewares.CustomOffsiteMiddleware": 50,
}

爬取顺序、登录模拟与项目内数据拼接

  • 爬取顺序:Scrapy 默认按深度优先(depth-first) 方式爬取,如需其他顺序(如广度优先),需要借助扩展或自定义调度器/队列实现。
  • 模拟用户登录:官方指引见 request-response 文档中的登录小节,核心思路是通过 FormRequest 提交登录表单并利用 Cookie 自动维护保持会话。
  • 跨页面属性拼接(item 拆到多页):FAQ 指向 callback-data 一节,通用做法是利用请求回调参数(cb_kwargs)或 Request.meta 携带已采集的部分数据,在后续页面回调中合并。

在 item pipeline 里把一个 item 拆成多个?

FAQ 明确指出:item pipeline 不能对单个输入 item 产出多个 item,正确做法是编写一个 spider middleware,利用其 process_spider_output 方法(自定义中间件的写法见 spider-middleware 文档)。官方示例:

from copy import deepcopy

from itemadapter import ItemAdapter
from scrapy import Request


class MultiplyItemsMiddleware:
    def process_spider_output(self, response, result):
        for item_or_request in result:
            if isinstance(item_or_request, Request):
                yield item_or_request
                continue
            adapter = ItemAdapter(item_or_request)
            for _ in range(adapter["multiply_by"]):
                yield deepcopy(item_or_request)

这个例子按 item 中的 multiply_by 字段把一条记录复制成多条,展示了 process_spider_outputRequest 与 item 混合流的统一处理模式——请求原样放行,item 则复制后逐个产出。

控制下载行为:状态码 999、提前取消下载与空白请求

状态码 999:站点在对你限速

部分站点(如早期的某些新闻站)会返回自定义状态码 999 来限流。FAQ 建议:对受影响域名把下载延迟提高到 2 秒或更高。两种方式:

# 按域名差异化延迟
DOWNLOAD_SLOTS = {
    "example.com": {"delay": 2},
}

或者用全局设置 DOWNLOAD_DELAY 统一设置项目延迟。

在收到完整响应前取消下载

某些场景下,光看响应头或响应体前几个字节就能判断是否需要完整内容,此时可以注册 bytes_receivedheaders_received 信号处理器,在其中抛出 StopDownload 异常来中止下载,节省带宽和内存。详见 request-response 文档中的 stop-response-download 小节

构造一个"空白请求"

from scrapy import Request

blank_request = Request("data:,")

这里使用 data: URI 方案、内容为空,等价于一个不带具体内容的内联数据请求。该方案不发起真实网络连接,在需要占位请求或测试数据流时很实用。

大型 XML/CSV 数据源的流式解析

FAQ 提醒:用 XPath 选择器解析大型 feed 有问题,因为选择器需要把整个 feed 构建进内存 DOM,既慢又吃内存。正确做法是使用 Scrapy 提供的流式迭代器:

  • scrapy.utils.iterators.xmliter_lxml
  • scrapy.utils.iterators.csviter

事实上,XMLFeedSpiderCSVFeedSpider 底层用的就是这两个函数。从 scrapy/utils/iterators.py 的源码可以看到实现要点:

  • xmliter_lxml 基于 lxml.etree.iterparse,以 end 事件驱动逐节点产出,并对每个处理完的节点调用 node.clear() 释放内存,实现真正的流式处理;
  • csviter 基于标准库 csv.reader 逐行迭代,未提供表头时默认取第一行作为键;行长度与表头不一致的数据行会被跳过并记录告警。

函数签名(可直接照抄使用):

xmliter_lxml(obj, nodename, namespace=None, prefix="x")  # 产出 Selector 迭代器
csviter(obj, delimiter=None, headers=None, encoding=None, quotechar=None)  # 产出 dict 迭代器

obj 都可以是 Response 对象、Unicode 字符串或 utf-8 编码的字节串。

Cookie:自动管理 + 一行设置打开调试

Scrapy 像普通浏览器一样自动接收并跟踪服务器下发的 Cookie,并在后续请求中带回。想观察 Cookie 的收发过程,只需打开设置:

COOKIES_DEBUG = True

对应实现见 CookiesMiddlewarefrom_crawlerCOOKIES_DEBUG 读取开关,之后 _debug_cookie_debug_set_cookie 两个方法分别以 DEBUG 级别记录请求携带的 Cookie响应下发的 Set-Cookie。更多机制详见 cookies 文档

命令行实战:runspider、数据导出与调试

不建项目直接跑 spider

FAQ 确认可以用 runspider 命令免项目运行。若 spider 写在 my_spider.py 中:

scrapy runspider my_spider.py

runspider 命令 的实现把 spider 文件所在目录临时插入 sys.path 后导入模块,再用 DummySpiderLoader 启动,无需 scrapy.cfg

FAQ 同时提醒一个已知陷阱:出现 error: No spider found in file: <filename> 时,通常是你的 spider 模块名与 Python 标准库或已安装包重名(如 csv.pyos.py),导致导入的不是你的文件。

最简单的数据导出方式

crawl 命令的 -O 参数可以直接把抓取结果导出为指定格式,按文件扩展名自动选择导出器

scrapy crawl myspider -O items.json   # JSON
scrapy crawl myspider -O items.csv    # CSV
scrapy crawl myspider -O items.xml    # XML

完整的 Feed 导出机制(存储方案、批处理、后处理等)见 feed-exports 文档。关于"能否用 JSON 导出超大结果",FAQ 的答案是视数据规模而定,并指向 exporters 文档中的大体积 JSON 警告

调试:pdb 还是 Scrapy shell?

可以在 spider 里调用 pdb.set_trace(),但官方更推荐 Scrapy shell:它能快速检查(甚至修改)spider 正在处理的响应,通常比裸 pdb 更实用。用法与"检查特定响应"的完整流程见 shell 文档

让 spider 自己停下来

在回调中抛出 CloseSpider 异常即可优雅关闭 spider,例如按条件主动结束爬取。

其他高频问题的准确答案

语言不是母语却总是拿到英文页面?

服务器按请求的 Accept-Language 头返回对应语言页面。覆盖默认请求头即可:

DEFAULT_REQUEST_HEADERS = {
    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.5",
}

spider 参数还是 settings?

两者都能配置 spider,没有硬性规定,但分工上:settings 适合基本不变的参数(如登录凭据),spider 参数适合频繁变化、甚至每次运行必变的参数(如本次要抓的栏目 URL)。官方示例:需要登录的站点,凭据放 settings,目标栏目 URL 放 spider 参数。

XML 的 XPath 取不到东西?

很可能是命名空间的问题,需要按 selectors 文档中的 removing-namespaces 小节 先注册/剥离命名空间再匹配。

IPv6 支持

Scrapy 支持 IPv6,但当使用 HTTP11DownloadHandlerH2DownloadHandler 时,需要把 TWISTED_DNS_RESOLVER 设为 scrapy.resolver.CachingHostnameResolver(见 scrapy/resolver.py)。注意代价:这样做后 DNS_TIMEOUT 设置会被忽略,即失去对 DNS 请求单独设置超时的能力。

macOS 上大规模爬取报 filedescriptor out of range in select()

该问题历史上出现在 macOS 宽范围爬取、且 Twisted 默认 reactor 为 SelectReactor 的场景。如果你用 TWISTED_REACTOR 设置显式选择了该 reactor,换用其他 reactor 即可规避。

如何避免被目标站点封禁?

FAQ 指向 practices 文档中的 bans 小节,核心手段包括合理延迟、限制并发、遵守站点规则等。

生产环境部署与内存

  • 生产部署:见 deploy 文档
  • 内存泄漏:FAQ 建议参考 leaks 文档,其中同时解释了"没有泄漏却看似泄漏"的 Python 解释器内置内存管理机制(对象池不归还 OS 的现象)。减少内存占用的思路与泄漏排查是同一套方法。

写在最后

FAQ 的价值不在于答案本身——多数答案只是一两句话加一个文档跳转——而在于它集中定义了 Scrapy 的能力边界与官方推荐路径:解析交给选择器或 lxml/BeautifulSoup、代理与鉴权交给下载器中间件、超大数据源交给流式迭代器、数据膨胀交给 spider middleware 而非 pipeline。本文给出的所有行为描述均可在 scrapy/downloadermiddlewares/offsite.pyscrapy/downloadermiddlewares/httpproxy.pyscrapy/core/downloader/handlers/_httpx.pyscrapy/utils/iterators.pyscrapy/commands/runspider.py 等文件中逐行验证,配合 docs/faq.rst 原文即可作为生产排障时的可靠参照。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

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