基于Redis实现的动态ip代理池

一、引言

1.1 声明

声明:
  本文章仅供学习交流使用,不提供完整代码,严禁用于商业用途和非法用途,否则由此产生的一切后果均与本人无关,请各位自觉遵守相关法律法规。
  本文章未经许可禁止转载,禁止任何二次修改(加工)后的传播;若有侵权,联系删除。

交流、合作请留言,24小时内回复!

1.2 简介

  进行高频率的数据采集时,需要切换大量的ip,以应对服务端封ip的反爬虫措施,本人目前使用快代理的私密代理,每个ip的有效性在3~5分钟。
  当一个时间段内,只采集一个平台的数据时(只运行一个项目时),可以在 当前项目代码内部实现 每次只提取一个代理,在ip的有效时间内批量采集数据,直至ip过期,然后切换下一个ip。
  但是,在同一个时间运行多个项目程序(采集不同的平台),并且各个项目在数据采集过程中都需要切换ip时,就不适合在项目代码内部直接提取ip只供当前项目使用,这造成了ip一种浪费。
  因此,需要将ip存储在数据库中,构建出一个代理ip池,供多个项目使用。

ip池应具有以下几个特性:
  ip池中的ip具有一定的生命周期,一定时间后会失效,需要被清除;
  ip池中的ip可以得到补充,会有一些新的ip加入进去;
  ip池中的ip可以被随机取出使用。

1.3 代办

  由于时间有限,本文目前只描述了思路和自己实际用到的部分代码,未完成所有内容的撰写,后续有时间再补充。

二、实现方式

本文不再介绍为什么选择redis作为ip池的载体,直接说明 具体如何实现这个功能。
下面放几个redis的网站,可以自行查看、下载、安装、学习等。
Redis官网 https://redis.io/
Redis中文官网 https://www.redis.net.cn/
Redis中文学习网 https://redis.com.cn/
Redis中国用户组(CRUG) http://www.redis.cn/

2.1 redis

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
redis的键可以设置过期时间,当时间达到后,键值就会被清除。

127.0.0.1:6379> set name jack ex 20		// 设置name的值为jack,且过期时间为20秒
OK
127.0.0.1:6379> ttl name	// 以秒为单位,返回给定 key 的剩余生存时间(TTL,time to live)
17
127.0.0.1:6379> get name	// 在有效期内,可以获取到name的值
jack
127.0.0.1:6379> ttl name	// 还有两秒过期
2
127.0.0.1:6379> get name	// 只要没过期,就可以获取到键值
jack
127.0.0.1:6379> ttl name	// 如果键有设置过期时间,当键过期或者被删除了,TTL命令会返回-2;当键没有设置过期时间,TTL命令返回-1
-2
127.0.0.1:6379> get name	// 键已过期,此时再获取键值,是没有结果的

127.0.0.1:6379>

2.2 ip池实现方式

  使用列表或者集合,将多个ip存储在一个键中,更符合池的概念,但是redis的过期时间只能针对键进行操作,无法对列表或者集合中的元素进行单独设置。暂有两种思路实现

2.2.1 string(字符串)

  Redis实例默认创建了16个数据库,randomkey 命令 用于从当前数据库中随机返回一个键。
  因此可以将其中一个数据库 作为ip池,每一个ip作为键,这个ip键其对应的值可以随意设置,也可以设置为具有实际业务意义的内容,然后设置其相应的ex过期时间即可。

2.2.2 zset(有序集合)

  Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。

  不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

  有序集合的成员是唯一的,但分数(score)却可以重复。

  集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

集合是可以通过 srandmember 指令,随机返回指定个数的元素。有序集合中没有直接返回随机元素的指令。但是可以通过其他方式实现,具体方式以及效率 可以查看此文章 《优快云-从有序集合随机取一个值,应该用什么方案》,这篇文章中最终结论是:先取出所有符合的元素,再随机取出一个值。

有序集合的基本操作如下:只介绍本次用到的,其他指令不赘述

# ZADD key score1 member1 [score2 member2] 向有序集合添加一个或多个成员,或者更新已存在成员的分数
127.0.0.1:6379[1]> zadd my_ip_pool 1 212.56.209.250:1234 2 78.22.19.46:1234 3 66.8.1.122:1234 4 133.255.74.130:1234 5 31.62.75.72:1234
5

# zrangebyscore key min max [LIMIT offset count] 通过分数返回有序集合指定区间内的成员
127.0.0.1:6379[1]> zrangebyscore my_ip_pool 1 5	# 返回 分数score 在[1,5]之间的成员
'212.56.209.250:1234'
'78.22.19.46:1234'
'66.8.1.122:1234'
'133.255.74.130:1234'
'31.62.75.72:1234'

127.0.0.1:6379[1]> zrangebyscore my_ip_pool 1 5 limit 2 2	# 分数在[1,5]之间的成员,从第二个之后开始 返回两个
'66.8.1.122:1234'
'133.255.74.130:1234'

# ZCOUNT key min max 计算在有序集合中指定区间分数的成员数
127.0.0.1:6379[1]> zcount my_ip_pool 3 4	# 分数在[3,4]之间的成员数量
2

根据有序集合以上的特点:可以将代理ip过期时的时间戳作为分数值,ip地址作为成员(元素)值。
每次取ip时,限制分数值(score)大于等于当前时间戳即可取出可用的ip,当前项目中只需要取最近将要过期的一个ip值,可以使用以下命令:zrangebyscore my_ip_pool 当前时间戳 +inf limit 0 1

下面python代码为: 根据redis的有序集合(zset)实现的ip代理池管理。

import time
import redis
import loguru

class IPPool:
    __instance = None

    def __new__(cls, *args, **kwargs):
        if cls.__instance is None:
            cls.__instance = super().__new__(cls, *args, **kwargs)
        return cls.__instance
    
    def __init__(self, auto_clear=1):
        self.redis_client = redis.Redis(host='云服务器地址/或者本机', port=6379, db=15, password='密码')
        self.auto_clear = auto_clear  # 此值为1,则每次在 新增/获取 代理时,都自动清除失效代理.

    # 新增代理
    def add_ip(self, ip_validtime: dict):
        current_time = int(time.time())  # 当前时间戳
        new_dict = {ip: validtime + current_time for ip, validtime in ip_validtime.items()}
        result = self.redis_client.zadd('ip_pool', new_dict)
        loguru.logger.success(f"新增代理{result}个\t{ip_validtime}")
        if self.auto_clear:
            self.clear_ip()  # 清理过期ip
            self.count_ip()  # 统计有效ip个数

    # 清理过期代理
    def clear_ip(self):
        current_time = int(time.time())
        result = self.redis_client.zremrangebyscore('ip_pool', '-inf', current_time)
        loguru.logger.warning(f"清除失效代理{result}个")

    # 统计可用代理的个数
    def count_ip(self):
        current_time = int(time.time())
        result = self.redis_client.zcount('ip_pool', current_time, '+inf')
        loguru.logger.info(f"当前可用代理总个数为:{result}")

    # 获取一个最近将要过期的代理
    def get_ip(self, count=1, validtime=0):
        current_time = int(time.time()) + validtime  # 获取有效时长不小于validtime秒的代理
        result = self.redis_client.zrangebyscore('ip_pool', current_time, '+inf', 0, count)  # count 获取代理的个数
        result = [ip.decode() if ip else '' for ip in result] 
        loguru.logger.info(f"获取到的可用代理如下:{result}")
        if self.auto_clear:
            self.clear_ip()
        return result

    # 根据ip:port 删除一个ip(若发现获取的代理,质量不好,可以不等到过期时间 直接提前删除)
    def del_ip(self, ip, need_print=1):
        result = self.redis_client.zrem('ip_pool', ip)
        if need_print == 1:
            loguru.logger.warning(f"清除低质量代理{result}个:{ip}")
        return result

    # 删除多个代理(调用self.del_ip方法,redis-zset 可一次删除多个元素,为了获得实际删除的ip,这里一个一个的删除)
    def del_ips(self, *ips):
        del_list = []
        for ip in ips:
            if self.del_ip(ip, need_print=0):
                del_list.append(ip)     # 如果为True 说明当前代理删除成功,否则删除失败
        loguru.logger.warning(f"清除低质量代理{len(del_list)}个:{del_list}")

	def __del__(self):
        print("关闭redis连接")
        self.redis_client.close()


if __name__ == '__main__':
    ip_pool = IPPool()
    ip_pool.add_ip({'122.45.46.12:21808': 200, '113.20.61.66:22989': 3,'195.165.56.45:4569':30})
    time.sleep(5)   # 等待5秒,使第二个代理过期
    ip_pool.get_ip()
    ip_pool.del_ips('122.45.46.12:21808','113.120.61.66:22989','195.165.56.45:4569')    # 第2个ip在等待5秒时已经过期,前面一步get_ip时会被自动删除的,此时手动执行删除命令,实际只删除掉两个ip

-----------------------------输出------------------------------------------------------------------
2023-08-28 09:24:36.089 | SUCCESS  | __main__:add_ip:23 - 新增代理2{'122.45.46.12:21808': 200, '113.20.61.66:22989': 3, '195.165.56.45:4569': 30}
2023-08-28 09:24:36.110 | WARNING  | __main__:clear_ip:32 - 清除失效代理02023-08-28 09:24:36.130 | INFO     | __main__:count_ip:38 - 当前可用代理总个数为:3
2023-08-28 09:24:41.164 | INFO     | __main__:get_ip:44 - 获取到的可用代理如下:[b'195.165.56.45:4569']
2023-08-28 09:24:41.184 | WARNING  | __main__:clear_ip:32 - 清除失效代理12023-08-28 09:24:41.249 | WARNING  | __main__:del_ips:62 - 清除低质量代理2:['122.45.46.12:21808', '195.165.56.45:4569']

上述代码对应的程序运行效果截图如下:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值