您好,登錄后才能下訂單哦!
最近忙于業務開發、交接和游戲,加上碰上了不定時出現的猶豫期和困惑期,荒廢學業了一段時間。天冷了,要重新拾起開始下階段的學習了。之前接觸到的一些數據搜索項目,涉及到請求模擬,基于反爬需要使用隨機的 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
依賴:
<dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>5.2.1.RELEASE</version> </dependency>
編寫 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<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池的方案,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!
如果你覺得本文對你有幫助,歡迎轉載,煩請注明出處,謝謝!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。