import os
import six
import logging
from collections import defaultdict
from scrapy.exceptions import NotConfigured
from scrapy.http import Response
from scrapy.http.cookies import CookieJar
from scrapy.utils.python import to_native_str
logger = logging.getLogger(__name__)
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
def _debug_cookie(self, request, spider):
if self.debug:
cl = [to_native_str(c, errors='replace')
for c in request.headers.getlist('Cookie')]
if cl:
cookies = "\n".join("Cookie: {}\n".format(c) for c in cl)
msg = "Sending cookies to: {}\n{}".format(request, cookies)
logger.debug(msg, extra={'spider': spider})
def _debug_set_cookie(self, response, spider):
if self.debug:
cl = [to_native_str(c, errors='replace')
for c in response.headers.getlist('Set-Cookie')]
if cl:
cookies = "\n".join("Set-Cookie: {}\n".format(c) for c in cl)
msg = "Received cookies from: {}\n{}".format(response, cookies)
logger.debug(msg, extra={'spider': spider})
def _format_cookie(self, cookie):
# build cookie string
cookie_str = '%s=%s' % (cookie['name'], cookie['value'])
if cookie.get('path', None):
cookie_str += '; Path=%s' % cookie['path']
if cookie.get('domain', None):
cookie_str += '; Domain=%s' % cookie['domain']
return cookie_str
def _get_request_cookies(self, jar, request):
if isinstance(request.cookies, dict):
cookie_list = [{'name': k, 'value': v} for k, v in \
six.iteritems(request.cookies)]
else:
cookie_list = request.cookies
cookies = [self._format_cookie(x) for x in cookie_list]
headers = {'Set-Cookie': cookies}
response = Response(request.url, headers=headers)
return jar.make_cookies(response, request)
- from_crawler方法 从配置文件中读取COOKIE_ENABLEED,决定是否启用该中间件。如果启用,调用构造器创建对象,否则抛出NotConfigured异常,Scrapy将忽略该中间件。
- __init__ 使用标准库中的collections.defaultdict创建一个默认字典self.jars,该字典中每一项的值都是一个scrappy.http.cookies.CookieJar对象,CookiesMiddleware可以让爬虫同时使用多个不同的CookieJar。例如,在某网站有两个账号,如果想让爬虫同时登陆两个账号进行爬取,为了避免Cookie冲突,可以让两个账号发送的HTTP请求使用不同的CookieJar,在构造Request对象时,可以 通过meta参数的cookiejar字段指定所要使用的CookieJar
-
#账号account1发送的请求 Request(url1,meta={'cookiejar' : 'account1'}) Request(url2,meta={'cookiejar' : 'account1'}) Request(url3,meta={'cookiejar' : 'account1'}) #账号account2发送的请求 Request(url1,meta={'cookiejar' : 'account2'}) Request(url2,meta={'cookiejar' : 'account2'}) Request(url3,meta={'cookiejar' : 'account2'})
process_request方法 处理每一个待发送的Request对象,尝试从request.meta.get("cookiejar")获取用户指定使用的CookieJar,如果用户未指定,就使用默认的CookieJar(self.jars[None])。调用的self._get_request_cookies方法获取发送请求request应携带的Cookie信息,填写到HTTP请求头部。
- process_response方法 处理每一个Response对象,依然通过request.meta.get("cookiejar")获取CookieJar对象,调用extract_cookies方法将HTTP响应头部中的Cookie信息保存到CookieJar对象中。
这里的CookieJar是
scrapy.http.cookies.CookieJar
非http.cookies.CookieJar,它们是不同的类Scrappy对http.cookies.CookieJar进行了包装,它们可以互相转化。
CookiesMiddleware自动处理Cookie的特性给用户提供了便利,但是它不能使用浏览器的Cookie,我们可以利用browsercookie对CookiesMiddleware,进行改良,实现一个能使用浏览器的Cookie的中间件。
import browsercookie
from scrapy.downloadermiddlewares.cookies import CookiesMiddleware
class BrowserCookiesMiddleware(CookiesMiddleware):
def __init__(self, debug = False):
super().__init__(debug)
self.load_browser_cookies()
def load_browser_cookies(self):
#加载Chrome浏览器中的Cookie
jar = self.jars['chrome']
chrome_cookiejar = browsercookie.Chrome()
for cookie in chrome_cookiejar:
jar.set_cookie(cookie)
#加载Firefox浏览器中的Cookie
jar = self.jars['firefox']
firefox_cookiejar = browsercookie.firefox()
for cookie in firefox_cookiejar:
jar.set_cookie(cookie)
在构造 BrowserCookiesMiddleware 对象时,使用browsercookie将浏览器中的 Cookie 提取,存储到CookieJar字典self.jars中
- 继承CookiesMiddleware并实现构造器方法,在构造器方法中先调用基类的构造器方法,然后调用self.load_browser_cookies()方法加载浏览器Cookie
- 在 load_browser_cookies()方法中,使用 self.jars['chrome']和 self.jars['firefox']从默认字典中获得两个CookieJar对象,然后调用browsercookie 的chrome 和 firefox方法,分别获取两个浏览器中的Cookie,将它们填入各自的CookieJar对象中。