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