bigywater.tech
Scrapy · 46

FilesPipeline:文件下载与去重

item 里放一串 file_urls,FilesPipeline 就替你下载文件。它存在哪、文件名怎么来、重复下载怎么避免、失败了怎么办?这篇用一个带 404、重定向、无扩展名下载链接的服务器全跑一遍。

SHA1(URL)
落盘文件名的来源(加上 URL 路径里的扩展名)
uptodate × 3
第二次运行已下载文件的状态(服务器 0 次请求)
False
MEDIA_ALLOW_REDIRECTS 的默认值

落盘与结果

实测 · 本机执行输出
  服务器收到的请求:['/', '/f/a.txt', '/f/b.pdf', '/dl?id=3', '/f/missing.txt', '/f/redir']
  item["files"] 共 4 个结果(file_urls 有 6 个:a.txt 出现两次得到两条同样的结果但只请求了一次,404 与 302 没有结果):
    status=downloadedpath=full/0ba82ee857b25dc90235004d3736f67edf84eb40.txt checksum=94f86a84ec00ce17aeadfdc30912e4ea   url=/f/a.txt
    status=downloadedpath=full/52ea197978f62ec0cb02c715c46eba47885abeed.pdf checksum=ee4498a36143054208a4c3bb26adca87   url=/f/b.pdf
    status=downloadedpath=full/ffb2a4d93b5a93a22cc0e0aa1dc14dab710d3e77     checksum=ee4498a36143054208a4c3bb26adca87   url=/dl?id=3
    status=downloadedpath=full/0ba82ee857b25dc90235004d3736f67edf84eb40.txt checksum=94f86a84ec00ce17aeadfdc30912e4ea   url=/f/a.txt
  磁盘上:full/0ba82ee857b25dc90235004d3736f67edf84eb40.txt(13 字节), full/52ea197978f62ec0cb02c715c46eba47885abeed.pdf(20009 字节), full/ffb2a4d93b5a93a22cc0e0aa1dc14dab710d3e77(20009 字节)
  SHA1("http://…/f/a.txt") 对得上文件名:True;stats {'file_count': 3, 'file_status_count/downloaded': 3}
  日志:WARNING: File (code: 404): Error downloading file from <GET http://127.0.0.1:9960/f/missing.txt> referred in <None> | WARNING: File (unknown-error): Error downloading file from <GET http://127.0.0.1:9960/f/missing.txt> referred in <None>: | WARNING: File (code: 302): Error downloading file from <GET http://127.0.0.1:9960/f/redir> referred in <None> | WARNING: File (unknown-error): Error downloading file from <GET http://127.0.0.1:9960/f/redir> referred in <None>: downl
  a.txt 的 checksum = md5 内容:True

文件保存在 FILES_STORE/full/,文件名是 URL 的 SHA1 加扩展名;结果写进 item 的 files 字段,每条有 url、path、checksum、status。

避免重复下载

实测 · 本机执行输出
  第二次:服务器收到 ['/', '/f/missing.txt', '/f/redir'];files 状态 ['uptodate', 'uptodate', 'uptodate', 'uptodate'];stats {'file_count': 3, 'file_status_count/uptodate': 3}
  FILES_EXPIRES=0(任何已有的文件都算过期):服务器收到 ['/', '/f/a.txt', '/f/b.pdf', '/dl?id=3', '/f/missing.txt', '/f/redir'];files 状态 ['downloaded', 'downloaded', 'downloaded', 'downloaded']

已存在且没过 FILES_EXPIRES 天的文件状态为 uptodate,不再请求服务器;失败的 URL 不会被记住。

重定向

实测 · 本机执行输出
  默认(False):files=[];服务器收到 ['/', '/f/redir'];日志 ['WARNING: File (code: 302): Error downloading file from <GET http://127.0.0.1:9960/f/redir> referred ']
  MEDIA_ALLOW_REDIRECTS=True:files=[{'path': 'full/df36b499e1bae1cf1a29c3680c05008deff254c0', 'status': 'downloaded'}];服务器收到 ['/', '/f/redir', '/f/a.txt'];日志 []

媒体请求默认不跟随 3xx,需要 MEDIA_ALLOW_REDIRECTS = True;保存路径仍按最初的 URL 计算。

自定义文件名

实测 · 本机执行输出
  磁盘上:by-name/a.txt(13 字节), by-name/b.pdf(20009 字节), by-name/dl-id3(20009 字节)

重写 file_path() 即可用可读的名字,但要自己避免冲突。

分步演示

文件下载
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / Scrapy 2.19.0 / Twisted 26.4.0 / lxml 6.1.3 / parsel 1.12.1(临时虚拟环境);所有请求只发给本机自己起的测试服务器(127.0.0.1,端口 9960 以上,不访问任何真实网站);服务器可以按实验需要制造延迟、重定向、压缩、断连等行为,并记录收到的原始请求;每个实验在临时目录里新建 Scrapy 项目或单文件 spider,命令输出来自真实的 scrapy 进程;计时用 time.perf_counter,数字每次运行会有小幅抖动。服务器提供 a.txt(13 字节)、b.pdf(20,009 字节随机内容)、/dl?id=3(同内容但无扩展名)、404、302;item 里直接放 file_urls 的 dict;首次运行和第二次运行共用同一个 FILES_STORE
    • 没有验证:ImagesPipeline(缩略图、尺寸过滤);S3、GCS、FTP 存储;大文件下载时的内存(FilesPipeline 在 2.19 里是否流式写入没有测);并发下载文件数(MEDIA 管道与下载器并发的关系);Content-Disposition 里的文件名(默认不使用);超过 DOWNLOAD_MAXSIZE 的文件
    京ICP备2026067502号-1京公网安备11010502063487号