手把手教你爬取妹紙圖片
序:
之前為了演示定向爬取的demo.寫了個簡單的爬取妹紙圖片的小程式(之前的程式碼下載不了(從明文的圖片地址變成動態載入))。
為了整理下,貼出來跟大家分享下。
****************
我們略去了動態獲取資料及驗證碼的。百度搜出來妹子圖煎蛋網靠前,就用它了。
受制於爬蟲與反爬蟲的策略,請允許我做個悲傷的表情,本來想整個簡單的,人家反扒了。
說一下思路:終點是js的破解:
<li id="comment-3734604"> <div> <div class="row"> <div class="author"><strong title="防偽碼:2331a2d8338f5de26f6a2bc2c4499e507e3334e1" class="">草莓人戰士</strong> <br> <small><a href="#footer" title="@回覆" onclick="document.getElementById('comment').value += '@<a href="//jandan.net/ooxx/page-47#comment-3734604">草莓人戰士</a>: '">@24 hours ago</a></span></small> </div> <div class="text"><span class="righttext"><a href="//jandan.net/ooxx/page-47#comment-3734604">3734604</a></span><p><img src="//img.jandan.net/img/blank.gif" onload="jandan_load_img(this)" /><span class="img-hash">01efWrXKnc59BiRVcs2U49PtFIX1VkNkWMi/4fNbo547DAzz/x0SbLWsRfcUH5qCuSSSrVd2eTM3Yi8NpSNYYJ6SvedHJJGQw6P2cfTRqdlynavSJUmmrSY</span><br /> <img src="//img.jandan.net/img/blank.gif" onload="jandan_load_img(this)" /><span class="img-hash">4b1doZOmnAdA2XZIZ0trocIqDEz7bqrN0jel8zSvcNRSh4xcnP0iNsojUZMKd9YuU8daGmbUBm0NtmZcAjcScM4JAMAq0tHGliVrekavVIeB8hIc+KGqdJI</span><br /> <img src="//img.jandan.net/img/blank.gif" onload="jandan_load_img(this)" /><span class="img-hash">3c48IBOKg44u0mwdXOFpDFEjxNeWgYk3/o5OzbmgSi5fBz5r060SzTwPVBgPIXg9i3yFBwwztwrZYR2bjYEwesbIbGFZuml7ngJNAuASe2Xjt5AD2DRaWRU</span></p> </div> <div class="jandan-vote"> <span class="comment-report-c"> <a title="投訴" href="javascript:;" class="comment-report" data-id="3734604">[投訴]</a> </span> <span class="tucao-like-container"> <a title="圈圈/支援" href="javascript:;" class="comment-like like" data-id="3734604" data-type="pos">OO</a> [<span>123</span>] </span> <span class="tucao-unlike-container"> <a title="叉叉/反對" href="javascript:;" class="comment-unlike unlike" data-id="3734604" data-type="neg">XX</a> [<span>51</span>] <a href="javascript:;" class="tucao-btn" data-id="3734604"> 吐槽 [9] </a> </span> </div> </div> </div> </li>
上面貼了段原始檔,可以看出來,煎蛋網對於圖片反扒採取的是JS動態獲取方式。
核心方法是:jandan_load_img
這端程式碼在頁面沒找到。讀了下原始碼:
<!-- |
<script src="//cdn.jandan.net/static/min/2163d136d2142160d02749fa2e4a8131.51111215.js"></script> |
從這裡看出,這個原始碼也是不斷的在修改的,道高一尺魔高一丈。說不定哪天又改了。
這段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 href="' + c.replace(/(\/\/\w+\.sinaimg\.cn\/)(\w+)(\/.+\.(gif|jpg|jpeg))/, "$1large$3") + '" target="_blank" class="view_img_link">[a]</a>'); d.before(a); d.before("<br>"); 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.onload=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下載佇列,解密並下載。
*************************************************************
結果不美好,過程記錄下。