实测 22 个信号:一次抓取里各信号的触发次数与参数、先后顺序;bytes_received 里抛 StopDownload 能把 300,000 字节的响应截成 65,536 字节(fail=False 回调、fail=True errback);async 处理函数只对 spider_opened、item_scraped、spider_closed 等信号被 await,对 response_received 等同步信号根本不运行;局部函数连信号会被垃圾回收。
实测 JOBDIR:Ctrl-C 后留下 requests.queue/ 磁盘队列、active.json、requests.seen(41 × 22 字节)、spider.state;续跑 22 个页面零重复,self.state 累加(18 + 22 = 40);自己写的 start() 起始请求在续跑时被去重,默认 start_urls 会重发;kill -9 会丢掉 requests.seen 内容,恢复等于重头抓(重叠 22 个);meta 里放不能 pickle 的对象会让 24 个请求在中断时永久丢失。
实测 scraper 的背压:待处理响应的字节数超过 SCRAPER_SLOT_MAX_ACTIVE_SIZE(默认 5,000,000)引擎就不再取新请求;400 个 100 KB 页面、慢回调下,上限 1 MB → 100 MB 让 RSS 从 99.5 涨到 239.4 MiB、耗时从 11.1 s 降到 0.9 s;快回调没有堆积;小响应按 1024 字节算。
实测 async def 回调:return 单个/列表、yield、异步生成器都被接受;异步生成器边产出边处理(136 ms / 1138 ms);20 个 await 0.3 s 的回调总耗时 0.33 s,同步 sleep 要 6.25 s;直接 await Deferred 抛 RuntimeError,要用 maybe_deferred_to_future;回调里可以用 aiohttp;async errback 可以 yield item。
实测 Scrapy 的控制异常:中间件里抛 IgnoreRequest 走 errback、回调里抛则是普通错误;CloseSpider 只在回调和 start() 里生效,在 pipeline、下载器中间件、信号处理函数里抛只记 ERROR 不关爬虫;NotConfigured 悄悄(或带 WARNING)禁用组件;不支持的协议抛 NotSupported 且不重试。
用能制造各种故障的服务器看 errback 拿到的 Failure:500/404 是 HttpError(带 response),连接被拒、DNS 失败、超时、数据截断、超过 DOWNLOAD_MAXSIZE 各有自己的 scrapy.exceptions 类,TLS 或解析错误统一包成 DownloadFailedError;failure.check 可分支;errback 返回 None 吞掉、返回 Request 重调度、不定义则记 ERROR。
从 Scrapy 2.19 源码 grep 出 45 个 request.meta 键(公开 40、内部 5;输入键 24、输出键 10)并实测前面没讲过的:download_timeout、download_maxsize、handle_httpstatus_*、bindaddress、referrer_policy;请求级 Basic 认证 http_user / http_pass,以及 HTTPAUTH_DOMAIN 因匹配 netloc(含端口)而对带端口的 URL 失效的坑。
实测 Scrapy 2.19 的下载处理器:协议到处理器的映射;file:// 与 data: 返回的响应类;自己写 BaseDownloadHandler 注册新协议、把 ftp 设成 None 禁用;换成实验性的 AiohttpDownloadHandler 后每域名并发限制真的生效(峰值 4 对 48),但 bindaddress 被忽略。
用 nginx(HTTPS + HTTP/2)实测 H2DownloadHandler:41 个请求只用 1 条连接(默认处理器 20 条),response.protocol = h2;DOWNLOAD_VERIFY_CERTIFICATES 默认 False,自签名证书能连、设 True 就失败;服务器的最大并发流 4 把总耗时从 0.66 s 拉到 3.12 s;不支持代理。
实测 FilesPipeline:file_urls 下载到 full/<SHA1(URL)><扩展名>,checksum 是内容 md5,重复 URL 只请求一次;失败的 404/302 没有结果也不缓存;第二次运行 3 个文件变 uptodate 不再请求,FILES_EXPIRES=0 全部重下;MEDIA_ALLOW_REDIRECTS 默认不跟 302;重写 file_path 自定义文件名。
实测 spider 参数:-a 的值总是字符串并写成实例属性(覆盖同名类属性);__init__ 漏了 **kwargs 会 TypeError;-a start_urls=字符串 被按字符遍历,抛 Missing scheme 却退出码为 0;-a 没有 = 退出码 2;CrawlerProcess.crawl(S, page=3) 保留类型,与设置互相独立。
实测四种 item 类型:ItemAdapter 给 dict/scrapy.Item/dataclass/attrs 同一套接口;scrapy.Item 赋值未声明字段抛 KeyError,dataclass 不检查类型;导出 jsonlines/csv 都能写,Item 的 serializer 生效,csv 表头取第一个 item;创建 10 万个 3 字段 item:dict 1.3 ms/万次,scrapy.Item 17.8 ms/万次(13.5 倍),dataclass 3.0 ms,内存 192 / 436 / 104 字节。