注册
登录
提问时间:2018/12/26 15:41:00    楼主:未知网友   阅读量:192

爬虫的目的就是为了模拟点击浏览器操作的行为,在反反爬策略中,最基础的就是更换User-Agent。

User-Agent的作用是方便服务器识别,当前请求对象的身份信息。无法从身份属性来识别是否是机器操作,网站服务器只能通过其他信息来辨别,区别机器和正常用户。识别IP访问频率,判断cookie信息,添加验证码操作等都是常见的网站反爬操作。

今天,主要说的就是突破网站根据IP访问频率的反反爬策略:随机更换请求对象的IP信息。

Scrapy中,更换请求对象的IP信息非常的方便,只需要在request对象进入下载器之前,修改request对象的参数信息。

所以我们需要在下载器中间件 Download_middleware中自定义一个下载器中间件ProxiesMiddleware,在process_request()函数中修改request对象信息。


from random import choice

from .settings import PROXIES_LIST


class ProxiesMiddleware(object):

    def process_request(self, request, spider):

        request.meta["proxy"] = random.choice(PROXIES_LIST)

        return None


其中 PROXIES_LIST 是构建的代理列表。

process_request的参数分别是request当前请求对象,spider当前的爬虫对象。

返回None,Scrapy将继续处理该Request;

返回Request对象时,这个Reuqest会重新放到调度队列里,更低优先级的process_reqyest()函数不会执行;

返回Response对象时,直接将Response对象发送给Spider来处理。

现在每次发送一次请求,请求对象的IP信息就会从配置中的代理IP池中随机挑选一个。不同的IP就会迷惑服务器监控,不会对本次请求做反爬处理了。至此,我们的爬虫就能顺顺当当的获取数据了。

你以为这样就完事了吗,并不是,这才只是代理的第一步。没有哪个代理是能保证百分之百成功的。付费代理也好,免费代理也好,即使是高匿代理也只不过是提高了防反爬的几率,并没有说一定会成功的。

问题来了: 如果加入了随机代理的请求被反爬了,应该如何解决呢? 换下一家网站?收拾铺盖跑路?还是跟它死磕呢?

请求失败的解决方案有两种:

1. 多试几次,直到请求成功,不成功就报错。

2. 换一个代理试试,直到换成请求成功的代理。对代理质量要求必须高。如果代理池质量很差,项目就会陷入死循环中。

解决逻辑是: 设置重试次数,达到指定次数就换代理。

幸运的是Scrapy框架中自带了处理失败请求的中间件RetryMiddleware。

源码如下:


class RetryMiddleware(objec):

    # IOError is raised by the HttpCompression middleware when trying to

    # decompress an empty response

    EXCEPTIONS_TO_RETRY = (defer.TimeoutError, TimeoutError, DNSLookupError,

                           ConnectionRefusedError, ConnectionDone, ConnectError,

                           ConnectionLost, TCPTimedOutError, ResponseFailed,

                           IOError, TunnelError)


    def __init__(self, settings):

        '''

            RETRY_ENABLED: 用于开启中间件,默认为TRUE

            RETRY_TIMES: 重试次数, 默认为2

            RETRY_HTTP_CODES: 遇到哪些返回状态码需要重试, 一个列表,默认为[500, 503, 504, 400, 408]

            RETRY_PRIORITY_ADJUST:调整相对于原始请求的重试请求优先级,默认为-1

        '''

        if not settings.getbool('RETRY_ENABLED'):

            raise NotConfigured

        self.max_retry_times = settings.getint('RETRY_TIMES')

        self.retry_http_codes = set(int(x) for x in settings.getlist('RETRY_HTTP_CODES'))

        self.priority_adjust = settings.getint('RETRY_PRIORITY_ADJUST')


    @classmethod

    def from_crawler(cls, crawler):

        return cls(crawler.settings)


    # def process_request(self, request, spider):

        # 将IP放入请求头中

        # request.meta["proxy"] = "http://" + self.get_random_proxy()

# 设置请求不过滤

        # request.dont_filter = True

        # return None


    def process_response(self, request, response, spider):

        # 判断请求参数中是否有 重新发送请求的参数 不重新请求,跳过执行下次请求, False重新请求

        if request.meta.get('dont_retry', False):

            return response


        # 判断状态码是否在需要重试的状态码列表中, 如果在,重新发送请求

        if response.status in self.retry_http_codes:

            reason = response_status_message(response.status)

            return self._retry(request, reason, spider) or response

        return response


    def process_exception(self, request, exception, spider):

       #  self.request_url = request.url

        # 捕获异常

        if isinstance(exception, self.EXCEPTIONS_TO_RETRY) \

                and not request.meta.get('dont_retry', False):

            return self._retry(request, exception, spider)


    def _retry(self, request, reason, spider):

    # 设置当前请求次数的参数

        retries = request.meta.get('retry_times', 0) + 1

        retry_times = self.max_retry_times

# 设置最大请求次数

        if 'max_retry_times' in request.meta:

            retry_times = request.meta['max_retry_times']


        stats = spider.crawler.stats

        # 判断是否超出最大请求次数

        if retries <= retry_times:

            logger.debug("Retrying %(request)s (failed %(retries)d times): %(reason)s",

                         {'request': request, 'retries': retries, 'reason': reason},

                         extra={'spider': spider})

            retryreq = request.copy()

            retryreq.meta['retry_times'] = retries


            # 重试的请求加入新代理

           #  retryreq.meta['proxy'] = "http://" + self.get_random_proxy()


            retryreq.dont_filter = True

            retryreq.priority = new_request.priority + self.priority_adjust

            # retryreq.priority = request.priority + self.priority_adjust


            if isinstance(reason, Exception):

                reason = global_object_name(reason.__class__)


            stats.inc_value('retry/count')

            stats.inc_value('retry/reason_count/%s' % reason)

            return retryreq

        else:

            print("重试次数超出,报错")

            stats.inc_value('retry/max_reached')

            logger.debug("Gave up retrying %(request)s (failed %(retries)d times): %(reason)s",

                     {'request': request, 'retries': retries, 'reason': reason},

                   extra={'spider': spider})


注释的部分是添加的加入代理的逻辑,需要继承RetryMiddleware之后再重写。


# 重试请求状态码

RETRY_HTTP_CODES = [500, 502, 503, 504, 400, 403, 404, 408, 302]

# 重试次数

RETRY_TIMES = 4


在settings中设置最大重试次数以及需要进行重试的异常状态码列表。


关闭Scrapy自带的RetryMiddleware中间件,开启自定义的Retry中间件。


DOWNLOADER_MIDDLEWARES = {

'demo2.middlewares.RandomUserAgentMiddleware': 200,

'demo2.new_Retrymiddlewares.NewRetryMiddlewares': 600,

'scrapy.downloadermiddlewares.retry.RetryMiddleware': None  # 关闭自动重试


}

启动项目测试。遇到失败的请求会重新发送,超过最大重试次数以后返回一个空的数据。


这就是我们预期的结果了,剩下的就是提高代理池的质量,就能最大程度的保证爬虫的可用性了。


# 限制下载速度

# DOWNLOAD_DELAY = 2


# 开启自动限速,防止反爬

# AUTOTHROTTLE_ENABLED = True


# 初始下载延迟

# AUTOTHROTTLE_START_DELAY = 2


# 高延迟最大下载延迟

# AUTOTHROTTLE_MAX_DELAY = 30


可以在settings中开启限制爬取速度的配置,使得我们的爬虫项目更接近点击行为操作,提高反反爬效率。