您的位置:首页 >新闻资讯 > 正文

基于Redis的代理IP池如何设计?

来源:互联网 作者:admin 时间:2019-09-09 15:33:14

  代理IP作为反反爬虫的有效手段,配置简单使用方便,但是比较容易不稳定,IP容易被封。优质代理IP毕竟是占少数,所以,一般我们要做爬虫项目,通常都会建立一个专门的爬虫代理IP池,保证爬虫的稳定性。


  接下来,让我们看看用Redis构建代理IP池的具体步骤,共同学习。


  整体流程:


基于Redis的代理IP池如何设计


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


  上面也提到 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池的时候,可以通过这种方式进行。


相关文章内容简介