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

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

本文记录在 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 相关产品和应用

全部下载

专题列表