1. 程式人生 > >手把手教你爬取妹紙圖片

手把手教你爬取妹紙圖片

序:

之前為了演示定向爬取的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下載佇列,解密並下載。

*************************************************************

結果不美好,過程記錄下。