起因
之前挖過爬取免費代理ip的坑,一個比較帥的同事熱心發我有免費代理ip的網站,遂研究了下:https://proxy.coderbusy.com/。
解密
因為之前爬過類似的網站有了些經驗,大概知道這些家伙都是啥套路於是就隨手ctrl+shift+c選了一下端口號:
端口元素有個奇怪的data字段,懷疑是在這個數字8781的基礎上生成的8080,查看源代碼看看返回的是什么樣的:
果然返回的html中的數字跟頁面上顯示的數字不一致,基本可以確定端口號是在頁面加載完成后通過js在data-i字段的基礎上生成的新端口號。
在此元素上右擊,打一個dom斷點並刷新網頁:
當此元素被修改的時候自動停在了斷點,格式化,分析js:
這是對所有的.port應用b方法,b方法是什么方法呢,在斷點調試模式下選中此變量-->在控制台執行:
然后單擊一下控制台上的輸出,跳到了內存中的某段js,這段就是加密邏輯:
將其拿出:
$(function() {
$('\x2e\x70\x6f\x72\x74\x2d\x62\x6f\x78')["\x65\x61\x63\x68"](function(wssP1, fnDKXroKU2) {
var ClpoEy3 = $(fnDKXroKU2);
var jgemfCG4 = ClpoEy3["\x64\x61\x74\x61"]('\x69\x70');
var TO5 = window["\x70\x61\x72\x73\x65\x49\x6e\x74"](ClpoEy3["\x64\x61\x74\x61"]('\x69'));
var tVF6 = jgemfCG4["\x73\x70\x6c\x69\x74"]('\x2e');
for (var d7 = 0; d7 < tVF6["\x6c\x65\x6e\x67\x74\x68"]; d7++) {
TO5 -= window["\x70\x61\x72\x73\x65\x49\x6e\x74"](tVF6[d7])
}
ClpoEy3["\x74\x65\x78\x74"](TO5)
})
})
但是jQuery的選擇器和方法都被轉為了十六進制,完全看不懂是個啥啊,現在的問題就是怎么把上面的\x十六進制部分轉成可讀的形式:十六進制將\x部分去掉,然后使用String.fromCharCode()將剩下的部分轉為字符,寫出解密邏輯:
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<script id="raw_code" type="text/code-template">
$(function() {
$('\x2e\x70\x6f\x72\x74\x2d\x62\x6f\x78')["\x65\x61\x63\x68"](function(wssP1, fnDKXroKU2) {
var ClpoEy3 = $(fnDKXroKU2);
var jgemfCG4 = ClpoEy3["\x64\x61\x74\x61"]('\x69\x70');
var TO5 = window["\x70\x61\x72\x73\x65\x49\x6e\x74"](ClpoEy3["\x64\x61\x74\x61"]('\x69'));
var tVF6 = jgemfCG4["\x73\x70\x6c\x69\x74"]('\x2e');
for (var d7 = 0; d7 < tVF6["\x6c\x65\x6e\x67\x74\x68"]; d7++) {
TO5 -= window["\x70\x61\x72\x73\x65\x49\x6e\x74"](tVF6[d7])
}
ClpoEy3["\x74\x65\x78\x74"](TO5)
})
})
</script>
<script type="text/javascript">
let content = document.getElementById('raw_code').innerHTML;
content = content.replace(/\\x../g, hex => {
hex = parseInt(hex.replace(/\\x/, ""), 16);
return String.fromCharCode(hex)
});
document.write(content);
</script>
</body>
</html>
十六進制轉為字符串之后:
$(function() {
$('.port-box')["each"](function(wssP1, fnDKXroKU2) {
var ClpoEy3 = $(fnDKXroKU2);
var jgemfCG4 = ClpoEy3["data"]('ip');
var TO5 = window["parseInt"](ClpoEy3["data"]('i'));
var tVF6 = jgemfCG4["split"]('.');
for (var d7 = 0; d7 < tVF6["length"]; d7++) {
TO5 -= window["parseInt"](tVF6[d7])
}
ClpoEy3["text"](TO5)
})
})
稍稍整理下變量名,讓可讀性好一些:
$(function() {
$('.port-box')["each"](function(i, elt) {
var self = $(elt);
var ip = self["data"]('ip');
var falseIp = window["parseInt"](self["data"]('i'));
var ipArray = ip["split"]('.');
for (var i = 0; i < ipArray["length"]; i++) {
falseIp -= window["parseInt"](ipArray[i])
}
self["text"](falseIp)
})
})
值得一提的是上面的方法調用都采用了[“…”]的方式是因為方法名都被十六進制編碼了,如果還用點.來調用的話可能編譯都通不過啦。
分析上面代碼,先將此對象轉為了jQuery對象,然后調用jQuery的data()方法取數據字段,data()是jQuery對h5的一個支持,只需要data-*后面的*號部分就可以取出數據。
將ip和i字段取出,比如取出172.87.221.221和8781,然后將ip按照點號分割,即分為四段,然后使用8781減去每一段的值即為最終的端口。
可依據此邏輯寫出java代碼:
private static int decode(String ip, String basePortStr) {
int basePort = Integer.parseInt(basePortStr);
int ipSum = Arrays.stream(ip.split("\\.")).map(Integer::parseInt).reduce(0, Integer::sum);
return basePort - ipSum;
}
完整爬取demo:
package org.cc11001100.mybatis_study_001;
import org.jsoup.Jsoup;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import static java.util.stream.Collectors.toList;
/**
* @author CC11001100
*/
public class CoderBusyProxyCrawler {
private static int decode(String ip, String basePortStr) {
int basePort = Integer.parseInt(basePortStr);
int ipSum = Arrays.stream(ip.split("\\.")).map(Integer::parseInt).reduce(0, Integer::sum);
return basePort - ipSum;
}
private static List<String> grab(String url) throws IOException {
return Jsoup.parse(new URL(url), 3000)
.select(".table .port-box")
.stream().map(elt -> {
String ip = elt.attr("data-ip");
String falsePort = elt.attr("data-i");
return ip + ":" + decode(ip, falsePort);
}).collect(toList());
}
public static void main(String[] args) throws IOException {
grab("https://proxy.coderbusy.com/").forEach(System.out::println);
}
}
