Scrapy 性能基准测试:scrapy bench 命令、内置基准服务器与仓库中的 CPU 基准套件
【免费下载链接】scrapyScrapy, a fast high-level web crawling & scraping framework for Python.项目地址: https://gitcode.com/GitHub_Trending/sc/scrapy
Scrapy 自带一套轻量级基准测试工具:运行scrapy bench即可在本机启动一个 HTTP 服务器,并让一个"只跟链接"的蜘蛛以最大速度爬取它,从而得到当前硬件下 Scrapy 的吞吐基线(pages/min)。读完本文,你能掌握scrapy bench的运行方式与输出解读、命令背后子进程服务器与蜘蛛的实现细节(源码级),以及仓库中用于追踪引擎性能回归的 pytest-codspeed CPU 基准套件,为你的爬虫性能调优建立可对比的基准。
基准测试的目标与设计思路
官方文档 benchmarking.rst 对基准测试的定位非常明确:
- 启动一个本地 HTTP 服务器,以尽可能快的速度爬取它;
- 目标是了解 Scrapy在你的硬件上表现如何,为不同版本、不同配置之间的比较提供一个共同基线;
- 使用的蜘蛛"什么都不做,只是跟链接"(a simple spider that does nothing and just follows links)。
这是一种"压力上限"式测量:它不模拟真实业务中解析、入库、延迟、反爬等待成本,因此测出的速率是引擎与网络栈在理想条件下的吞吐上限,适合作为对比锚点,而不是对你自己爬虫的实际预估。
运行 scrapy bench 并解读输出
运行方式只有一条命令:
scrapy bench安装文档 也推荐用它来验证 Scrapy 是否安装成功。该命令会打印与真实爬取几乎一致的启动日志(启用的扩展、下载器中间件、蜘蛛中间件),随后每秒输出一行进度统计,例如:
2016-12-16 21:18:49 [scrapy.core.engine] INFO: Spider opened 2016-12-16 21:18:49 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min) 2016-12-16 21:18:50 [scrapy.extensions.logstats] INFO: Crawled 70 pages (at 4200 pages/min), scraped 0 items (at 0 items/min) ... 2016-12-16 21:18:59 [scrapy.core.engine] INFO: Closing spider (closespider_timeout) 2016-12-16 21:18:59 [scrapy.extensions.logstats] INFO: Crawled 518 pages (at 2880 pages/min), scraped 0 items (at 0 items/min) 2016-12-16 21:19:00 [scrapy.statscollectors] INFO: Dumping Scrapy stats: {'downloader/request_bytes': 229995, 'downloader/request_count': 534, 'downloader/response_bytes': 1565504, 'downloader/response_count': 534, 'downloader/response_status_count/200': 534, 'finish_reason': 'closespider_timeout', 'request_depth_max': 19, 'scheduler/dequeued': 533, 'scheduler/dequeued/memory': 533, 'scheduler/enqueued': 10661, 'scheduler/enqueued/memory': 10661, ...} 2016-12-16 21:19:00 [scrapy.core.engine] INFO: Spider closed (closespider_timeout)输出中几个关键点:
- 速率读数:官方示例约 10 秒爬了 518 页,即大约3000 pages/min。每秒一行日志来自
LOGSTATS_INTERVAL: 1,由 LogStats 扩展 打印累计页数与瞬时速率(pages/min)。 - 结束原因
closespider_timeout:爬取在 10 秒后被 CloseSpider 扩展 以超时为由关闭(CLOSESPIDER_TIMEOUT: 10)。这两个设置并非来自你的配置文件,而是bench命令自带的默认设置,见下文源码分析。 - 调度器统计揭示去重:
scheduler/enqueued: 10661而scheduler/dequeued只有 533。服务器端每个页面都会生成随机链接,大量链接指向相同 URL,调度器的去重机制过滤掉了大部分重复项——所以请求数远少于入队数,这正是"只跟链接"场景下调度器正常工作的一部分。 - 不要拿它当你的爬虫速率:文档特别提醒,这是最简蜘蛛,你自己写的 spider 通常会做更多事(解析、字段提取、Item 处理),实际爬取速率会更慢,慢多少取决于 spider 的工作量与实现质量。
源码解析:bench 命令如何工作
命令入口与默认设置
scrapy bench的实现是 bench.py。命令类 Command 携带一组内置默认设置:
class Command(ScrapyCommand): default_settings: ClassVar[dict[str, Any]] = { "LOG_LEVEL": "INFO", "LOGSTATS_INTERVAL": 1, "CLOSESPIDER_TIMEOUT": 10, } def run(self, args: list[str], opts: argparse.Namespace) -> None: with _BenchServer(): self.crawler_process.crawl(_BenchSpider, total=100000) self.crawler_process.start()LOGSTATS_INTERVAL: 1对应输出中每秒一行的Crawled N pages (at X pages/min);CLOSESPIDER_TIMEOUT: 10对应输出末尾的Closing spider (closespider_timeout),即基准固定跑 10 秒;- 爬取的蜘蛛是同一个模块内定义的
_BenchSpider,链接池规模通过total=100000传入。
内置基准 HTTP 服务器
_BenchServer(bench.py)是一个上下文管理器:进入时用当前解释器以子进程方式启动基准服务器,读取首行输出(表示服务器就绪)后才开始爬取;退出时kill()掉该进程并等待其结束。
class _BenchServer: def __enter__(self) -> None: pargs = [sys.executable, "-u", "-m", "scrapy.utils.benchserver"] self.proc = subprocess.Popen(pargs, stdout=subprocess.PIPE, env=get_testenv()) self.proc.stdout.readline() def __exit__(self, exc_type, exc_value, traceback): self.proc.kill() self.proc.wait() time.sleep(0.2)服务器本体是 benchserver.py,基于 Twisted Web 实现:
- 端口 8998:
__main__块通过reactor.listenTCP(8998, Site(root))监听本机 8998 端口,并在启动时打印Bench server at http://...:8998(见 benchserver.py); - 任意路径都渲染同一个页面:
Root资源的getChild始终返回自身,因此/follow?...下任意路径都命中同一渲染逻辑(benchserver.py); - 随机链接树:每次渲染读取
total(默认 100)与show(默认 10)两个查询参数,随机取show个[1, total]范围内的整数,为每个整数生成一个形如<a href='/follow?total=...&show=...&n=N'>follow N</a>的链接。链接目标 URL 由参数决定,从而形成一个规模可控、无重复死循环的随机链接网络。
服务器端这套渲染逻辑有专门的单元测试 test_utils_benchserver.py,验证了链接数量、取值范围以及 HTML 结构(如test_render断言生成恰好show个 1..total 范围内的链接)。
基准蜘蛛:只跟链接
_BenchSpider(bench.py)结构极简:
class _BenchSpider(scrapy.Spider): """A spider that follows all links""" name = "follow" total = 100000 show = 20 baseurl = "http://localhost:8998" link_extractor = LinkExtractor() async def start(self) -> AsyncIterator[Any]: qargs = {"total": self.total, "show": self.show} url = f"{self.baseurl}?{urlencode(qargs, doseq=True)}" yield scrapy.Request(url, dont_filter=True) def parse(self, response: Response) -> Any: for link in self.link_extractor.extract_links(response): yield scrapy.Request(link.url, callback=self.parse)total=100000、show=20决定了链接池规模:每个页面 20 条随机链接,池子足够大,10 秒内不会把所有页面都爬完;- 起点请求使用
dont_filter=True,避免去重器把唯一一条入口 URL 过滤掉; parse回调只做一件事:用 LinkExtractor 提取链接并逐个发起Request,没有任何解析、Item 产出或 I/O 之外的重逻辑——这正是"引擎吞吐上限"测量的前提。
仓库中的 CPU 基准套件:pytest-codspeed
除了scrapy bench这条面向用户的命令,仓库还维护了一套面向开发者的 CPU 基准测试,位于 tests/benchmarks,基于pytest-codspeed(conftest.py 中安装AsyncioSelectorReactor并以reactor.iterate()手动驱动事件循环)。这些基准用于持续追踪引擎层面的性能回归。
tests/benchmarks/test_crawl.py 按维度拆分了多种场景,比scrapy bench精细得多:
| 基准 | 测量目标 |
|---|---|
test_parse_http/test_parse_engine | 在真实书籍快照页面上做链接提取 + CSS/XPath 字段解析的成本(带/不带真实 HTTP I/O) |
test_overhead_http/test_overhead_engine | 单请求经过引擎、中间件、下载处理器的固定开销(页面刻意做得很小) |
test_overhead_broad | 广域爬取:每主机一页(shallow)与少主机多页(deep)两种布局的调度开销差异 |
test_overhead_concurrency | CONCURRENT_REQUESTS_PER_DOMAIN: 1受限并发的开销 |
test_overhead_delay | 每个请求都受DOWNLOAD_DELAY等待的场景(关闭随机化以保证可复现) |
test_overhead_items/test_overhead_item_concurrency | Item 经 Item Pipeline 传递的开销,以及CONCURRENT_ITEMS并发上限生效时的表现 |
其关键技巧是 NullDownloadHandler:一个不做任何 I/O 的下载处理器,直接把响应"凭空"返回,让基准只测量引擎、调度器与中间件,而不受 HTTP 解析、DNS 或套接字影响;同时它用await asyncio.sleep(0)让出一次事件循环,保证并发限制依然真实生效,并用benchmark/peak_concurrency统计跟踪峰值并发。真实 HTTP 场景则复用测试用 mock 服务器的/follow资源(http_resources.py),该资源额外支持order(desc/rand)与maxlatency(模拟网络延迟)参数,比scrapy bench的服务器更灵活。
适用前提与进阶建议
- 适用前提:
scrapy bench依赖本机可用端口 8998(scrapy.utils.benchserver硬编码监听该端口),且使用当前 Python 解释器启动子进程服务器;它测量的是"跟链接上限",不含真实业务解析成本; - 解读方式:把同一台机器、同一版本配置下的速率作为基线,再对比修改后的数值,才有意义;跨机器、跨硬件的绝对值没有可比性;
- 更复杂的基准:官方文档指出,如需更复杂的基准测试,应使用社区项目 scrapy-bench(文档中 benchmarking.rst 的延伸阅读)。对于"我的具体爬虫为什么慢"这类问题,建议结合 性能调优指南 定位瓶颈,而不是只看 bench 数字;
- 可深入的仓库位置:命令实现 scrapy/commands/bench.py、服务器实现 scrapy/utils/benchserver.py、CPU 基准套件 tests/benchmarks/、命令说明 commands.rst。
【免费下载链接】scrapyScrapy, a fast high-level web crawling & scraping framework for Python.项目地址: https://gitcode.com/GitHub_Trending/sc/scrapy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考