-
242024年4月
-
302024年3月
-
292024年2月
-
312024年1月
-
312023年12月
-
302023年11月
-
312023年10月
-
302023年9月
-
312023年8月
-
352023年7月
-
312023年6月
-
312023年5月
-
302023年4月
-
312023年3月
-
282023年2月
-
312023年1月
-
312022年12月
-
302022年11月
-
312022年10月
-
302022年9月
-
312022年8月
-
322022年7月
-
292022年6月
-
322022年5月
-
302022年4月
-
332022年3月
-
312022年2月
-
372022年1月
-
382021年12月
-
382021年11月
-
402021年10月
-
432021年9月
-
372021年8月
-
442021年7月
-
442021年6月
-
432021年5月
-
342021年4月
-
312021年3月
-
292021年2月
-
352021年1月
-
422020年12月
-
402020年11月
-
482020年10月
-
522020年9月
-
852020年8月
-
752020年7月
-
802020年6月
-
782020年5月
-
772020年4月
-
432020年3月
-
362020年2月
-
452020年1月
-
562019年12月
-
712019年11月
-
612019年10月
-
562019年9月
-
532019年8月
-
362019年7月
-
362019年6月
-
382019年5月
-
402019年4月
-
352019年3月
-
342019年2月
-
442019年1月
-
392018年12月
-
402018年11月
-
392018年10月
-
392018年9月
-
452018年8月
-
452018年7月
-
392018年6月
-
512018年5月
-
492018年4月
-
342018年3月
-
282018年2月
-
482018年1月
-
732017年12月
-
7292017年11月
-
7442017年10月
-
2892017年9月
-
12017年8月
爬虫的目的就是为了模拟点击浏览器操作的行为,在反反爬策略中,最基础的就是更换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中开启限制爬取速度的配置,使得我们的爬虫项目更接近点击行为操作,提高反反爬效率。