无忧代理免费ip爬取(端口js加密)
起因
为了训练爬虫技能(其实主要还是js技能…),翻了可能有反爬的网站挨个摧残,现在轮到这个网站了:http://www.data5u.com/free/index.shtml
解密过程
打开网站,在免费ip的列表页查看元素选一个端口,发现表示端口的元素class属性上有可疑的东西(代理ip类网站的反爬总是这么没有创意…):
上面的“GEA”很像是密文存储的东西,怀疑端口号是页面加载完再用js计算出来填充上的,要证明的话也很简单,只需要对照下这个元素当前的值和刚下载下来的时候值是否一致,在控制台查看元素看到的是内存中元素的当前状态,查看页面源代码的才是页面被下载来那一刻的状态,右键-->查看网页源代码。搜索“49.236.220.14”,发现端口号果然不一样,页面被下载下来时是8916,现在显示的却是80.
解密逻辑在这个js中:http://www.data5u.com/theme/data5u/javascript/pde.js?v=1.0,原始的js进行了压缩,使用之前写过的展开eval的方法进行eval展开并格式化(注意需要eval展开两次):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | var _$ = [ '\x2e\x70\x6f\x72\x74' , "\x65\x61\x63\x68" , "\x68\x74\x6d\x6c" , "\x69\x6e\x64\x65\x78\x4f\x66" , '\x2a' , "\x61\x74\x74\x72" , '\x63\x6c\x61\x73\x73' , "\x73\x70\x6c\x69\x74" , "\x20" , "" , "\x6c\x65\x6e\x67\x74\x68" , "\x70\x75\x73\x68" , '\x41\x42\x43\x44\x45\x46\x47\x48\x49\x5a' , "\x70\x61\x72\x73\x65\x49\x6e\x74" , "\x6a\x6f\x69\x6e" , '' ]; $( function () { $(_$[0])[_$[1]]( function () { var a = $( this )[_$[2]](); if (a[_$[3]](_$[4]) != -0x1) { return }; var b = $( this )[_$[5]](_$[6]); try { b = (b[_$[7]](_$[8]))[0x1]; var c = b[_$[7]](_$[9]); var d = c[_$[10]]; var f = []; for ( var g = 0x0; g < d; g++) { f[_$[11]](_$[12][_$[3]](c[g])) }; $( this )[_$[2]](window[_$[13]](f[_$[14]](_$[15])) >> 0x3) } catch (e) {} }) }) |
上面这段js仍然是不可读的,可以看到一些关键词被抽取出来放到了一个字典数组中,字典数组中的字面值还被十六进制编码了,所以接下来需要写点js将其转换为可读形式,下面是转换的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <html> <head></head> <body> <script type= "text/code-template" id= "functionBody" > $( function () { $(_$[0])[_$[1]]( function () { var a = $( this )[_$[2]](); if (a[_$[3]](_$[4]) != -0x1) { return }; var b = $( this )[_$[5]](_$[6]); try { b = (b[_$[7]](_$[8]))[0x1]; var c = b[_$[7]](_$[9]); var d = c[_$[10]]; var f = []; for ( var g = 0x0; g < d; g++) { f[_$[11]](_$[12][_$[3]](c[g])) }; $( this )[_$[2]](window[_$[13]](f[_$[14]](_$[15])) >> 0x3) } catch (e) {} }) }) </script> <script type= "text/javascript" > var _$ = [ '\x2e\x70\x6f\x72\x74' , "\x65\x61\x63\x68" , "\x68\x74\x6d\x6c" , "\x69\x6e\x64\x65\x78\x4f\x66" , '\x2a' , "\x61\x74\x74\x72" , '\x63\x6c\x61\x73\x73' , "\x73\x70\x6c\x69\x74" , "\x20" , "" , "\x6c\x65\x6e\x67\x74\x68" , "\x70\x75\x73\x68" , '\x41\x42\x43\x44\x45\x46\x47\x48\x49\x5a' , "\x70\x61\x72\x73\x65\x49\x6e\x74" , "\x6a\x6f\x69\x6e" , '' ]; let functionBody = document.getElementById( "functionBody" ).innerHTML; let readableFunctionBody = functionBody.replace(/_\$\[[0-9]+\]/g, x => "'" + eval(x) + "'" ); document.write(readableFunctionBody); </script> </body> </html> |
转换并格式化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | $( function () { $( '.port' )[ 'each' ]( function () { var a = $( this )[ 'html' ](); if (a[ 'indexOf' ]( '*' ) != -0x1) { return }; var b = $( this )[ 'attr' ]( 'class' ); try { b = (b[ 'split' ]( ' ' ))[0x1]; var c = b[ 'split' ]( '' ); var d = c[ 'length' ]; var f = []; for ( var g = 0x0; g < d; g++) { f[ 'push' ]( 'ABCDEFGHIZ' [ 'indexOf' ](c[g])) }; $( this )[ 'html' ](window[ 'parseInt' ](f[ 'join' ]( '' )) >> 0x3) } catch (e) {} }) }) |
可以看到解密逻辑已经很清晰了,就是把端口元素上第二个class(假定从1开始),也就是那个奇怪的字符串拿出来,然后在'ABCDEFGHIZ'中找其位置,最后把找到的位置坐标按顺序拼接并转为数字然后除以8,即得到最终的端口号,根据解密逻辑写出java代码:
1 2 3 4 5 6 7 | private static int decodePort(String rawContent) { String rawNum = Stream.of(rawContent.split( "" )) .map( "ABCDEFGHIZ" ::indexOf) .map(Object::toString) .collect(Collectors.joining()); return Integer.parseInt(rawNum) >> 3 ; } |
一个简单的抓取demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | package org.cc11001100.t1; import javaslang.Tuple; import javaslang.Tuple2; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.select.Elements; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLEncoder; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; import static java.util.stream.Collectors.toList; /** * 这个网站的代理: http://www.data5u.com/free/index.shtml * 端口有加密 * * @author CC11001100 */ public class Data5UProxyGrab { private static int decodePort(String rawContent) { String rawNum = Stream.of(rawContent.split( "" )) .map( "ABCDEFGHIZ" ::indexOf) .map(Object::toString) .collect(Collectors.joining()); return Integer.parseInt(rawNum) >> 3 ; } private static List<Tuple2<String, Integer>> parse(String url) { try { Document document = Jsoup.parse( new URL(url), 3000 ); return document.select( ".wlist ul li[style=text-align:center;] ul.l2" ) .stream() .map(elt -> { String ip = elt.select( "span" ).first().text(); Elements portElt = elt.select( ".port" ); if (!portElt.isEmpty() && !portElt.html().contains( "*" )) { String[] ss = portElt.attr( "class" ).split( "\\s+" ); if (ss.length >= 2 ) { return Tuple.of(ip, decodePort(ss[ 1 ])); } } return null ; }) .filter(Objects::nonNull) .collect(toList()); } catch (IOException e) { e.printStackTrace(); } return Collections.emptyList(); } /** * 按照国家抓取 */ public static List<Tuple2<String, Integer>> grabByCountry() throws IOException { return Jsoup.parse( new URL(String.format(url, urlEncode( "中国" ))), 3000 ) .select( "#areaDist ul.bigr span" ) .stream() .map(elt -> elt.attr( "title" )) .flatMap(countryName -> parse(String.format(url, urlEncode(countryName))).stream()) .distinct() .collect(toList()); } private static String urlEncode(String raw) { try { return URLEncoder.encode(raw, "UTF-8" ); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return "" ; } public static void main(String[] args) throws IOException { grabByCountry().forEach(System.out::println); } } |
更省力的方案
上面都太麻烦了,只是为了锻炼一下js技能,其实观察一下发现这个网站的功能设计得很奇怪,比如ip列表提供的筛选功能,下面被圈起来的都是可以作为筛选条件的:
但是偏偏没有端口,鼠标移动到端口上点击是没有反应的,这是因为他要做端口加密啊,让你知道了端口不白做了,然而木用…
下面是分别使用几种过滤条件时地址栏中显示的url:
1 2 3 4 5 | http://www.data5u.com/free/anoy/匿名/index.html http://www.data5u.com/free/country/中国/index.html http://www.data5u.com/free/area/云南/index.html http://www.data5u.com/free/isp/电信/index.html |
根据以上已知基本可推出端口过滤的话可能是类似于下面这种:
然后试了一下,只一次就成功了 …
不知道作者怎么想的,这点不如蚂蚁代理了,蚂蚁代理也支持端口号筛选,不过它普通的情况下是这样的:
端口号是用图片显示的,按照端口筛选是这样的:
因为发请求的人已经知道端口号了,所以再图片显示端口号也没用了,不如干脆将ip地址的一部分按图片显示,这种设计还是比较好的,因为反爬虫对对方已知信息增加获取难度没有意义,应该对其未知信息设计获取门槛。
不过没卵用,下一篇写破解蚂蚁代理的反爬。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架