Python3爬虫关于代理池的维护详解

时间:2021-05-22

我们在上一节了解了代理的设置方法,利用代理我们可以解决目标网站封 IP 的问题,而在网上又有大量公开的免费代理,其中有一部分可以拿来使用,或者我们也可以购买付费的代理 IP,价格也不贵。但是不论是免费的还是付费的,都不能保证它们每一个都是可用的,毕竟可能其他人也可能在用此 IP 爬取同样的目标站点而被封禁,或者代理服务器突然出故障或网络繁忙。一旦我们选用了一个不可用的代理,势必会影响我们爬虫的工作效率。

所以说,在用代理时,我们需要提前做一下筛选,将不可用的代理剔除掉,保留下可用代理,接下来在获取代理时从可用代理里面取出直接使用就好了。

所以本节我们来搭建一个高效易用的代理池。

1. 准备工作

要实现代理池我们首先需要成功安装好了 Redis 数据库并启动服务,另外还需要安装 Aiohttp、Requests、RedisPy、PyQuery、Flask 库,如果没有安装可以参考第一章的安装说明。

2. 代理池的目标

代理池要做到易用、高效,我们一般需要做到下面的几个目标:

基本模块分为四块,获取模块、存储模块、检查模块、接口模块。

获取模块需要定时去各大代理网站抓取代理,代理可以是免费公开代理也可以是付费代理,代理的形式都是 IP 加端口,尽量从不同来源获取,尽量抓取高匿代理,抓取完之后将可用代理保存到数据库中。

存储模块负责存储抓取下来的代理。首先我们需要保证代理不重复,另外我们还需要标识代理的可用情况,而且需要动态实时处理每个代理,所以说,一种比较高效和方便的存储方式就是使用 Redis 的 Sorted Set,也就是有序集合。

检测模块需要定时将数据库中的代理进行检测,在这里我们需要设置一个检测链接,最好是爬取哪个网站就检测哪个网站,这样更加有针对性,如果要做一个通用型的代理,那可以设置百度等链接来检测。另外我们需要标识每一个代理的状态,如设置分数标识,100 分代表可用,分数越少代表越不可用,检测一次如果可用,我们可以将其立即设置为100 满分,也可以在原基础上加 1 分,当不可用,可以将其减 1 分,当减到一定阈值后就直接从数据库移除。通过这样的标识分数,我们就可以区分出代理的可用情况,选用的时候会更有针对性。

接口模块需要用 API 来提供对外服务的接口,其实我们可以直接连数据库来取,但是这样就需要知道数据库的连接信息,不太安全,而且需要配置连接,所以一个比较安全和方便的方式就是提供一个 Web API 接口,通过访问接口即可拿到可用代理。另外由于可用代理可能有多个,我们可以提供随机返回一个可用代理的接口,这样保证每个可用代理都可以取到,实现负载均衡。

以上便是设计代理的一些基本思路,那么接下来我们就设计一下整体的架构,然后用代码该实现代理池。

3. 代理池的架构

根据上文的描述,代理池的架构可以是这样的,如图 9-1 所示:

图 9-1 代理池架构

代理池分为四个部分,获取模块、存储模块、检测模块、接口模块。

存储模块使用Redis的有序集合,用以代理的去重和状态标识,同时它也是中心模块和基础模块,将其他模块串联起来。

获取模块定时从代理网站获取代理,将获取的代理传递给存储模块,保存到数据库。

检测模块定时通过存储模块获取所有代理,并对其进行检测,根据不同的检测结果对代理设置不同的标识。

接口模块通过 Web API 提供服务接口,其内部还是连接存储模块,获取可用的代理。

4. 代理池的实现

接下来我们分别用代码来实现一下这四个模块。

存储模块

存储在这里我们使用 Redis 的有序集合,集合的每一个元素都是不重复的,对于代理代理池来说,集合的元素就变成了一个个代理,也就是 IP 加端口的形式,如 60.207.237.111:8888,这样的一个代理就是集合的一个元素。另外有序集合的每一个元素还都有一个分数字段,分数是可以重复的,是一个浮点数类型,也可以是整数类型。该集合会根据每一个元素的分数对集合进行排序,数值小的排在前面,数值大的排在后面,这样就可以实现集合元素的排序了。

对于代理池来说,这个分数可以作为我们判断一个代理可用不可用的标志,我们将 100 设为最高分,代表可用,0 设为最低分,代表不可用。从代理池中获取代理的时候会随机获取分数最高的代理,注意这里是随机,这样可以保证每个可用代理都会被调用到。

分数是我们判断代理稳定性的重要标准,在这里我们设置分数规则如下:

分数 100 为可用,检测器会定时循环检测每个代理可用情况,一旦检测到有可用的代理就立即置为 100,检测到不可用就将分数减 1,减至 0 后移除。

新获取的代理添加时将分数置为 10,当测试可行立即置 100,不可行分数减 1,减至 0 后移除。

这是一种解决方案,当然可能还有更合理的方案。此方案的设置有一定的原因,在此总结如下:

当检测到代理可用时立即置为 100,这样可以保证所有可用代理有更大的机会被获取到。你可能会说为什么不直接将分数加 1 而是直接设为最高 100 呢?设想一下,我们有的代理是从各大免费公开代理网站获取的,如果一个代理并没有那么稳定,平均五次请求有两次成功,三次失败,如果按照这种方式来设置分数,那么这个代理几乎不可能达到一个高的分数,也就是说它有时是可用的,但是我们筛选是筛选的分数最高的,所以这样的代理就几乎不可能被取到,当然如果想追求代理稳定性的化可以用这种方法,这样可确保分数最高的一定是最稳定可用的。但是在这里我们采取可用即设置 100 的方法,确保只要可用的代理都可以被使用到。

当检测到代理不可用时,将分数减 1,减至 0 后移除,一共 100 次机会,也就是说当一个可用代理接下来如果尝试了 100 次都失败了,就一直减分直到移除,一旦成功就重新置回 100,尝试机会越多代表将这个代理拯救回来的机会越多,这样不容易将曾经的一个可用代理丢弃,因为代理不可用的原因可能是网络繁忙或者其他人用此代理请求太过频繁,所以在这里设置为 100 级。

新获取的代理分数设置为 10,检测如果不可用就减 1,减到 0 就移除,如果可用就置 100。由于我们很多代理是从免费网站获取的,所以新获取的代理无效的可能性是非常高的,可能不足 10%,所以在这里我们将其设置为 10,检测的机会没有可用代理 100 次那么多,这也可以适当减少开销。

以上便是代理分数的一个设置思路,不一定是最优思路,但个人实测实用性还是比较强的。

所以我们就需要定义一个类来操作数据库的有序集合,定义一些方法来实现分数的设置,代理的获取等等。

实现如下:

MAX_SCORE=100MIN_SCORE=0INITIAL_SCORE=10REDIS_HOST='localhost'REDIS_PORT=6379REDIS_PASSWORD=NoneREDIS_KEY='proxies'importredisfromrandomimportchoiceclassRedisClient(object):def__init__(self,host=REDIS_HOST,port=REDIS_PORT,password=REDIS_PASSWORD):"""初始化:paramhost:Redis地址:paramport:Redis端口:parampassword:Redis密码"""self.db=redis.StrictRedis(host=host,port=port,password=password,decode_responses=True)defadd(self,proxy,score=INITIAL_SCORE):"""添加代理,设置分数为最高:paramproxy:代理:paramscore:分数:return:添加结果"""ifnotself.db.zscore(REDIS_KEY,proxy):returnself.db.zadd(REDIS_KEY,score,proxy)defrandom(self):"""随机获取有效代理,首先尝试获取最高分数代理,如果不存在,按照排名获取,否则异常:return:随机代理"""result=self.db.zrangebyscore(REDIS_KEY,MAX_SCORE,MAX_SCORE)iflen(result):returnchoice(result)else:result=self.db.zrevrange(REDIS_KEY,0,100)iflen(result):returnchoice(result)else:raisePoolEmptyErrordefdecrease(self,proxy):"""代理值减一分,小于最小值则删除:paramproxy:代理:return:修改后的代理分数"""score=self.db.zscore(REDIS_KEY,proxy)ifscoreandscore>MIN_SCORE:print('代理',proxy,'当前分数',score,'减1')returnself.db.zincrby(REDIS_KEY,proxy,-1)else:print('代理',proxy,'当前分数',score,'移除')returnself.db.zrem(REDIS_KEY,proxy)defexists(self,proxy):"""判断是否存在:paramproxy:代理:return:是否存在"""returnnotself.db.zscore(REDIS_KEY,proxy)==Nonedefmax(self,proxy):"""将代理设置为MAX_SCORE:paramproxy:代理:return:设置结果"""print('代理',proxy,'可用,设置为',MAX_SCORE)returnself.db.zadd(REDIS_KEY,MAX_SCORE,proxy)defcount(self):"""获取数量:return:数量"""returnself.db.zcard(REDIS_KEY)defall(self):"""获取全部代理:return:全部代理列表"""returnself.db.zrangebyscore(REDIS_KEY,MIN_SCORE,MAX_SCORE)

首先定义了一些常量,如 MAX_SCORE、MIN_SCORE、INITIAL_SCORE 分别代表最大分数、最小分数、初始分数。REDIS_HOST、REDIS_PORT、REDIS_PASSWORD 分别代表了 Redis 的连接信息,即地址、端口、密码。REDIS_KEY 是有序集合的键名,可以通过它来获取代理存储所使用的有序集合。

接下来定义了一个 RedisClient 类,用以操作 Redis 的有序集合,其中定义了一些方法来对集合中的元素进行处理,主要功能如下:

init() 方法是初始化的方法,参数是Redis的连接信息,默认的连接信息已经定义为常量,在 init() 方法中初始化了一个 StrictRedis 的类,建立 Redis 连接。这样当 RedisClient 类初始化的时候就建立了Redis的连接。

add() 方法向数据库添加代理并设置分数,默认的分数是 INITIAL_SCORE 也就是 10,返回结果是添加的结果。

random() 方法是随机获取代理的方法,首先获取 100 分的代理,然后随机选择一个返回,如果不存在 100 分的代理,则按照排名来获取,选取前 100 名,然后随机选择一个返回,否则抛出异常。

decrease() 方法是在代理检测无效的时候设置分数减 1 的方法,传入代理,然后将此代理的分数减 1,如果达到最低值,那么就删除。

exists() 方法判断代理是否存在集合中

max() 方法是将代理的分数设置为 MAX_SCORE,即 100,也就是当代理有效时的设置。

count() 方法返回当前集合的元素个数。

all() 方法返回所有的代理列表,供检测使用。

定义好了这些方法,我们可以在后续的模块中调用此类来连接和操作数据库,非常方便。如我们想要获取随机可用的代理,只需要调用 random() 方法即可,得到的就是随机的可用代理。

获取模块

获取模块的逻辑相对简单,首先需要定义一个 Crawler 来从各大网站抓取代理,示例如下:

importjsonfrom.utilsimportget_pagefrompyqueryimportPyQueryaspqclassProxyMetaclass(type):def__new__(cls,name,bases,attrs):count=0attrs['__CrawlFunc__']=[]fork,vinattrs.items():if'crawl_'ink:attrs['__CrawlFunc__'].append(k)count+=1attrs['__CrawlFuncCount__']=countreturntype.__new__(cls,name,bases,attrs)classCrawler(object,metaclass=ProxyMetaclass):defget_proxies(self,callback):proxies=[]forproxyineval("self.{}()".format(callback)):print('成功获取到代理',proxy)proxies.append(proxy)returnproxiesdefcrawl_daili66(self,page_count=4):"""获取代理66:parampage_count:页码:return:代理"""start_url='http:///Python3WebSpider/ProxyPool。

7. 结语

本节我们实现了一个比较高效的代理池来获取随机可用的代理,整个内容比较多,需要好好理解一下。

在后文我们会利用代理池来实现数据的抓取。

以上就是Python3爬虫关于代理池的维护详解的详细内容,更多关于Python3爬虫代理池维护的资料请关注其它相关文章!

声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。

相关文章