Scrapy HTTP Cache:完整的初学者指南(停止对网站进行猛烈请求)
Source: Dev.to
抱歉,我无法访问外部链接获取文章内容。请提供您希望翻译的具体文本,我将为您翻译成简体中文。
当我首次构建爬虫时
我过去会反复运行爬虫进行测试。
每次修改选择器后,我都会重新运行爬虫,再次请求网站,重新下载相同的页面。
在一天内第 50 次运行后,我意识到了一件事:我是一名糟糕的互联网公民。仅仅因为我写不出正确的 CSS selector,我就对某个可怜的网站发起了数百次请求。
随后我发现了 Scrapy’s HTTP cache —— 这彻底改变了局面。现在,当我测试爬虫时,它们只会抓取一次页面,然后复用缓存的响应。测试更快了,毫无负罪感,也不容易被封禁。
下面让我演示一下如何正确使用缓存。
什么是 HTTP 缓存?
可以把 HTTP 缓存想象成网页的复印机。
Without cache
- Run spider → Downloads page
运行爬虫 → 下载页面 - Fix selector → Run again → Downloads same page again
修复选择器 → 再次运行 → 再次下载相同页面 - Fix another thing → Run again → Downloads same page again
修复其他内容 → 再次运行 → 再次下载相同页面 再次
With cache
- Run spider → Downloads page → Saves a copy
运行爬虫 → 下载页面 → 保存副本 - Fix selector → Run again → Uses saved copy (no download!)
修复选择器 → 再次运行 → 使用已保存的副本(无需下载!) - Fix another thing → Run again → Still using saved copy
修复其他内容 → 再次运行 → 仍然使用已保存的副本
你只下载一次,却可以无限次测试。网站只会看到一次请求。
Source: …
启用缓存(单行代码)
在你的 settings.py 中添加:
HTTPCACHE_ENABLED = True
就这么简单。Scrapy 现在会缓存所有内容。
运行爬虫:
scrapy crawl myspider
第一次运行:正常下载页面。检查项目文件夹——你会看到一个新的 .scrapy/httpcache/myspider/ 目录,缓存的页面就存放在那里。
再次运行:
scrapy crawl myspider
这一次速度极快。没有实际的 HTTP 请求,所有内容都来自缓存。
缓存工作原理(简明解释)
- 首次请求 – Spider 请求一个 URL。
- 缓存检查 – “我已经有这个页面了吗?”
- 缓存未命中 – 没有,它没有。
- 下载 – 从网站获取。
- 存储 – 将响应保存到缓存。
- 返回 – 把响应交给 spider。
下次请求相同的 URL 时:
- 请求 – Spider 再次请求相同的 URL。
- 缓存检查 – “我有这个页面吗?”
- 缓存命中 – 有!
- 返回 – 返回缓存的响应(无需下载!)。
简洁。快速。高效。
基本缓存设置
缓存保持多长时间
默认情况下,缓存永不过期。你可以设置过期时间:
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_EXPIRATION_SECS = 86400 # 24 hours
24 小时后,缓存的页面会重新下载。
何时使用过期
- 爬取新闻网站(内容每日变化)
- 商品价格(经常变动)
- 任何动态内容
何时不使用过期
- 开发阶段(希望页面保持缓存)
- 爬取静态内容
- 不会变化的历史数据
缓存存放位置
默认位置:.scrapy/httpcache/。可以通过以下方式更改:
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_DIR = 'my_custom_cache'
现在缓存会存放在 my_custom_cache/ 中。
忽略特定状态码
不要缓存错误页面:
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_IGNORE_HTTP_CODES = [404, 500, 502, 503]
404 和 500 不会被缓存——你不想缓存损坏的页面。
缓存策略(两种)
Scrapy 提供两种缓存策略:DummyPolicy 和 RFC2616Policy。
DummyPolicy(简单版)
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_POLICY = 'scrapy.extensions.httpcache.DummyPolicy'
- 缓存所有内容。
- 从不检查缓存是否新鲜。
- 从不重新验证。
使用场景: 测试、离线开发,或希望“完整重放”抓取结果时。
RFC2616Policy(智能版)
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_POLICY = 'scrapy.extensions.httpcache.RFC2616Policy'
- 尊重 HTTP 缓存头(
Cache‑Control、max‑age等)。 - 在需要时进行重新验证。
使用场景: 运行生产环境爬虫、遵守网站缓存规则、需要最新数据,或仅仅是想做一个好的互联网公民。
实际示例:开发 vs. 生产
开发设置(缓存所有内容)
# settings.py
# Development: cache everything forever
HTTPCACHE_ENABLED = True
HTTPCACHE_POLICY = 'scrapy.extensions.httpcache.DummyPolicy'
HTTPCACHE_DIR = '.dev_cache'
HTTPCACHE_EXPIRATION_SECS = 0 # Never expire
完美用于测试——下载一次,永久测试。
生产设置(智能缓存)
# settings.py
# Production: respect HTTP caching rules
HTTPCACHE_ENABLED = True
HTTPCACHE_POLICY = 'scrapy.extensions.httpcache.RFC2616Policy'
HTTPCACHE_DIR = '.prod_cache'
HTTPCACHE_EXPIRATION_SECS = 3600 # 1 hour
HTTPCACHE_IGNORE_HTTP_CODES = [404, 500, 502, 503]
遵守网站规则并在需要时更新。
实用工作流
步骤 1:为开发启用缓存
# settings.py
HTTPCACHE_ENABLED = True
从这里您可以调整其他选项(策略、目录、过期时间等),以适应您的工作流程。爬取愉快!
步骤 2:首次运行(填充缓存)
scrapy crawl myspider
这将下载所有页面并将其缓存。
步骤 3:使用缓存进行开发
现在您可以在不访问网站的情况下运行爬虫数百次:
# 再次运行
scrapy crawl myspider
# 修正选择器
# 再次运行
scrapy crawl myspider
# 再修正其他内容
# 再次运行
scrapy crawl myspider
所有运行都是瞬时的,因为它们直接从缓存中提供。
步骤 4:需要时清除缓存
当网站结构发生变化或您需要新数据时:
rm -rf .scrapy/httpcache/
然后再次运行爬虫,以使用新页面重新填充缓存。
每请求缓存控制
您可以为特定请求禁用缓存:
def parse(self, response):
# This request won't be cached
yield scrapy.Request(
'https://example.com/dynamic',
callback=self.parse_dynamic,
meta={'dont_cache': True}
)
当某些页面必须保持最新而其他页面可以缓存时,这很有用。
高级:存储后端
Scrapy 为 HTTP 缓存提供了两种存储后端:Filesystem(默认)和 DBM。
文件系统(默认)
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
优点
- 易于检查(只需打开文件)
- 在任何环境下都能工作
- 简单
缺点
- 大量小文件
- 处理成千上万页面时速度较慢
- 占用更多磁盘空间
DBM(数据库)
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.DbmCacheStorage'
优点
- 在大量页面时更快
- 文件更少
- 更高效
缺点
- 检查更困难
- 特定数据库的问题
- 更复杂
提示: 对于大多数项目,建议使用 Filesystem 后端;它更简单。
调试缓存
查看已缓存内容
ls -R .scrapy/httpcache/
你会看到每个请求对应的文件夹。每个文件夹中包含:
request_body– 发出的请求体request_headers– 发送的请求头response_body– 接收到的 HTMLresponse_headers– 响应头meta– 元数据
检查请求是否被缓存
Scrapy 会在日志中记录缓存命中:
[scrapy.core.engine] DEBUG: Crawled (200) (referer: None) ['cached']
['cached'] 后缀表示缓存命中。
如果没有缓存命中,日志会是:
[scrapy.core.engine] DEBUG: Crawled (200) (referer: None)
常见陷阱
陷阱 #1:缓存在爬虫运行之间保持
缓存会跨运行保留。要强制获取新数据,请手动清除:
rm -rf .scrapy/httpcache/
或者设置过期时间(见上文)。
陷阱 #2:不同爬虫共享同一缓存目录
如果在同一项目中有多个爬虫,它们共享 .scrapy/httpcache/,但每个爬虫都有自己的子文件夹:
.scrapy/httpcache/
spider1/
spider2/
spider3/
陷阱 #3:POST 请求默认不被缓存
只有 GET 请求会被缓存。POST 请求(例如表单提交)会绕过缓存:
# This won't be cached
yield scrapy.FormRequest(
'https://example.com/search',
formdata={'query': 'test'}
)
这是有意为之,因为 POST 请求通常不是幂等的。
陷阱 #4:重定向也会被缓存
如果一个 URL 会重定向,重定向本身也会被缓存。后续运行将返回已缓存的最终页面,而不会再次跟随重定向:
https://example.com → https://www.example.com
实际场景
场景 1:测试选择器
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_EXPIRATION_SECS = 0 # never expire
先运行一次以填充缓存,然后整天调整选择器而不再访问站点。
场景 2:抓取历史数据
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_POLICY = 'scrapy.extensions.httpcache.DummyPolicy'
HTTPCACHE_EXPIRATION_SECS = 0 # keep forever
适用于永不改变的数据(例如旧文章)。
场景 3:生产环境爬虫
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_POLICY = 'scrapy.extensions.httpcache.RFC2616Policy'
HTTPCACHE_EXPIRATION_SECS = 1800 # 30 minutes
遵循 HTTP 缓存规则,并在 30 分钟后刷新——一种平衡的做法。
场景 4:离线开发
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_IGNORE_MISSING = False # fail if a page isn’t cached
你的爬虫只会使用已缓存的页面;如果某页缺失,则会失败而不是尝试下载——非常适合在飞机上工作。
Tips Nobody Tells You
- 对缓存进行版本控制(或其子集),在协作时确保每个人使用相同的数据。
- 合并策略:对静态页面使用
DummyPolicy,对动态页面使用RFC2616Policy,通过在每个 spider 中覆盖HTTPCACHE_POLICY实现。 - 监控缓存大小:定期运行
du -sh .scrapy/httpcache/并清理旧条目,以防磁盘膨胀。 - 使用
HTTPCACHE_IGNORE_HTTP_CODES防止缓存错误页面(例如 404、500)。
提示 #1:在 CI/CD 中使用缓存
在持续集成过程中,你不想访问真实网站。使用 Scrapy 的 HTTP 缓存:
# settings.py for CI/CD
HTTPCACHE_ENABLED = True
HTTPCACHE_IGNORE_MISSING = False # Tests fail if page not cached
在你的仓库中预先填充缓存。测试将在缓存页面上运行——快速且可靠。
提示 #2:在开发者之间共享缓存
将缓存文件夹提交到版本控制:
git add .scrapy/httpcache/
git commit -m "Add test cache"
现在团队中的每个人都使用相同的缓存页面进行测试,从而获得一致的结果。
提示 #3:为不同环境使用不同缓存
# settings.py
import os
HTTPCACHE_ENABLED = True
if os.getenv('ENV') == 'production':
HTTPCACHE_DIR = '.prod_cache'
HTTPCACHE_POLICY = 'scrapy.extensions.httpcache.RFC2616Policy'
else:
HTTPCACHE_DIR = '.dev_cache'
HTTPCACHE_POLICY = 'scrapy.extensions.httpcache.DummyPolicy'
为开发和生产环境分别使用缓存——兼顾两者优势。
提示 #4:压缩缓存以节省空间
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_GZIP = True # Compress cached responses
节省大量磁盘空间,尤其是对于大型页面。
完整示例爬虫
一个具备智能缓存的可投入生产的爬虫:
# spider.py
import scrapy
class SmartCacheSpider(scrapy.Spider):
name = 'smartcache'
start_urls = ['https://example.com/products']
custom_settings = {
'HTTPCACHE_ENABLED': True,
'HTTPCACHE_POLICY': 'scrapy.extensions.httpcache.RFC2616Policy',
'HTTPCACHE_EXPIRATION_SECS': 3600,
'HTTPCACHE_IGNORE_HTTP_CODES': [404, 500, 502, 503],
'HTTPCACHE_GZIP': True,
'HTTPCACHE_DIR': '.product'
}
def parse(self, response):
# parsing logic here
pass