使用Redis實(shí)現(xiàn)UA池的方案
最近忙于業(yè)務(wù)開(kāi)發(fā)、交接和游戲,加上碰上了不定時(shí)出現(xiàn)的猶豫期和困惑期,荒廢學(xué)業(yè)了一段時(shí)間。天冷了,要重新拾起開(kāi)始下階段的學(xué)習(xí)了。之前接觸到的一些數(shù)據(jù)搜索項(xiàng)目,涉及到請(qǐng)求模擬,基于反爬需要使用隨機(jī)的 User Agent
,于是使用 Redis
實(shí)現(xiàn)了一個(gè)十分簡(jiǎn)易的 UA
池。
背景
最近的一個(gè)需求,有模擬請(qǐng)求的邏輯,要求每次請(qǐng)求的請(qǐng)求頭中的 User Agent
要滿足下面幾點(diǎn):
- 每次獲取的
User Agent
是隨機(jī)的。 - 每次獲取的
User Agent
(短時(shí)間內(nèi))不能重復(fù)。 - 每次獲取的
User Agent
必須帶有主流的操作系統(tǒng)信息(可以是Uinux
、Windows
、IOS
和安卓等等)。
這里三點(diǎn)都可以從 UA
數(shù)據(jù)的來(lái)源解決,實(shí)際上我們應(yīng)該關(guān)注具體的實(shí)現(xiàn)方案。簡(jiǎn)單分析一下,流程如下:
在設(shè)計(jì) UA
池的時(shí)候,它的數(shù)據(jù)結(jié)構(gòu)和環(huán)形隊(duì)列十分類似:
上圖中,假設(shè)不同顏色的 UA
是完全不同的 UA
,它們通過(guò)洗牌算法打散放進(jìn)去環(huán)形隊(duì)列中,實(shí)際上每次取出一個(gè) UA
之后,只需要把游標(biāo) cursor
前進(jìn)或者后退一格即可(甚至可以把游標(biāo)設(shè)置到隊(duì)列中的任意元素)。最終的實(shí)現(xiàn)就是:需要通過(guò)中間件實(shí)現(xiàn)分布式隊(duì)列(只是隊(duì)列,不是消息隊(duì)列)。
具體實(shí)現(xiàn)方案
毫無(wú)疑問(wèn)需要一個(gè)分布式數(shù)據(jù)庫(kù)類型的中間件才能存放已經(jīng)準(zhǔn)備好的 UA
,第一印象就感覺(jué) Redis
會(huì)比較合適。接下來(lái)需要選用 Redis
的數(shù)據(jù)類型,主要考慮幾個(gè)方面:
UA
支持這幾個(gè)方面的 Redis
數(shù)據(jù)類型就是 List
,不過(guò)注意 List
本身不能去重,去重的工作可以用代碼邏輯實(shí)現(xiàn)。然后可以想象客戶端獲取 UA
的流程大致如下:
結(jié)合前面的分析,編碼過(guò)程有如下幾步:
準(zhǔn)備好需要導(dǎo)入的 UA
數(shù)據(jù),可以從數(shù)據(jù)源讀取,也可以直接文件讀取。
- 因?yàn)樾枰獙?dǎo)入的
UA
數(shù)據(jù)集合一般不會(huì)太大,考慮先把這個(gè)集合的數(shù)據(jù)隨機(jī)打散,如果使用Java
開(kāi)發(fā)可以直接使用Collections#shuffle()
洗牌算法,當(dāng)然也可以自行實(shí)現(xiàn)這個(gè)數(shù)據(jù)隨機(jī)分布的算法, 這一步對(duì)于一些被模擬方會(huì)嚴(yán)格檢驗(yàn)UA
合法性的場(chǎng)景是必須的 。 - 導(dǎo)入
UA
數(shù)據(jù)到Redis
列表中。 - 編寫(xiě)
RPOP + LPUSH
的Lua
腳本,實(shí)現(xiàn)分布式循環(huán)隊(duì)列。
編碼和測(cè)試示例
引入 Redis
的高級(jí)客戶端 Lettuce
依賴:
<dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>5.2.1.RELEASE</version> </dependency>
編寫(xiě) 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
這個(gè)腳本十分簡(jiǎn)單,但是已經(jīng)實(shí)現(xiàn)了循環(huán)隊(duì)列的功能。剩下來(lái)的測(cè)試代碼如下:
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(); // 模擬構(gòu)建UA池的原始數(shù)據(jù),假設(shè)有10個(gè)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隊(duì)列中寫(xiě)入U(xiǎn)A數(shù)據(jù),數(shù)據(jù)量多的時(shí)候可以考慮分批寫(xiě)入防止長(zhǎng)時(shí)間阻塞Redis服務(wù) 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)); }); } }
某次運(yùn)行結(jié)果如下:
第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
可見(jiàn)洗牌算法的效果不差,數(shù)據(jù)相對(duì)分散。
小結(jié)
其實(shí) UA
池的設(shè)計(jì)難度并不大,需要注意幾個(gè)要點(diǎn):
- 一般主流的移動(dòng)設(shè)備或者桌面設(shè)備的系統(tǒng)版本不會(huì)太多,所以來(lái)源
UA
數(shù)據(jù)不會(huì)太多,最簡(jiǎn)單的實(shí)現(xiàn)可以使用文件存放,一次讀取直接寫(xiě)入Redis
中。 - 注意需要隨機(jī)打散
UA
數(shù)據(jù),避免同一個(gè)設(shè)備系統(tǒng)類型的UA
數(shù)據(jù)過(guò)于密集,這樣可以避免觸發(fā)模擬某些請(qǐng)求時(shí)候的風(fēng)控規(guī)則。 - 需要熟悉
Lua
的語(yǔ)法,畢竟Redis
的原子指令一定離不開(kāi)Lua
腳本。
總結(jié)
以上所述是小編給大家介紹的使用Redis實(shí)現(xiàn)UA池的方案,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
如果你覺(jué)得本文對(duì)你有幫助,歡迎轉(zhuǎn)載,煩請(qǐng)注明出處,謝謝!
相關(guān)文章
Redis持久化方式之RDB和AOF的原理及優(yōu)缺點(diǎn)
在Redis中,數(shù)據(jù)可以分為兩類,即內(nèi)存數(shù)據(jù)和磁盤(pán)數(shù)據(jù),Redis?提供了兩種不同的持久化方式,其中?RDB?是快照備份機(jī)制,AOF?則是追加寫(xiě)操作機(jī)制,本文將詳細(xì)給大家介紹Redis?持久化方式RDB和AOF的原理及優(yōu)缺點(diǎn),感興趣的同學(xué)可以跟著小編一起來(lái)學(xué)習(xí)2023-06-06手動(dòng)實(shí)現(xiàn)Redis的LRU緩存機(jī)制示例詳解
這篇文章主要介紹了手動(dòng)實(shí)現(xiàn)Redis的LRU緩存機(jī)制示例詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03在Centos?8.0中安裝Redis服務(wù)器的教程詳解
由于考慮到linux服務(wù)器的性能,所以經(jīng)常需要把一些中間件安裝在linux服務(wù)上,今天通過(guò)本文給大家介紹下在Centos?8.0中安裝Redis服務(wù)器的詳細(xì)過(guò)程,感興趣的朋友一起看看吧2022-03-03redis?zset實(shí)現(xiàn)滑動(dòng)窗口限流的代碼
這篇文章主要介紹了redis?zset實(shí)現(xiàn)滑動(dòng)窗口限流,滑動(dòng)窗口算法思想就是記錄一個(gè)滑動(dòng)的時(shí)間窗口內(nèi)的操作次數(shù),操作次數(shù)超過(guò)閾值則進(jìn)行限流,本文通過(guò)實(shí)例代碼給大家詳細(xì)介紹,需要的朋友參考下吧2022-03-03