Redis教程之代理ip池设计方法详解

发布时间 - 2026-01-10 22:39:58    点击率:

前言

众所周知代理 ip 因为配置简单而且廉价,经常用来作为反反爬虫的手段,但是稳定性一直是其诟病。筛选出优质的代理 ip 并不简单,即使付费购买的代理 ip 源,卖家也不敢保证 100% 可用;另外代理 ip 的生命周期也无法预知,可能上一秒能用,下一秒就扑街了。基于这些原因,会给使用代理 ip 的爬虫程序带来很多不稳定的因素。要排除代理 ip 的影响,通常的做法是建一个代理 ip 池,每次请求前来池子取一个 ip,用完之后归还,保证池子里的 ip 都是可用的。本文接下来就探讨一下,如何使用 Redis 构建代理 ip 池,实现自动更新,自动择优。

整体流程

由上图所示,左侧是形成了整个流程的闭环,从爬虫程序以独占的方式拿到一个代理 ip 到爬取完成归还 ip。这个流程其实是不太严谨的,如果爬虫程序异常中断,就会导致 ip 无法归还,就会导致这个 ip 无法循环利用。但是由于代理 ip 本身的特点,量多而且循环利用的价值并不大,所以这种情况就let it go。

上面也提到 ip 是以独占的方式获取,如果是去爬两个毫不相关的网站,本来一个 ip 就可以,可现在需要两个。为了资源最大化使用,这里引入了频道 ip 池和总代理 ip 池。两个网站就当做两个频道,各自独占,互不相关;总池子就是保存所有的 ip,每个频道都共享。假设只有一个 ip:1.1.1.1 在总池子,爬 A 网站会把它从总池子取到 A 频道的 ip 池,然后 A 爬虫程序从 A 频道 ip 池取出 1.1.1.1 进行使用,这时 1.1.1.1 依然在总池子里,但 A 频道的 ip 池已经不包含 1.1.1.1 了;爬 B 网站也是一样的流程拿到 1.1.1.1,只是从 B 自己的频道池获取。下面就详细说说总池子和频道池子。

总代理 ip 池

总池子的作用就是共享所有可用的 ip,但是仅作为存储 ip 的池子并不能实现自动择优啊,这里的择优通常是希望延迟低速度快的 ip 更容易被筛选出,所以我们希望池子中的 ip 是根据它们的延时升序排列,借助 Redis 的 Sorted Sets 数据结构即可实现,用延时表示 score,ip 表示 member。

使用 ZADD 添加新 ip 或更新 ip 的延迟:

> ZADD proxy_global_ips 200 1.1.1.1:8080 100 2.2.2.2:80 300 3.3.3.3:8888
(integer) 3

使用 ZRANGE 获取 ip,可以指定获取的个数,比如取两个:

> ZRANGE proxy_global_ips 0 1 WITHSCORES
1) "2.2.2.2:80" 
2) "100" 
3) "1.1.1.1:8080" 
4) "200" 

频道 ip 池

频道 ip 池的作用是为了最大化使用总池子中的 ip,并且隔离其他频道的 ip 池。由于一个 ip 使用次数过多是有很大的概率被目标网站屏蔽掉,所以这里也需要进行择优,应该优先筛选出使用次数少的 ip,同理也是使用 Sorted Sets,使用次数表示 score,ip 表示 member,这里与总池子明显的不同之处是 key 不是固定的,需要把频道名称组合进去,这样保证频道之间的隔离,如频道 abc 的 key:proxy_channel_abc_ips

由于频道池子中的 ip 是要以独占的方式取出,我们需要一个 ZPOP 的方法,奈何 Redis 本身没有,还好可以通过 Lua 模拟,在一个原子操作下取出 ip,然后删除:

> eval "local el = redis.call('zrange', KEYS[1], 0, 0, 'WITHSCORES'); redis.call('zrem', KEYS[1], el[1]); return el;" 1 proxy_channel_abc_ips

往频道 ip 池添加 ip:

> ZADD proxy_channel_abc_ips INCR 0 1.1.1.1:8080

这里与总池子不同的是多了一个 INCR 选项,这是 Redis 3.0.2 版本后才支持的新特性,即指定在 ZADD 时发生 member 冲突采取的处理方式,INCR 顾名思义是冲突后累加 score 的方式,为什么要用这个选项,看看下面这个流程:

  1. 在频道池子中只有 1.1.1.1,使用次数为 10;总池子也有 1.1.1.1,而且排在第一个
  2. 线程 A 取出 1.1.1.1
  3. 线程 B 从频道池子取 ip,没取到,从总池子补充 ip 到频道池子:ZADD proxy_channel_abc_ips 0 1.1.1.1;取出 1.1.1.1
  4. 线程 A 归还 1.1.1.1:ZADD proxy_channel_abc_ips 11 1.1.1.1
  5. 线程 B 归还 1.1.1.1:ZADD proxy_channel_abc_ips 1 1.1.1.1

第 5 步结束后,ip 1.1.1.1 的计数被错误地重置为 1,而不是我们预期的 12。使用 INCR 选项就可以避免这个尴尬,其实这也只能保证最终计数正确,中途还是会有些非预期的情况,如:

  1. 在频道池子中有 1.1.1.1,使用次数为 10,还有 2.2.2.2,使用次数为 2;总池子也有 1.1.1.1,而且排在第一个
  2. 线程 A 取出 1.1.1.1
  3. 线程 B 取出 2.2.2.2
  4. 线程 C 从频道池子取 ip,没取到,从总池子补充 ip 到频道池子:ZADD proxy_channel_abc_ips 0 1.1.1.1;取出 1.1.1.1
  5. 线程 C 归还 1.1.1.1:ZADD proxy_channel_abc_ips INCR 1 1.1.1.1
  6. 线程 B 归还 2.2.2.2:ZADD proxy_channel_abc_ips INCR 3 2.2.2.2
  7. 线程 D 来池子取 ip,按使用次数少的被分配了 1.1.1.1,这就不是我们期望的,1.1.1.1 实际已经用了 12 次,我们更希望 2.2.2.2 被取出

如果要避免这个问题,一个简单粗暴的办法就是增加频道池子的容量,让 ip 数永远大于并发的线程数。

更新

与 ip 有关的两个属性:延时(爬取页面所花的时间)和使用次数。上面只讲到了根据它们自动择优,这里的就来说下它们是如何更新的。延时和使用次数的更新需要爬虫程序的配合,程序中要记录时间和递增使用次数,在归还 ip 时要将最新值带回给总池子和频道池子。上面频道 ip 池的例子也有提及,每次归还 ip 都要将最新的使用次数带上,其次还要将 ip 的延时更新到总池子里面。如果归还 ip 时出现使用失败的情况,就要将该 ip 从总池子里删除掉,保证该 ip 不会再被使用,至于当前的频道池不用归还就行了。其他频道池不作任何处理,因为 ip 在当前频道不可用,一般都是因为被屏蔽,其他频道依然可以使用,即使确实都不能使用,也会在其他频道归还 ip 时被删除。

这两个属性其实也可以都在 Redis 中更新,在获取 ip 时,使用 Hashs 保存 ip 对应的获取时间和使用次数;在归还时从 Hashs 中取出时间计算出延时,取出使用次数并加 1,再分别更新到总池子和频道池子中。而且这还能避免上面提到的获取 ip 不符合预期的问题。

总结

放在 Redis 中更新的方法也有弊端,延时会包含获取和归还的传输时间,如果爬虫程序获取一个 ip 多次使用,会造成使用次数统计偏少。当然也可以通过在程序中多次调用 Redis 更新 ip 的属性来解决,这样增加了整个流程的复杂性,需要自己权衡。

个人还是倾向在程序中记录,最后更新到 Redis 中。这个方案逻辑确实不够严谨,但是出现问题也不会导致严重后果。程序的健壮性也不是不允许出现 bug,而是出现 bug 有很好的容错性。

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。


# 代理ip地址池  # ip代理池  # redis  # ip  # 超强、超详细Redis数据库入门教程  # 30个php操作redis常用方法代码例子  # redis 队列操作的例子(php)  # redis常用命令、常见错误、配置技巧等分享  # 64位Windows下安装Redis教程  # Flyway数据库版本控制的教程详解  # 利用Python爬取可用的代理IP  # SpringBoot MCP 入门使用步骤详解  # php代码检查代理ip的有效性  # 通过Python爬虫代理IP快速增加博客阅读量  # 也有  # 都是  # 就会  # 第一个  # 数为  # 可以通过  # 要将  # 排在  # 自己的  # 新到  # 的是  # 这是  # 升序  # 循环利用  # 很好  # 闭环  # 放在  # 都不  # 都在  # 是有 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 如何撰写建站申请书?关键要点有哪些?  Windows Hello人脸识别突然无法使用  Laravel怎么写单元测试_PHPUnit在Laravel项目中的基础测试入门  实现点击下箭头变上箭头来回切换的两种方法【推荐】  制作电商网页,电商供应链怎么做?  电商网站制作价格怎么算,网上拍卖流程以及规则?  Swift中循环语句中的转移语句 break 和 continue  Laravel Pest测试框架怎么用_从PHPUnit转向Pest的Laravel测试教程  Python面向对象测试方法_mock解析【教程】  Laravel如何使用Collections进行数据处理?(实用方法示例)  Edge浏览器怎么启用睡眠标签页_节省电脑内存占用优化技巧  Linux虚拟化技术教程_KVMQEMU虚拟机安装与调优  Python自动化办公教程_ExcelWordPDF批量处理案例  智能起名网站制作软件有哪些,制作logo的软件?  如何使用 jQuery 正确渲染 Instagram 风格的标签列表  香港服务器选型指南:免备案配置与高效建站方案解析  Java类加载基本过程详细介绍  Laravel如何使用Socialite实现第三方登录?(微信/GitHub示例)  猪八戒网站制作视频,开发一个猪八戒网站,大约需要多少?或者自己请程序员,需要什么程序员,多少程序员能完成?  Laravel怎么使用Session存储数据_Laravel会话管理与自定义驱动配置【详解】  制作公司内部网站有哪些,内网如何建网站?  Laravel如何使用Gate和Policy进行权限控制_Laravel权限判定与策略规则配置  Laravel如何处理文件上传_Laravel Storage门面实现文件存储与管理  如何在云主机上快速搭建多站点网站?  Laravel怎么实现前端Toast弹窗提示_Laravel Session闪存数据Flash传递给前端【方法】  JavaScript模板引擎Template.js使用详解  绝密ChatGPT指令:手把手教你生成HR无法拒绝的求职信  linux写shell需要注意的问题(必看)  如何在HTML表单中获取用户输入并结合JavaScript动态控制复利计算循环  如何在七牛云存储上搭建网站并设置自定义域名?  如何快速搭建高效WAP手机网站?  装修招标网站设计制作流程,装修招标流程?  Win11怎么关闭专注助手 Win11关闭免打扰模式设置【操作】  Java解压缩zip - 解压缩多个文件或文件夹实例  Laravel Seeder怎么填充数据_Laravel数据库填充器的使用方法与技巧  Laravel怎么做数据加密_Laravel内置Crypt门面的加密与解密功能  详解免费开源的DotNet二维码操作组件ThoughtWorks.QRCode(.NET组件介绍之四)  使用C语言编写圣诞表白程序  Laravel如何部署到服务器_线上部署Laravel项目的完整流程与步骤  如何在IIS中新建站点并解决端口绑定冲突?  学生网站制作软件,一个12岁的学生写小说,应该去什么样的网站?  如何在Ubuntu系统下快速搭建WordPress个人网站?  如何获取免费开源的自助建站系统源码?  南京网站制作费用,南京远驱官方网站?  ,在苏州找工作,上哪个网站比较好?  Laravel如何处理CORS跨域问题_Laravel项目CORS配置与解决方案  Laravel Telescope怎么调试_使用Laravel Telescope进行应用监控与调试  图册素材网站设计制作软件,图册的导出方式有几种?  Laravel如何实现事件和监听器?(Event & Listener实战)  Laravel怎么进行浏览器测试_Laravel Dusk自动化浏览器测试入门