反反爬策略(二):请求对象添加随机代理IP

本文探讨了爬虫模拟浏览器操作以应对网站反爬措施的方法,重点介绍了通过更换User-Agent和随机更换IP来迷惑服务器监控的策略。同时,文章深入解析了Scrapy框架下实现这些策略的具体步骤,包括使用下载器中间件自定义IP更换逻辑,以及通过重试中间件处理请求失败的情况。

爬虫的目的就是为了模拟点击浏览器操作的行为,在反反爬策略中,最基础的就是更换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就会迷惑服务器监控,不会对本次请求做反爬处理了。至此,我们的爬虫就能顺顺当当的获取数据了。好了,本期结束,大家散会吧。

NO! NO! NO!这还远远不够,这才只是代理的第一步。没有哪个代理是能保证百分之百成功的。付费代理也好,免费代理也好,即使是高匿代理也只不过是提高了防反爬的几率,并没有说一定会成功的。

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

欢迎收听你的月亮我的心,好男人就是我,我就是曾小贤~~ 今日话题:教你如何跟代理死磕到底,不死不休,不眠不觉,如痴如醉。

请求失败的解决方案有两种:
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中开启限制爬取速度的配置,使得我们的爬虫项目更接近点击行为操作,提高反反爬效率。

不过还是感觉有一些未知的bug存在,如果有大佬洞察了,希望能指点一下。欢迎大家留言和讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值