zoukankan      html  css  js  c++  java
  • scrapy中添加cookie踩坑记录

    【问题发现】

    爬虫项目中,为了防止被封号(提供的可用账号太少),对于能不登录就可以抓取的内容采用不带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,
    }
    

    到这里可能有些大佬已经能够看出端倪了,和我一样认为这么写没啥问题的同志们继续往下看。

    在这么编写完之后,我正常开启了项目,还适当调高了并发量,然后第二天发现账号被封了。在debug过程中看到在抓取不需要携带cookie的url的时候,依然携带了cookie,并且cookie是被放在了header中,经过我花费了两个多小时查看框架源码之后,终于发现了原因。

    【原因&解决方案】

    在scrapy的settings目录下的default_settings.py文件中,初始声明了一些DOWNLOADER_MIDDLEWARES_BASE,这些middlewares的声明如下:

    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中也声明了一个CookiesMiddleware,而且是700,也就是说比我们写的CookieMiddleware(500)要靠后执行,而且在debug过程中也看到,在执行完我们编写的CookieMiddleware之后,header中没有携带cookie,但是在执行完scrapy.downloadermiddlewares.cookies.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’])**方式添加的cookie,scrapy也不再会帮我们合并到header 中了。

    【解决方案】
    我们的解决方法就是在request的meta中加入dont_merge_cookies属性,并设置为true,在CookieMiddleware中,我们将cookie添加在header中,而不是赋值给request.cookies

    问题解决了,但是这么简单是不是很不爽,所以就继续想看看是为什么scrapy可以自动给我们加上cookie,这个接下来就需要读下面的代码了。

    继续看process_request方法中的内容,在检查完CookiesMiddleware属性之后,然后会在request.meta中查找cookiejar属性的值,然后用这个值去自己的CookiJar管理器中查找是否有这个cookieJar,scrapy的cookieJar管理器使用的是self.jars=defaultdict(CookieJar)。如果没有携带cookiejar属性,则返回默认的CookieJar。

    然后通过 _get_request_cookies方法获得我们放在request.cookies中的cookie内容,然后遍历这个cookies内容,将所有的内容保存在cookieJar的_cookies属性中。

    scrapy.http.cookies.CookieJar通过在属性中设置self.jar=http.cookiejar.CookieJar,来实现CookieJar大部分的功能。

    接下来是帮我们把cookies放在header中,

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

    先把request.headers中的Cookie属性删除,然后把刚刚保存在jar中的cookies包装到request.headers中。

    scrapy.http.cookies.CookieJar 中添加cookies的代码如下:

    def add_cookie_header(self, request):
            wreq = WrappedRequest(request)
            self.policy._now = self.jar._now = int(time.time())
    
    
            # the cookiejar implementation iterates through all domains
            # instead we restrict to potential matches on the domain
            req_host = urlparse_cached(request).hostname
            if not req_host:
                return
    
    
            if not IPV4_RE.search(req_host):
                hosts = potential_domain_matches(req_host)
                if '.' not in req_host:
                    hosts += [req_host + ".local"]
            else:
                hosts = [req_host]
    
    
            cookies = []
            for host in hosts:
                if host in self.jar._cookies:
                    cookies += self.jar._cookies_for_domain(host, wreq)
    
    
            attrs = self.jar._cookie_attrs(cookies)
            if attrs:
                if not wreq.has_header("Cookie"):
                    wreq.add_unredirected_header("Cookie", "; ".join(attrs))
    
    
            self.processed += 1
            if self.processed % self.check_expired_frequency == 0:
                # This is still quite inefficient for large number of cookies
                self.jar.clear_expired_cookies()
    
    
    

    这段代码的大致思路就是,从url中解析出host,然后根据host从jar._cookies属性中获取到cookie并包装到header中,并且每包装一次就对这次的cookie计数,如果达到了过期检查次数,就对jar中的cookie做一次清空。

    【最后】

    看到这里,最需要注意的是:经过上面的过程,scrapy帮我们将cookies保存在了一个默认的CookieJar中,每当我们执行

    cookiejarkey = request.meta.get("cookiejar")
    jar = self.jars[cookiejarkey]
    
    
    

    这段代码的时候,实际上并没有拿到新的CookieJar,拿到的是那个默认的CookieJar,他在defaultdict中的key是None,所以虽然我们没有给request.cookies赋值,但是实际上add_cookie_header方法,将cookieJar中保存的cookie又给我们包装到了header中。

    - END -

    有热门推荐????

  • 相关阅读:
    Android使用sqlliteOpenhelper更改数据库的存储路径放到SD卡上
    递归实现全排列(一)
    poj_1284_原根
    绝对让你理解Android中的Context
    Java Web---登录验证和字符编码过滤器
    ceph理论及部署配置实践
    ceph for openstack快速部署实施
    php set env
    基于本地iso 搭建的本地yum源 安装部署openldap
    ceph rpm foor rhel6
  • 原文地址:https://www.cnblogs.com/chenlove/p/14231692.html
Copyright © 2011-2022 走看看