Python Scrapy 自定义 Cookie 中间件导致自动合并的排查与解决

Felix
Felix
发布时间:2026-10-14| 3人浏览过
原创

本文记录在 Scrapy 项目中自定义 Cookie 中间件时遇到的坑:即使手动控制 Request 的 meta 标志位,账号仍被封禁。经排查发现 Scrapy 内置的 CookiesMiddleware(优先级 700)会在自定义中间件(优先级 500)之后自动合并 Cookie 到 Header。解决方案是在 Request meta 中设置 dont_merge_cookies 为 true,并手动将 Cookie 写入 Header 而非 request.cookies 属性,从而避免框架自动处理。

问题发现

问题发现:

此前在项目中,由于可用账号资源有限,为防止账号被封禁,采取了差异化策略:对于无需登录即可抓取的内容,不携带 Cookie;仅对必要内容请求时携带 Cookie 进行访问。

初步设想较为简单:在每个生成的 Request 的 meta 中设置一个标志位,并在自定义的 CookieMiddleware 中读取该标志位,以此决定是否为当前 Request 附加 Cookie。

实现代码大致如下:


class CookieMiddleware(object):
  """
  每次请求都随机从账号池中选择一个账号去访问
  """

  def __init__(self):
    client = pymongo.MongoClient(MONGO_URI)
    self.account_collection = client[MONGO_DATABASE][ACCOUNT_COLLECTION]

  def process_request(self, request, spider):
    if 'target' in request.meta: 
      logging.debug('进入到process_request了')
      flag = request.meta['target']
      if flag != 'no':
        all_count = self.account_collection.find({'status': 'success'}).count()
        if all_count == 0:
          raise Exception('当前账号池为空')
        random_index = random.randint(0, all_count - 1)
        random_account = self.account_collection.find({'status': 'success'})[random_index]
        
        request.cookies = json.loads(random_account['cookie'])
      else:
        logging.debug('对XXX的请求不做处理')
    else:
      all_count = self.account_collection.find({'status': 'success'}).count()
      if all_count == 0:
        raise Exception('当前账号池为空')
      random_index = random.randint(0, all_count - 1)
      random_account = self.account_collection.find({'status': 'success'})[random_index]
      
      request.cookies = json.loads(random_account['cookie'])

在 settings.py 中的配置如下:


DOWNLOADER_MIDDLEWARES = {
  'eyny.middlewares.CookieMiddleware': 550,
}

此时部分资深开发者可能已察觉问题所在,与作者起初认为“写法无误”的同事一样,请继续向下阅读。

代码编写完成后,正常启动项目并适当提高了并发量。然而次日发现账号被封禁。在调试过程中观察到,即使抓取无需携带 Cookie 的 URL,请求中依然包含了 Cookie,且该 Cookie 被放置在了 HTTP Header 中。经过两个多小时查阅框架源码,最终定位到根本原因。

原因分析与解决方案

原因分析与解决方案:

在 Scrapy 的 settings 目录下的 default_settings.py 文件中,预定义了一些 DOWNLOADER_MIDDLEWARES_BASE。这些中间件的声明顺序如下:


DOWNLOADER_MIDDLEWARES_BASE = {
  # Engine side
  'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100,
  'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300,
  'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350,
  'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400,
  'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500,
  'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550,
  'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560,
  'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580,
  'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590,
  'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600,
  'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700,
  'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750,
  'scrapy.downloadermiddlewares.stats.DownloaderStats': 850,
  'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900,
  # Downloader side
}

从上述配置可见,DOWNLOADER_MIDDLEWARES_BASE 中声明了 Scrapy 内置的 CookiesMiddleware,其优先级为 700。这意味着它比作者自定义的 CookieMiddleware(优先级 500)执行得更晚。调试结果显示,在自定义中间件执行完毕后,Header 中并未携带 Cookie;但在 Scrapy 内置的 CookiesMiddleware(优先级 700)执行后,Header 中出现了 Cookie。这表明 Cookie 是由 Scrapy 框架自动添加的。

查看 scrapy.downloadermiddlewares.cookies.CookiesMiddleware 的源码,重点关注其 process_request 方法:


class CookiesMiddleware(object):
  """This middleware enables working with sites that need cookies"""

  def __init__(self, debug=False):
    self.jars = defaultdict(CookieJar)
    self.debug = debug

  @classmethod
  def from_crawler(cls, crawler):
    if not crawler.settings.getbool('COOKIES_ENABLED'):
      raise NotConfigured
    return cls(crawler.settings.getbool('COOKIES_DEBUG'))

  def process_request(self, request, spider):
    if request.meta.get('dont_merge_cookies', False):
      return

    cookiejarkey = request.meta.get("cookiejar")
    jar = self.jars[cookiejarkey]
    cookies = self._get_request_cookies(jar, request)
    for cookie in cookies:
      jar.set_cookie_if_ok(cookie, request)

    # set Cookie header
    request.headers.pop('Cookie', None)
    jar.add_cookie_header(request)
    self._debug_cookie(request, spider)

	def process_response(self, request, response, spider):
    if request.meta.get('dont_merge_cookies', False):
      return response

    # extract cookies from Set-Cookie and drop invalid/expired cookies
    cookiejarkey = request.meta.get("cookiejar")
    jar = self.jars[cookiejarkey]
    jar.extract_cookies(response, request)
    self._debug_set_cookie(response, spider)

    return response

上述代码中最关键的部分是 process_request 方法。可以看到,该方法首先检查 request.meta 中是否存在 dont_merge_cookies 属性;若该属性不存在或为 False,则不会执行后续的合并逻辑。这正是我们需要寻找的关键点。

特别注意:若要将 dont_merge_cookies 设置为 true,必须手动将 Cookie 添加到 Header 中。如果通过 request.cookies = json.loads(random_account['cookie']) 这种方式赋值,Scrapy 将不再协助将其合并到 Header 中。

解决方案:在 Request 的 meta 中添加 dont_merge_cookies 属性,并将其设置为 true。在自定义的 CookieMiddleware 中,将 Cookie 直接添加到 Header 中,而不是赋值给 request.cookies 属性。

问题得以解决。但为了探究 Scrapy 自动添加 Cookie 的机制,接下来需阅读相关源码以深入理解。

总结

总结

至此,关于 Python Scrapy 中添加 Cookie 时遇到的坑及解决方法介绍完毕。更多 Scrapy Cookie 相关问题,请搜索本站历史文章或继续浏览后续内容,感谢大家的支持!

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。