使用Redis实现UA池的方案
最近忙于业务开发、交接和游戏,加上碰上了不定时出现的犹豫期和困惑期,荒废学业了一段时间。天冷了,要重新拾起开始下阶段的学习了。之前接触到的一些数据搜索项目,涉及到请求模拟,基于反爬需要使用随机的 User Agent ,于是使用 Redis 实现了一个十分简易的 UA 池。
背景
最近的一个需求,有模拟请求的逻辑,要求每次请求的请求头中的 User Agent 要满足下面几点:
User Agent 是随机的。 每次获取的
User Agent (短时间内)不能重复。 每次获取的
User Agent 必须带有主流的操作系统信息(可以是
Uinux 、
Windows 、
IOS 和安卓等等)。
这里三点都可以从 UA 数据的来源解决,实际上我们应该关注具体的实现方案。简单分析一下,流程如下:

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

上图中,假设不同颜色的 UA 是完全不同的 UA ,它们通过洗牌算法打散放进去环形队列中,实际上每次取出一个 UA 之后,只需要把游标 cursor 前进或者后退一格即可(甚至可以把游标设置到队列中的任意元素)。最终的实现就是:需要通过中间件实现分布式队列(只是队列,不是消息队列)。
具体实现方案
毫无疑问需要一个分布式数据库类型的中间件才能存放已经准备好的 UA ,第一印象就感觉 Redis 会比较合适。接下来需要选用 Redis 的数据类型,主要考虑几个方面:
UA
支持这几个方面的 Redis 数据类型就是 List ,不过注意 List 本身不能去重,去重的工作可以用代码逻辑实现。然后可以想象客户端获取 UA 的流程大致如下:

结合前面的分析,编码过程有如下几步:
准备好需要导入的 UA 数据,可以从数据源读取,也可以直接文件读取。
UA 数据集合一般不会太大,考虑先把这个集合的数据随机打散,如果使用
Java 开发可以直接使用
Collections#shuffle() 洗牌算法,当然也可以自行实现这个数据随机分布的算法, 这一步对于一些被模拟方会严格检验
UA 合法性的场景是必须的 。 导入
UA 数据到
Redis 列表中。 编写
RPOP + LPUSH 的
Lua 脚本,实现分布式循环队列。
编码和测试示例
引入 Redis 的高级客户端 Lettuce 依赖:
io.lettuce lettuce-core 5.2.1.RELEASE
编写 RPOP + LPUSH 的 Lua 脚本, 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 COMMANDS;
private static AtomicReference 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 connect = redisClient.connect();
COMMANDS = connect.sync();
// 模拟构建UA池的原始数据,假设有10个UA,分别是UA-0 ... UA-9
List 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池的方案,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对免费资源网网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!
您可能感兴趣的文章
- 12-31hiredis从安装到项目实战操作
- 12-31phpmyadmin登录时怎么指定服务器ip和端口
- 12-31MySQL线上死锁分析实战
- 12-31MySQL 触发器的使用和理解
- 12-31MySQL 字段默认值该如何设置
- 12-31Redis主从同步配置的方法步骤(图文)
- 12-31MySQL 字符串拆分操作(含分隔符的字符串截取)
- 12-31redis 交集、并集、差集的具体使用
- 12-31MySQL精讲之二:DML数据操作语句
- 12-31PostgreSQL判断字符串是否包含目标字符串的多种方法


阅读排行
推荐教程
- 12-07mysql中外链接是什么意思?
- 12-23PL/SQL登录Oracle数据库报错ORA-12154:TNS:无法解析指定的连接标识符
- 12-07mysql数据库表格怎么建立
- 12-05mysql的事务,隔离级别和锁用法实例分析
- 12-07mysql的数据类型有哪些?
- 12-19Redis中实现查找某个值的范围
- 12-15浅析mysql迁移到clickhouse的5种方法
- 12-11mysql代码执行结构实例分析【顺序、分支、循环结构】
- 12-08添加mysql的用户名和密码是什么语句?
- 12-15CentOS7 64位下MySQL5.7安装与配置教程




