從零實現一個高性能網絡爬蟲(二)應對反爬蟲以前端數據混淆

摘要

  • 上一篇以知乎網爲例簡單分享網絡請求分析。這一篇主要分享一種應對反爬蟲的方法,前端數據混淆。

目的

開始

  • 打開這個網站首頁,而後控制檯查看ip和port的對應標籤。
  • 如上圖(圖一),從控制檯的標籤中能夠看出ip加了一些無關不顯示的標籤來混淆數據,這裏混淆的原理其實很簡單,經過標籤的style="display:none"屬性來達到混淆的目的,也就是包含這個屬性的標籤是不會顯示在頁面上的。知道了這一點就比較好處理了,只須要在解析的時候把包含style="display:none"屬性的標籤去掉。就能夠輕鬆的拿到ip和port數據了。
  • 代碼以下
     1 package com.cnblogs.wycm;
     2 
     3 import org.jsoup.Jsoup;
     4 import org.jsoup.nodes.Document;
     5 import org.jsoup.nodes.Element;
     6 import org.jsoup.select.Elements;
     7 import java.io.IOException;
     8 import java.net.URL;
     9 
    10 /**
    11  *
    12  * 數據的解析採用的是Jsoup框架,Jsoup是一個操做HTML標籤的Java庫,它提供了很是方便的API來提取和操縱庫,支持相似jquery的選擇器來查找標籤。
    13  * 因爲請求比較單一,這裏的網絡請求並無採用上一篇所使用HttpClient框架。直接經過Jsoup來執行http請求的。
    14  * 關於Jsoup的使用能夠參考http://www.open-open.com/jsoup/
    15  *
    16  */
    17 public class Chapter1 {
    18     public static void main(String[] args) throws IOException {
    19         Document document= Jsoup.parse(new URL("http://www.goubanjia.com/"), 10000);
    20         //獲取class='table'的table的全部子節點tr
    21         Elements elements = document.select("table[class=table] tr");
    22         for (int i = 1; i < elements.size(); i++){
    23             //獲取td節點
    24             Element td = elements.get(i).select("td").first();
    25             /**
    26              * 查找全部style屬性包含none字符串的標籤(頁面上未顯示的標籤),並移除
    27              * 包括如下兩種
    28              * style=display: none;
    29              * style=display:none;
    30              */
    31             for(Element none : td.select("[style*=none;]")){
    32                 none.remove();
    33             }
    34             //移除空格
    35             String ipPort = td.text().replaceAll(" ", "");
    36             //打印
    37             System.out.println(ipPort);
    38         }
    39     }
    40 }
    41 /*
    42 第一次運行打印結果:
    43 183.129.246.228:8132
    44 222.92.136.206:8987
    45 54.238.186.100:8988
    46 ...
    47 第二次運行打印結果:
    48 183.129.246.228:8377
    49 222.92.136.206:9059
    50 54.238.186.100:8622
    51 ...
    52 */
  • ip地址可以準確的拿到了,卻發現port被作了混淆,並且每次返回的port還在動態改變。你們能夠經過把瀏覽器的JavaScrip腳本關閉後,而後刷新這個網頁。會發現每次的port都不同。咱們每次看到的正確port都是經過JavaScript腳本處理後的。若是採用普通爬蟲的方式拿到的port都是錯誤的。如今要想拿到正確的port,能夠經過分析它JavaScrip腳本還原數據的邏輯。
  • 一樣打開控制檯->選擇Sources->選擇一行js代碼打斷點(點擊行編號),以下圖

  • 刷新網頁—>頁面Paused in debugger—>選擇Elements->右鍵td節點->Break on...->subtree modifications。這兩個步驟就是在設置斷點調試,也就是在td節點發生改變的時候paused。

  • 選擇Sources->F8(繼續執行),這個時候又會有一次pause,也就是js腳本在還原正確port的時候(以下圖)

  • 函數的調用棧有好多層,如何快速定位哪個函數的技巧就是,看它局部變量表的變量變化,由於這裏是port在發生改變,而後找到對應變量和對應邏輯函數。簡單分析能夠肯定到port發生改變的函數是一個匿名函數,以下圖

  • 格式化後,代碼以下:
     1 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", ''];
     2 $(function() {
     3     $(_$[0])[_$[1]](function() {
     4         var a = $(this)[_$[2]]();
     5         if (a[_$[3]](_$[4]) != -0x1) {
     6             return
     7         }
     8         ;var b = $(this)[_$[5]](_$[6]);
     9         try {
    10             b = (b[_$[7]](_$[8]))[0x1];
    11             var c = b[_$[7]](_$[9]);
    12             var d = c[_$[10]];
    13             var f = [];
    14             for (var g = 0x0; g < d; g++) {
    15                 f[_$[11]](_$[12][_$[3]](c[g]))
    16             }
    17             ;$(this)[_$[2]](window[_$[13]](f[_$[14]](_$[15])) >> 0x3)
    18         } catch (e) {}
    19     })
    20 })

     

  • 搜索'_$'字符串,能夠看到。代碼裏面有多處引用了這個數組的元素。其中\x後面是16進制數,對應asaii字符。好比\x61爲十進制的97,表示字符a。推薦一個在線的解碼工具:http://tool.lu/js。解碼後而後把代碼中引用到_$數組元素的地方,再進行替換。
  • 還原後以下:
     1 var _$ = ['.port', "each", "html", "indexOf", '*', "attr", 'class', "split", " ", "", "length", "push", 'ABCDEFGHIZ', "parseInt", "join", ''];
     2 $(function() {
     3     $('.port').each(function() {
     4         var a = $(this).html();
     5         if (a.indexOf('*') != -0x1) {
     6             return
     7         }
     8         ;var b = $(this).attr('class');
     9         try {
    10             b = (b.split(" "))[0x1];
    11             var c = b.split("");
    12             var d = c.length;
    13             var f = [];
    14             for (var g = 0x0; g < d; g++) {
    15                 f.push('ABCDEFGHIZ'.indexOf(c[g]))
    16             }
    17             ;$(this).html(window.parseInt(f.join('')) >> 0x3)
    18         } catch (e) {}
    19     })
    20 })
  • 這段代碼的邏輯,獲取port標籤的class屬性值,取出屬性中後面的幾個大寫字母,遍歷該字符串,找出每次字符在'ABCDEFGHIZ'這個字符串中的索引,而後parseInt轉換爲整數,而後進行右移3位的操做。
  • 完整代碼實現
     1 package com.cnblogs.wycm;
     2 
     3 import org.jsoup.Jsoup;
     4 import org.jsoup.nodes.Document;
     5 import org.jsoup.nodes.Element;
     6 import org.jsoup.select.Elements;
     7 
     8 import java.io.IOException;
     9 import java.net.URL;
    10 
    11 public class Chapter2 {
    12     public static void main(String[] args) throws IOException {
    13         Document document= Jsoup.parse(new URL("http://www.goubanjia.com/"), 10000);
    14         setPort(document);
    15         //獲取class='table'的table的全部子節點tr
    16         Elements elements = document.select("table[class=table] tr");
    17         for (int i = 1; i < elements.size(); i++){
    18             //獲取td節點
    19             Element td = elements.get(i).select("td").first();
    20             /**
    21              * 查找全部style屬性包含none字符串的標籤(頁面上未顯示的標籤),並移除
    22              * 包括如下兩種
    23              * style=display: none;
    24              * style=display:none;
    25              */
    26             for(Element none : td.select("[style*=none;]")){
    27                 none.remove();
    28             }
    29             //移除空格
    30             String ipPort = td.text().replaceAll(" ", "");
    31             //打印
    32             System.out.println(ipPort);
    33         }
    34     }
    35 
    36     /**
    37      * js代碼port還原
    38      * @param doc
    39      */
    40     private static void setPort(Document doc){
    41         for (Element e : doc.select(".port")){//$('.port').each(function() {
    42             String a = e.text();//var a = $(this).html();
    43             if(a.indexOf("*") != -0x1){//if (a.indexOf('*') != -0x1) {
    44                 return;
    45             }
    46             String b = e.attr("class");//var b = $(this).attr('class');
    47             b = b.split(" ")[0x1];//b = (b.split(" "))[0x1];
    48             String[] c = b.split("");//var c = b.split("");
    49             int d = b.length();//var d = c.length;
    50             StringBuilder f = new StringBuilder();//var f = [];
    51             for(int g = 0x0; g < d; g++){//for (var g = 0x0; g < d; g++) {
    52                 f.append("ABCDEFGHIZ".indexOf(c[g]));//f.push('ABCDEFGHIZ'.indexOf(c[g]))
    53             }
    54             e.text(String.valueOf(Integer.valueOf(f.toString()) >> 0x3));//$(this).html(window.parseInt(f.join('')) >> 0x3)
    55         }
    56     }
    57 }
  • maven依賴html

    1 <dependency>
    2        <groupId>org.jsoup</groupId>
    3        <artifactId>jsoup</artifactId>
    4        <version>1.10.2</version>
    5 </dependency>

總結

  • 該篇文章簡單分項了下如何應對前端混淆的反爬蟲。關於這種反爬蟲,還有其它的一些應對方式。如採用無頭瀏覽器的方式,好比phantomjs框架。這種無頭瀏覽器本來是用來作自動化測試的。它是基於webkit內核的,因此它能夠較容易的爬取這種前端混淆的這種網站。通常來講瀏覽器可以正常訪問到的數據,這種方式也能夠比較容易爬取這些數據。固然這種方式的最大問題就是效率比較低。由於這種方式它每加載一個頁面,都須要下載它的附加資源,如js腳本,腳本下載完成後,還要去執行js腳本。
  • 我這裏採用的方式是閱讀js代碼,得出前端混淆的邏輯,而後再經過目標語言來實現對應邏輯。這種方式若是針對一些簡單的加密混淆仍是頗有用的。可是當遇到一些大型複雜的網站,如百度、微博等,須要抓取登陸後的數據。這時候須要來手動模擬登陸,相對來講,這種網站的模擬登陸會更復雜,找各類登陸參數來源。都會耗費大量精力。分析請求的成本會比較高。這種方式的優勢就是爬取速度快,只獲取目標數據。不須要額外網絡請求成本。

 

 



一個程序員平常分享,包括但不限於爬蟲、Java後端技術,歡迎關注。前端

相關文章
相關標籤/搜索