1.參考博客
https://www.jianshu.com/p/9975de57b0ce
https://blog.csdn.net/litang199612/article/details/83413002
https://blog.csdn.net/m0_37156322/article/details/84658872
https://blog.csdn.net/paul0926/article/details/96336947
本博客重點講解java實現反爬蟲字體解密,了解具體原因請參考以上博客,Python也請參考以上博客。
2.背景
在針對安居客等房地產項目進行數據爬蟲工作中,發現頁面的顯示為標准的數字,但數據抓取到確實亂碼
頁面:
頁面審查:
頁面顯示的“2500”,但數據顯示的卻是“龒麣龤龤”的亂碼,很疑惑,最后審查發現數據顯示是使用的一個特殊字體“fangchan-secret”。
fangchan-secret
經查詢相關文檔和博客,發現fang-secret是一個動態生成字體庫的工具,而且每次根據不同key生成,字體庫動態生成,后端又不存在相關字體庫,所以獲取的是亂碼。key為base64,重新加載頁面key為變化,具體的key可以審查頁面,檢索"AAAAA",比較長的一串的base64編碼的就是了,瀏覽器每次返回頁面根據動態字體庫渲染相關數據。
3.解決方案
在博客和相關文檔中,了解了相關原因,但其具體的實現卻是基於python實現,最關鍵的是python的ttffont的庫,一直想找java的解決方案沒有,只好自己動手。
拿到動態生成的字體庫的key
因為字體庫基於key生成,這里實現可以通過java的爬蟲工具,然后使用正則表達式實現,然后拿到以下的字符串:
生成字體庫,解碼
這里使用java的awt的相關jar包,關鍵的類Font實現
1 /** 2 * font-secret字符串專用解密工具 3 * 4 * @param key 密匙 5 * @param encodeString 加密后的字符串 6 * @return 解密后的字符串 7 */ 8 public static String decodeString(String key, String encodeString) { 9 try { 10 //base64解碼,初始化字體 11 byte[] ss = Base64.decodeBase64(key); 12 InputStream inputStream = new ByteArrayInputStream(ss); 13 Font dynamicFont = Font.createFont(Font.TRUETYPE_FONT, inputStream); 14 FontRenderContext fontRenderContext = new FontRenderContext(new AffineTransform(), false, false); 15 GlyphVector glyphVector = dynamicFont.createGlyphVector(fontRenderContext, ""); 16 17 //獲取font中字形的映射關系,字段為private,使用反射 18 Class<?> clazz = Font.class; 19 Field[] fs = clazz.getDeclaredFields(); 20 Font2DHandle font2DHandle = null; 21 for (int i = 0; i < fs.length; i++) { 22 fs[i].setAccessible(true);// 將目標屬性設置為可以訪問 23 if (fs[i].getName().equals("font2DHandle")) { 24 font2DHandle = (Font2DHandle) fs[i].get(dynamicFont); 25 } 26 27 } 28 29 //得到映射關系 30 Font2D font2D = font2DHandle.font2D; 31 TrueTypeFont trueTypeFont = (TrueTypeFont) font2D; 32 TrueTypeGlyphMapper charToGlyphMapper = (TrueTypeGlyphMapper) trueTypeFont.getMapper(); 33 34 //開始解密,encodeString為加密后的字符串 35 StringBuffer buffer = new StringBuffer(); 36 char[] chars = encodeString.toCharArray(); 37 for (int i = 0; i < chars.length; i++) { 38 buffer.append(charToGlyphMapper.charToGlyph(chars[i]) - 1); 39 } 40 return buffer.toString(); 41 } catch (Exception e) { 42 e.printStackTrace(); 43 } 44 return ""; 45 }
4.demo