使用Redis实现UA池的方案

 更新时间:2019年11月14日 09:09:43   作者:middleware  
这篇文章主要介绍了使用Redis实现UA池的方案,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下

最近忙于业务开发、交接和游戏,加上碰上了不定时出现的犹豫期和困惑期,荒废学业了一段时间。天冷了,要重新拾起开始下阶段的学习了。之前接触到的一些数据搜索项目,涉及到请求模拟,基于反爬需要使用随机的 User Agent ,于是使用 Redis 实现了一个十分简易的 UA 池。

背景

最近的一个需求,有模拟请求的逻辑,要求每次请求的请求头中的 User Agent 要满足下面几点:

  • 每次获取的 User Agent 是随机的。
  • 每次获取的 User Agent (短时间内)不能重复。
  • 每次获取的 User Agent 必须带有主流的操作系统信息(可以是 UinuxWindowsIOS 和安卓等等)。

这里三点都可以从 UA 数据的来源解决,实际上我们应该关注具体的实现方案。简单分析一下,流程如下:

在设计 UA 池的时候,它的数据结构和环形队列十分类似:

上图中,假设不同颜色的 UA 是完全不同的 UA ,它们通过洗牌算法打散放进去环形队列中,实际上每次取出一个 UA 之后,只需要把游标 cursor 前进或者后退一格即可(甚至可以把游标设置到队列中的任意元素)。最终的实现就是:需要通过中间件实现分布式队列(只是队列,不是消息队列)。

具体实现方案

毫无疑问需要一个分布式数据库类型的中间件才能存放已经准备好的 UA ,第一印象就感觉 Redis 会比较合适。接下来需要选用 Redis 的数据类型,主要考虑几个方面:

UA

支持这几个方面的 Redis 数据类型就是 List ,不过注意 List 本身不能去重,去重的工作可以用代码逻辑实现。然后可以想象客户端获取 UA 的流程大致如下:

结合前面的分析,编码过程有如下几步:

准备好需要导入的 UA 数据,可以从数据源读取,也可以直接文件读取。

  •  因为需要导入的 UA 数据集合一般不会太大,考虑先把这个集合的数据随机打散,如果使用 Java 开发可以直接使用 Collections#shuffle() 洗牌算法,当然也可以自行实现这个数据随机分布的算法, 这一步对于一些被模拟方会严格检验 UA 合法性的场景是必须的 。
  • 导入 UA 数据到 Redis 列表中。
  • 编写 RPOP + LPUSHLua 脚本,实现分布式循环队列。

编码和测试示例

引入 Redis 的高级客户端 Lettuce 依赖:

<dependency>
  <groupId>io.lettuce</groupId>
  <artifactId>lettuce-core</artifactId>
  <version>5.2.1.RELEASE</version>
</dependency>

编写 RPOP + LPUSHLua 脚本, Lua 脚本名字暂称为 L_RPOP_LPUSH.lua ,放在 resources/scripts/lua 目录下:

local key = KEYS[1]
local value = redis.call('RPOP', key)
redis.call('LPUSH', key, value)
return value

这个脚本十分简单,但是已经实现了循环队列的功能。剩下来的测试代码如下:

public class UaPoolTest {

  private static RedisCommands<String, String> COMMANDS;

  private static AtomicReference<String> LUA_SHA = new AtomicReference<>();
  private static final String KEY = "UA_POOL";

  @BeforeClass
  public static void beforeClass() throws Exception {
    // 初始化Redis客户端
    RedisURI uri = RedisURI.builder().withHost("localhost").withPort(6379).build();
    RedisClient redisClient = RedisClient.create(uri);
    StatefulRedisConnection<String, String> connect = redisClient.connect();
    COMMANDS = connect.sync();
    // 模拟构建UA池的原始数据,假设有10个UA,分别是UA-0 ... UA-9
    List<String> uaList = Lists.newArrayList();
    IntStream.range(0, 10).forEach(e -> uaList.add(String.format("UA-%d", e)));
    // 洗牌
    Collections.shuffle(uaList);
    // 加载Lua脚本
    ClassPathResource resource = new ClassPathResource("/scripts/lua/L_RPOP_LPUSH.lua");
    String content = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
    String sha = COMMANDS.scriptLoad(content);
    LUA_SHA.compareAndSet(null, sha);
    // Redis队列中写入UA数据,数据量多的时候可以考虑分批写入防止长时间阻塞Redis服务
    COMMANDS.lpush(KEY, uaList.toArray(new String[0]));
  }

  @AfterClass
  public static void afterClass() throws Exception {
    COMMANDS.del(KEY);
  }

  @Test
  public void testUaPool() {
    IntStream.range(1, 21).forEach(e -> {
      String result = COMMANDS.evalsha(LUA_SHA.get(), ScriptOutputType.VALUE, KEY);
      System.out.println(String.format("第%d次获取到的UA是:%s", e, result));
    });
  }
}

某次运行结果如下:

第1次获取到的UA是:UA-0
第2次获取到的UA是:UA-8
第3次获取到的UA是:UA-2
第4次获取到的UA是:UA-4
第5次获取到的UA是:UA-7
第6次获取到的UA是:UA-5
第7次获取到的UA是:UA-1
第8次获取到的UA是:UA-3
第9次获取到的UA是:UA-6
第10次获取到的UA是:UA-9
第11次获取到的UA是:UA-0
第12次获取到的UA是:UA-8
第13次获取到的UA是:UA-2
第14次获取到的UA是:UA-4
第15次获取到的UA是:UA-7
第16次获取到的UA是:UA-5
第17次获取到的UA是:UA-1
第18次获取到的UA是:UA-3
第19次获取到的UA是:UA-6
第20次获取到的UA是:UA-9

可见洗牌算法的效果不差,数据相对分散。

小结

其实 UA 池的设计难度并不大,需要注意几个要点:

  • 一般主流的移动设备或者桌面设备的系统版本不会太多,所以来源 UA 数据不会太多,最简单的实现可以使用文件存放,一次读取直接写入 Redis 中。
  • 注意需要随机打散 UA 数据,避免同一个设备系统类型的 UA 数据过于密集,这样可以避免触发模拟某些请求时候的风控规则。
  • 需要熟悉 Lua 的语法,毕竟 Redis 的原子指令一定离不开 Lua 脚本。

总结

以上所述是小编给大家介绍的使用Redis实现UA池的方案,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

相关文章

  • Redis持久化方式之RDB和AOF的原理及优缺点

    Redis持久化方式之RDB和AOF的原理及优缺点

    在Redis中,数据可以分为两类,即内存数据和磁盘数据,Redis 提供了两种不同的持久化方式,其中 RDB 是快照备份机制,AOF 则是追加写操作机制,本文将详细给大家介绍Redis 持久化方式RDB和AOF的原理及优缺点,感兴趣的同学可以跟着小编一起来学习
    2023-06-06
  • Redis中缓存预热与缓存穿透解决方案

    Redis中缓存预热与缓存穿透解决方案

    Redis缓存预热与缓存穿透是Redis缓存使用中的两个重要概念,文章首先介绍了Redis缓存预热和缓存穿透的基本概念,然后详细阐述了它们的产生原因和解决方案,感兴趣的可以了解一下
    2023-12-12
  • 手动实现Redis的LRU缓存机制示例详解

    手动实现Redis的LRU缓存机制示例详解

    这篇文章主要介绍了手动实现Redis的LRU缓存机制示例详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • Redis凭啥可以这么快

    Redis凭啥可以这么快

    本文详细的介绍了为啥使用Redis的时候,可以做到非常快的读取速度,对于大家学习Redis非常有帮助,希望大家喜欢
    2021-02-02
  • redis protocol通信协议及使用详解

    redis protocol通信协议及使用详解

    这篇文章主要为大家介绍了redis protocol通信协议及使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • 基于 Redis 实现接口限流的方式

    基于 Redis 实现接口限流的方式

    今天想和小伙伴们聊聊用 Redis 处理接口限流,这也是最近的 TienChin 项目涉及到这个知识点了,我就拎出来和大家聊聊这个话题
    2022-05-05
  • 在Centos 8.0中安装Redis服务器的教程详解

    在Centos 8.0中安装Redis服务器的教程详解

    由于考虑到linux服务器的性能,所以经常需要把一些中间件安装在linux服务上,今天通过本文给大家介绍下在Centos 8.0中安装Redis服务器的详细过程,感兴趣的朋友一起看看吧
    2022-03-03
  • Redis key命令key的储存方式

    Redis key命令key的储存方式

    这篇文章主要介绍了Redis key命令key的储存方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-04-04
  • Redis分布式限流的几种实现

    Redis分布式限流的几种实现

    分布式限流是指通过将限流策略嵌入到分布式系统中,以控制流量或保护服务,本文就来介绍一下Redis分布式限流的几种实现,感兴趣的可以了解一下
    2023-12-12
  • redis zset实现滑动窗口限流的代码

    redis zset实现滑动窗口限流的代码

    这篇文章主要介绍了redis zset实现滑动窗口限流,滑动窗口算法思想就是记录一个滑动的时间窗口内的操作次数,操作次数超过阈值则进行限流,本文通过实例代码给大家详细介绍,需要的朋友参考下吧
    2022-03-03

最新评论