序:
之前为了演示定向爬取的demo.写了个简单的爬取妹纸图片的小程序(之前的代码下载不了(从明文的图片地址变成动态加载))。
为了整理下,贴出来跟大家分享下。
****************
我们略去了动态获取数据及验证码的。百度搜出来妹子图煎蛋网靠前,就用它了。
受制于爬虫与反爬虫的策略,请允许我做个悲伤的表情,本来想整个简单的,人家反扒了。
说一下思路:终点是js的破解:
上面贴了段源文件,可以看出来,煎蛋网对于图片反扒采取的是JS动态获取方式。
核心方法是:jandan_load_img
这端代码在页面没找到。读了下源码:
从这里看出,这个源码也是不断的在修改的,道高一尺魔高一丈。说不定哪天又改了。
这段js是排版也是紧凑的。截取下看看
function jandan_load_img(b) {
var d = $(b);
var f = d.next("span.img-hash");
var e = f.text();
f.remove();
var c = f_hDkFHz230tMFyJJjrQ6QazNuBxMMbxGt(e, "tIvhVmg0AqsZl4dIwsp6EzcQXIpmSvBl");
var a = $('[a]');
d.before(a);
d.before("
");
d.removeAttr("onload");
d.attr("src", location.protocol + c.replace(/(\/\/\w+\.sinaimg\.cn\/)(\w+)(\/.+\.gif)/, "$1thumb180$3"));
if (/\.gif$/.test(c)) {
d.attr("org_src", location.protocol + c);
b.οnlοad=function(){add_img_loading_mask(this,load_sina_gif)}
}
}
我们看到传入的地址是参数b,f是img-hash,第6行c里面是变量,加密了,第7、8行将a标签插入到img之前,查看源码看到a标签就是是查看原图的链接,也就是我们接下来爬取的时候用到的地址了。第6行f_后跟着一长串字母的这个函数(简称f函数)返回的就是图片地址。第7行中replace函数的作用是当图片为gif时替换中间的一个字符串为large。
我们节下课看看这个函数实现:
var f_hDkFHz230tMFyJJjrQ6QazNuBxMMbxGt = function(m, r, d) {
var e = "DECODE";
var r = r ? r : "";
var d = d ? d : 0;
var q = 4;
r = md5(r);
var o = md5(r.substr(0, 16));
var n = md5(r.substr(16, 16));
if (q) {
if (e == "DECODE") {
var l = m.substr(0, q)
}
} else {
var l = ""
}
var c = o + md5(o + l);
var k;
if (e == "DECODE") {
m = m.substr(q);
k = base64_decode(m)
}
var h = new Array(256);
for (var g = 0; g < 256; g++) {
h[g] = g
}
var b = new Array();
for (var g = 0; g < 256; g++) {
b[g] = c.charCodeAt(g % c.length)
}
for (var f = g = 0; g < 256; g++) {
f = (f + h[g] + b[g]) % 256;
tmp = h[g];
h[g] = h[f];
h[f] = tmp
}
var t = "";
k = k.split("");
for (var p = f = g = 0; g < k.length; g++) {
p = (p + 1) % 256;
f = (f + h[p]) % 256;
tmp = h[p];
h[p] = h[f];
h[f] = tmp;
t += chr(ord(k[g]) ^ (h[(h[p] + h[f]) % 256]))
}
if (e == "DECODE") {
if ((t.substr(0, 10) == 0 || t.substr(0, 10) - time() > 0) && t.substr(10, 16) == md5(t.substr(26) + n).substr(0, 16)) {
t = t.substr(26)
} else {
t = ""
}
}
return t
};
主要是md5,base64.先
需要朱行翻译,这里有两种思路,一种是简单的,selenium。这种开发成本低,但是爬取太耗性能,太卡了。
另一种是我们翻译下js的逻辑,用Java实现。说白了就是把加密地址
055bM978+WxjCf7jTh52Z6gHVYbgSqWiC9Vbkdv0gns9CxAQAPia2FBIV7QrxXm9EpkjGQN5utbzBnT7hiFg1MWvP6uTOT1oQKzu0lvhZoagOyWiUmRkNA
翻译成:
http://ww3.sinaimg.cn/mw600/0073tLPGgy1fp93q7o37ij30ia0mt75s.jpg
********************************
3.15抽空补充测试下,结果解密失败,先补充一个点:
jandan_load_img里面第六行的关键函数名称及常量是会变化的。
js头部引用后面的类似时间戳也会变的,需要用正则去匹配。
我贴一段截取代码(没用正则)
if (response.getStatusLine().getStatusCode() == 200) {
//String detalall = EntityUtils.toString(response.getEntity(), "UTF-8");
HttpEntity entity = response.getEntity();
if (entity != null) {
String jsall = EntityUtils.toString(entity,"utf-8");
//截取函数与常数
if(jsall.contains("jandan_load_img")){
String tmp =jsall.substring(jsall.indexOf("var c=f_"), jsall.indexOf("(e,\"")+37);
function = tmp.substring(tmp.indexOf("=")+1, tmp.indexOf("("));
call = tmp.substring(tmp.indexOf("\"")+1,tmp.lastIndexOf("\""));
}
System.out.println(url + "download OK,function="+function+"('"+call );
}
}
EntityUtils.consume(response.getEntity());
response.close();
贴一下过程:
1 尝试了用htmlunit简单的去执行js.
也就是
ScriptResult ckstr =hp.executeJavaScript("javascript:"+function+"('"+pichash+"','"+call+"');");
System.out.println(ckstr.getJavaScriptResult().toString());
System.out.println(ckstr.getNewPage().getWebResponse());
输出的解密结果为空。
2. 尝试根据页面的js,用Java来实现
public static String decode(String imghash,String constant) throws IOException{
//1
int q= 4;
constant = md5Encode(constant);
String tt = constant.substring(0, 16);
String o = md5Encode(tt) ;
String n = MD5Util.encode(constant.substring(16, 32)) ;
String l = imghash.substring(0,q);
System.out.println(l);
//2
String c = o + MD5Util.encode(o + l);
imghash = imghash.substring(q);
byte[] k = Base64.decodeBase64(imghash); //不同jar的base64结果一样
// byte[] k = Base64.getDecoder().decode(imghash);
System.out.println("K1="+k);
//3
int[] h = new int[256];
for (int g = 0; g < 256; g++) {
h[g] = g;
}
int[] b = new int[256];
for (int g = 0; g < 256; g++) {
//js的charCodeAt返回指定位置字符在Unicode字符集中的编码值
// b[g] = c.charCodeAt(g % c.length)
b[g] = Character.codePointAt(c,g % c.length());
//b[g] = (int)c.charAt(g % c.length());
}
for (int f =0, g = 0; g < 256; g++) {
f = (f + h[g] + b[g]) % 256;
int tmp = h[g];
h[g] = h[f];
h[f] = tmp;
}
//4
String t = "";
for (int p =0, f =0, g = 0; g < k.length; g++) {
p = (p + 1) % 256;
f = (f + h[p]) % 256;
int tmp = h[p];
h[p] = h[f];
h[f] = tmp;
t += (char)(k[g]^(h[(h[p]+h[f]) % 256]));
}
t = t.substring(0,26);
// if ((t.substring(0, 10).equals("0") || t.substring(0, 10) - time() > 0) && t.substring(10, 16) == MD5Util.encode(t.substring(26) + n).substring(0, 16)) {
// t = t.substr(26)
// } else {
// t = ""
// }
return t;
}
其中,关于base64,Java有可以使用common包或者使用jdk自带包。结果是一样。MD5就是常见的。
还是失败。我觉得是php的有些函数我不懂理解的不对。有实现的同学可以帮忙看看哪里不对。
之前预想的思路:
1 。匹配js,获取js关键函数及常量。
2. jsoup解析页面。获取目标图片列表,加入任务队列。
3. 任务队列线程启动httpclient下载队列,解密并下载。
*************************************************************
结果不美好,过程记录下。