網絡機器人的識別與攻防的經典案例(也即爬蟲與反爬蟲的經典案例)

本文咱們介紹一個網絡機器人的識別與攻防的經典案例(也即爬蟲與反爬蟲的經典案例)。使用到的代碼見本人的superword項目:javascript

https://github.com/ysc/superword/blob/master/src/main/java/org/apdplat/superword/tools/ProxyIp.java html


咱們的目的是要使用機器人自動獲取站點http://ip.qiaodm.com/ 和站點http://proxy.goubanjia.com/ 的免費高速HTTP代理IP和端口號。java


不過他們未對機器人進行識別,如經過以下代碼就能夠獲取網頁內容:git

public static void main(String[] args) {
    try {
        String url = "http://proxy.goubanjia.com/";
        HttpURLConnection connection = (HttpURLConnection)new URL(url).openConnection();
        connection.setConnectTimeout(10000);
        connection.setReadTimeout(10000);
        connection.setUseCaches(false);
        BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        StringBuilder html = new StringBuilder();
        String line = null;
        while ((line=reader.readLine()) != null){
            html.append(line);
        }
        LOGGER.info("HTML:"+html);
    }catch (Exception e){
        LOGGER.error(e.getMessage());
    }
}

儘管如此,可是他們卻考慮到了機器人的防範,經過分析發現,兩個站點的防範措施是一致的,因此破一得二。github

他們是如何防範的呢?咱們看看IP:163.125.217.56和端口:9797,咱們利用FIREFOX的FIREBUG插件進行分析,以下圖所示:服務器

這裏,若是咱們直接調用選中的td節點的Jsoup的Element的text()方法,那麼獲得的值就會是  16363.1.125  25.21717.5.5,而不是咱們在頁面上看到的IP:163.125.217.56,還有<script>下面的那個6咱們在源代碼中是看不到的,這是<script>裏面的JS執行以後動態生成的結果,對於端口9797也同樣,在源代碼中全部的端口所有是8080,咱們這裏之因此在上圖中看到了6和9797,這是由於FIREBUG插件看到的是網頁加載完畢且全部JS執行完畢以後的視圖。網絡

經過上面的分析,咱們知道,防範的方法是將IP拆開在中間加入一些隱藏字符,並利用JS動態生成部分字符,而端口所有都是利用JS生成的app

那麼咱們如何來應對這種防範方法呢?首先的第一個要求是咱們的機器人要能動態執行JS其次是咱們須要對IP字段進行逐節點分析,忽略隱藏節點中的字符。下面用代碼說明:ui

一、動態執行JSurl

引入htmlunit依賴,注意的是若是你是使用slf4j日誌的話,須要排除commons-logging依賴。
<dependency>
    <groupId>net.sourceforge.htmlunit</groupId>
    <artifactId>htmlunit</artifactId>
    <version>2.14</version>
    <exclusions>
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

接下來看代碼,這裏獲取到的html就是執行JS以後的內容:

private static final WebClient WEB_CLIENT = new WebClient(BrowserVersion.INTERNET_EXPLORER_11);
String html = ((HtmlPage)WEB_CLIENT.getPage(url)).getBody().asXml();


二、對IP字段進行逐節點分析,忽略隱藏節點中的字符

引入jsoup依賴。
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.8.1</version>
</dependency>

接下來看代碼:

private static String getIps(Element element){
    StringBuilder ip = new StringBuilder();
    Elements all = element.children();
    LOGGER.info("");
    LOGGER.info("開始解析IP地址,機器讀到的文本:"+element.text());
    AtomicInteger count = new AtomicInteger();
    all.forEach(ele -> {
        String html = ele.outerHtml();
        LOGGER.info(count.incrementAndGet() + "、" + "原始HTML:"+html.replaceAll("[\n\r]", ""));
        String text = ele.text();
        if(ele.hasAttr("style")
                && (ele.attr("style").equals("display: none;")
                    || ele.attr("style").equals("display:none;"))) {
            LOGGER.info("忽略不顯示的文本:"+text);
        }else{
            if(StringUtils.isNotBlank(text)){
                LOGGER.info("須要的文本:"+text);
                ip.append(text);
            }else{
                LOGGER.info("忽略空文本");
            }
        }
    });
    LOGGER.info("----------------------------------------------------------------");
    LOGGER.info("解析到的ip: "+ip);
    LOGGER.info("----------------------------------------------------------------");
    Matcher matcher = IP_PATTERN.matcher(ip.toString());
    if(matcher.find()){
        String _ip = matcher.group();
        LOGGER.info("ip地址驗證經過:"+_ip);
        return _ip;
    }else{
        LOGGER.info("ip地址驗證失敗:"+ip);
    }
    return null;
}

接着看運行過程的輸出:

開始解析IP地址,機器讀到的文本:61 61 . 18 18 5. 1 1 49 .1 .1 63
一、原始HTML:<div style="display:inline-block;">  <script type="text/javascript">//<![CDATA[document.write('');//]]>                </script> </div>
忽略空文本
二、原始HTML:<label style="display: none;"> 61 </label>
忽略不顯示的文本:61
三、原始HTML:<span> 61 </span>
須要的文本:61
四、原始HTML:<div style="display:inline-block;">  . </div>
須要的文本:.
五、原始HTML:<div style="display:inline-block;">  <script type="text/javascript">//<![CDATA[document.write('');//]]>                </script> </div>
忽略空文本
六、原始HTML:<span style="display:inline-block;"> <script type="text/javascript">//<![CDATA[document.write('');//]]>                </script> </span>
忽略空文本
七、原始HTML:<p style="display: none;"> 18 </p>
忽略不顯示的文本:18
八、原始HTML:<span> 18 </span>
須要的文本:18
九、原始HTML:<div style="display:inline-block;">  <script type="text/javascript">//<![CDATA[document.write('5.');//]]>                </script> 5. </div>
須要的文本:5.
十、原始HTML:<p style="display: none;"> 1 </p>
忽略不顯示的文本:1
十一、原始HTML:<span> 1 </span>
須要的文本:1
十二、原始HTML:<div style="display:inline-block;">  49 </div>
須要的文本:49
1三、原始HTML:<label style="display:none;"> .1 </label>
忽略不顯示的文本:.1
1四、原始HTML:<span> .1 </span>
須要的文本:.1
1五、原始HTML:<div style="display:inline-block;">  <script type="text/javascript">//<![CDATA[document.write('');//]]>                </script> </div>
忽略空文本
1六、原始HTML:<span style="display:inline-block;"> 63 </span>
須要的文本:63
----------------------------------------------------------------
解析到的ip: 61.185.149.163
----------------------------------------------------------------


下面是經過上面的分析程序獲取到的部分能隱藏本身IP的代理服務器IP和端口號:

124.88.67.33:81
183.207.224.13:80
111.161.126.101:80
183.207.228.51:80
123.138.184.228:80
120.131.128.212:85
111.12.251.199:80
111.1.36.6:80
111.206.86.76:80
120.198.243.111:80
222.138.229.17:8104
123.125.104.240:80
124.88.67.25:81
202.102.22.182:80
183.207.228.114:80
162.208.49.45:8089
183.207.228.116:80
120.192.249.74:80
124.202.177.26:8118
124.88.67.32:80
111.161.126.100:80
183.207.224.14:80
183.207.224.43:80
111.206.81.248:80
183.207.224.45:80
182.118.31.110:80
124.88.67.53:80
111.13.109.52:80
190.38.26.167:8080
118.26.183.43:80
101.226.249.237:80
202.108.50.75:82
202.106.16.36:3128
111.1.36.133:80
124.88.67.24:80

有了這些IP和端口號,咱們在JAVA中如何使用呢?只須要設置系統屬性便可。

System.setProperty("proxySet", "true");
System.setProperty("http.proxyHost", ip);
System.setProperty("http.proxyPort", port);

設置完系統屬性以後,咱們如何判斷有沒有生效呢?咱們能夠經過看看在ip138的眼中,本身的IP是多少,而後和本身以前的IP做比較,看是否發生變化,若是發生變化,則認爲咱們的代理成功爲咱們向外部隱藏了本身的真實IP。

如何從ip138獲取本身的外部地址呢?看以下代碼

public static String getCurrentIp(){
    try {
        String url = "http://1111.ip138.com/ic.asp?timestamp="+System.nanoTime();
        String text = Jsoup.connect(url)
                .header("Accept", ACCEPT)
                .header("Accept-Encoding", ENCODING)
                .header("Accept-Language", LANGUAGE)
                .header("Connection", CONNECTION)
                .header("Host", "1111.ip138.com")
                .header("Referer", "http://ip138.com/")
                .header("User-Agent", USER_AGENT)
                .ignoreContentType(true)
                .timeout(5000)
                .get()
                .text();
        LOGGER.info("檢查自身IP地址:"+text);
        Matcher matcher = IP_PATTERN.matcher(text);
        if(matcher.find()){
            String ip = matcher.group();
            LOGGER.info("自身IP地址:"+ip);
            return ip;
        }
    }catch (Exception e){
        LOGGER.error(e.getMessage());
    }
    LOGGER.info("檢查自身IP地址失敗,返回以前的IP地址:"+ previousIp);
    return previousIp;
}

最後看看程序運行的部分截圖以下:

嘗試使用新的代理:186.91.60.155:8080
檢查自身IP地址:您的IP地址 您的IP是:[186.91.60.155] 來自:委內瑞拉
自身IP地址:186.91.60.155
Thread[main,5,main]自動更換代理成功!
Thread[main,5,main]更換代理耗時:4025毫秒
將66條代理IP地址寫入本地
將81條能隱藏本身的代理IP地址寫入本地
將108條不能隱藏本身的代理IP地址寫入本地
Thread[main,5,main]請求從新更換代理
Thread[main,5,main]開始從新更換代理
嘗試使用新的代理:117.158.98.214:80
檢查自身IP地址:您的IP地址 您的IP是:[117.158.98.214] 來自:河南省許昌市 移動
自身IP地址:117.158.98.214
Thread[main,5,main]自動更換代理成功!
Thread[main,5,main]更換代理耗時:176毫秒
將66條代理IP地址寫入本地
將81條能隱藏本身的代理IP地址寫入本地
將108條不能隱藏本身的代理IP地址寫入本地
Thread[main,5,main]請求從新更換代理
Thread[main,5,main]開始從新更換代理
嘗試使用新的代理:120.131.128.212:85
檢查自身IP地址:您的IP地址 您的IP是:[111.200.10.82] 來自:北京市 聯通
自身IP地址:111.200.10.82
Thread[main,5,main]自動更換代理成功!
Thread[main,5,main]更換代理耗時:240毫秒
將66條代理IP地址寫入本地
將81條能隱藏本身的代理IP地址寫入本地
將108條不能隱藏本身的代理IP地址寫入本地



完整的代碼見本人的superword項目:https://github.com/ysc/superword/blob/master/src/main/java/org/apdplat/superword/tools/ProxyIp.java 

相關文章
相關標籤/搜索