前提

最近忙于业务开发、交接和游戏,加上碰上了不定时出现的犹豫期和困惑期,荒废学业了一段时间。天冷了,要重新拾起开始下阶段的学习了。之前接触到的一些数据搜索项目,涉及到请求模拟,基于反爬需要使用随机的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的流程大致如下:

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

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

编码和测试示例

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

  1. <dependency>
  2. <groupId>io.lettuce</groupId>
  3. <artifactId>lettuce-core</artifactId>
  4. <version>5.2.1.RELEASE</version>
  5. </dependency>

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

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

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

  1. public class UaPoolTest {
  2. private static RedisCommands<String, String> COMMANDS;
  3. private static AtomicReference<String> LUA_SHA = new AtomicReference<>();
  4. private static final String KEY = "UA_POOL";
  5. @BeforeClass
  6. public static void beforeClass() throws Exception {
  7. // 初始化Redis客户端
  8. RedisURI uri = RedisURI.builder().withHost("localhost").withPort(6379).build();
  9. RedisClient redisClient = RedisClient.create(uri);
  10. StatefulRedisConnection<String, String> connect = redisClient.connect();
  11. COMMANDS = connect.sync();
  12. // 模拟构建UA池的原始数据,假设有10个UA,分别是UA-0 ... UA-9
  13. List<String> uaList = Lists.newArrayList();
  14. IntStream.range(0, 10).forEach(e -> uaList.add(String.format("UA-%d", e)));
  15. // 洗牌
  16. Collections.shuffle(uaList);
  17. // 加载Lua脚本
  18. ClassPathResource resource = new ClassPathResource("/scripts/lua/L_RPOP_LPUSH.lua");
  19. String content = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
  20. String sha = COMMANDS.scriptLoad(content);
  21. LUA_SHA.compareAndSet(null, sha);
  22. // Redis队列中写入UA数据,数据量多的时候可以考虑分批写入防止长时间阻塞Redis服务
  23. COMMANDS.lpush(KEY, uaList.toArray(new String[0]));
  24. }
  25. @AfterClass
  26. public static void afterClass() throws Exception {
  27. COMMANDS.del(KEY);
  28. }
  29. @Test
  30. public void testUaPool() {
  31. IntStream.range(1, 21).forEach(e -> {
  32. String result = COMMANDS.evalsha(LUA_SHA.get(), ScriptOutputType.VALUE, KEY);
  33. System.out.println(String.format("第%d次获取到的UA是:%s", e, result));
  34. });
  35. }
  36. }

某次运行结果如下:

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

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

小结

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

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

(本文完 c-2-d e-a-20191114)

原文链接

使用Redis实现UA池的更多相关文章

  1. selenium、UA池、ip池、scrapy-redis的综合应用案例

    案例: 网易新闻的爬取: https://news.163.com/ 爬取的内容为一下4大板块中的新闻内容 爬取: 特点: 动态加载数据  ,用 selenium 爬虫 1. 创建项目 scrapy ...

  2. Redis客户端连接池

    使用场景 对于一些大对象,或者初始化过程较长的可复用的对象,我们如果每次都new对象出来,那么意味着会耗费大量的时间. 我们可以将这些对象缓存起来,当接口调用完毕后,不是销毁对象,当下次使用的时候,直 ...

  3. redis运用连接池报错解决

    redis使用连接池报错解决redis使用十几小时就一直报异常 redis.clients.jedis.exceptions.JedisConnectionException: Could not g ...

  4. selenium在scrapy中的使用、UA池、IP池的构建

    selenium在scrapy中的使用流程 重写爬虫文件的构造方法__init__,在该方法中使用selenium实例化一个浏览器对象(因为浏览器对象只需要被实例化一次). 重写爬虫文件的closed ...

  5. 14.UA池和代理池

    今日概要 scrapy下载中间件 UA池 代理池 今日详情 一.下载中间件 先祭出框架图: 下载中间件(Downloader Middlewares) 位于scrapy引擎和下载器之间的一层组件. - ...

  6. UA池和代理池

    scrapy下载中间件 UA池 代理池 一.下载中间件 先祭出框架图: 下载中间件(Downloader Middlewares) 位于scrapy引擎和下载器之间的一层组件. - 作用: (1)引擎 ...

  7. UA池和代理池在scrapy中的应用

    一.下载中间件 下载中间件(Downloader Middlewares) 位于scrapy引擎和下载器之间的一层组件. - 作用: (1)引擎将请求传递给下载器过程中, 下载中间件可以对请求进行一系 ...

  8. 爬虫开发13.UA池和代理池在scrapy中的应用

      今日概要 scrapy下载中间件 UA池 代理池 今日详情 一.下载中间件 下载中间件(Downloader Middlewares) 位于scrapy引擎和下载器之间的一层组件. - 作用: ( ...

  9. scrapy下载中间件,UA池和代理池

    一.下载中间件 框架图: 下载中间件(Downloader Middlewares) 位于scrapy引擎和下载器之间的一层组件. - 作用: (1)引擎将请求传递给下载器过程中, 下载中间件可以对请 ...

随机推荐

  1. Bug复盘:接口异步返回的重要性

    前言 最近接收了一个老项目,突然甲方 QA 报了一个 bug,连续请求 60 次,成功 8 次,后面的 52 次全部失败,而且成功的 case 返回时间普遍较长.看了日志,并非业务上的异常.这让刚毕业 ...

  2. 搞懂toString()与valueOf()的区别

    一.toString() 作用:toString()方法返回一个表示改对象的字符串,如果是对象会返回,toString() 返回 “[object type]”,其中type是对象类型. 二.valu ...

  3. 你不知道的JavaScript(上)this和对象原型(四)原型

    五章 原型 1.[[ Prototype ]] JavaScript 中的对象有一个特殊的 [[Prototype]] 内置属性,其实就是对于其他对象的引用.几乎所有的对象在创建时 [[Prototy ...

  4. svn下载多模块及依赖框架的项目

    安装TortoiseSVN之后,在新建的文件里右键svn checkout 输入公司配给的svn地址.用户名.密码 需要分模块下载的项目在地址后面的三个...中选择需要下载的项目,点击OK等待下载完成

  5. sql语句字符串包含

    select instr('1222','122') from dual//前者包含后者>0 oracle mysql 数据库可中 select charindex('1','12') from ...

  6. ES6对数组的扩展(简要总结)

    文章目录 数组的扩展(ES6) 1. 扩展运算符 2. Array.from 3. Array.of() 4. copyWithin() 5. find() 和 findIndex() 6. fill ...

  7. Java 商户管理系统 客户管理 库存管理 销售报表 SSM项目源码

    系统介绍: 1.系统采用主流的 SSM 框架 jsp JSTL bootstrap html5 (PC浏览器使用) 2.springmvc +spring4.3.7+ mybaits3.3  SSM ...

  8. 开源Odoo13更新的模块功能信息(译文)

    本文来源江苏欧度软件:www.odooyun.com 本次Odoo13已于10月初发布,更新的模块有:Odoo会计模块.Odoo活动项目模块.Odoo13审批模块.Odoo评价.客户关系管理(CRM) ...

  9. Odoo系统有哪些不同版本?

    来源:www.odooyun.com 1. Odoo10.0 vs Odoo11.0 vs 8.0 截至2017年底,最新的Odoo发布版为Odoo 11.0,但功能上有一定精简(去除财务模块,去除工 ...

  10. ORA-27140: attach to post/wait facility failed

    Errors in file /home/u01/app/oracle/diag/rdbms/hnybdb21/hnybdb211/trace/hnybdb211_j000_143099.trc:OR ...