Python Scrapy 自定义 Cookie 中间件导致自动合并的排查与解决
本文记录在 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 相关问题,请搜索本站历史文章或继续浏览后续内容,感谢大家的支持!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









