Scrapy HTTP Cache:完整的初学者指南(停止对网站进行猛烈请求)

发布: (2025年12月27日 GMT+8 02:51)
13 min read
原文: Dev.to

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 请求,所有内容都来自缓存。

缓存工作原理(简明解释)

  1. 首次请求 – Spider 请求一个 URL。
  2. 缓存检查 – “我已经有这个页面了吗?”
  3. 缓存未命中 – 没有,它没有。
  4. 下载 – 从网站获取。
  5. 存储 – 将响应保存到缓存。
  6. 返回 – 把响应交给 spider。

下次请求相同的 URL 时:

  1. 请求 – Spider 再次请求相同的 URL。
  2. 缓存检查 – “我有这个页面吗?”
  3. 缓存命中 – 有!
  4. 返回 – 返回缓存的响应(无需下载!)。

简洁。快速。高效。

基本缓存设置

缓存保持多长时间

默认情况下,缓存永不过期。你可以设置过期时间:

# 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 提供两种缓存策略:DummyPolicyRFC2616Policy

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‑Controlmax‑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 – 接收到的 HTML
  • response_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
Back to Blog

相关文章

阅读更多 »