1. 程式人生 > >使用Java抓取解析汽車之家車型配置資料

使用Java抓取解析汽車之家車型配置資料

因為公司業務需求,需要獲取汽車之家的車型配置資料如下圖:


        由於汽車之家沒做防爬策略,只是資料給混淆了,這裡主要說解析資料。

        通過儲存頁面,配置項的資料是通過JS動態生成的。在頁面的第572行左右,有配置項的json格式資料


        主要的配置資料是config和option,車身顏色color和內部顏色innerColor。一開始以為汽車之家的資料還挺好抓取,直接頁面上就有。但是通過格式化工具後發現,配置項的名稱和值的部分字,被隨機的用span標籤加CSS給替換了,例如下面一個:

<span class='hs_kw72_configKE'></span>

        那就去頁面裡面看看有沒有.hs_kw72_configKE的CSS樣式,然而並沒有找到,然後再找找引入的CSS檔案裡也沒有,這就頭疼了。最後發現這些用於替代文字的CSS樣式是通過JS程式碼生成的,並且JS程式碼也在頁面中,在儲存下來頁面中有兩行。


上面這個第一部分,下面這個是第二部分,注意第二部分是兩個<script></script>塊,我這隻截取了開頭


        一開始到這步的時候覺得沒戲了,一開始也想著怎麼能執行這段JS程式碼,Java可以執行JS的腳步,但是這些JS腳步裡面有瀏覽器物件,例如document,window等,也嘗試著通過Java寫這些相關的物件,然後放入JavaScript執行引擎的上下文中,但是沒能成功,後來在網上看到七月流光

一個相關的文章,也是這個問題,給出了一個比較巧的解決方法(七月流光的部落格地址)。他的方法是通過建立幾個JS物件替代,腳步執行所需要的物件,包括document和window物件。

var rules = "";
var document = {};
document.createElement = function() {
    return {
        sheet: {
            insertRule: function(rule, i) {
                if (rules.length == 0) {
                    rules = rule;
                } else {
                    rules = rules + "|" + rule;
                }
            }
        }
    }
};
document.getElementsByTagName = function() {};
document.querySelectorAll = function() {
    return {};
};
document.head = {};
document.head.appendChild = function() {};
var window = {};
window.decodeURIComponent = decodeURIComponent;
window.location = {};
window.location.href = "car.m.autohome.com.cn";

 將上面這段JS放到用於生成CSS的JS程式碼前,然後通過Java的腳步引擎執行這段JS,就能獲取到如下結果

把CSS樣式破解出來,那麼再把上面的json資料裡面的span給替換成漢字,再解析json資料就OK了。

    但是注意一點,生成CSS的JS程式碼好像有兩種,一種是裡面有漢字的,一種裡面沒漢字,沒漢字的Java在執行程式碼是加上-Xss=5m,否則會報StackOverflowError

Java測試程式碼如下,用到Jsoup

package cn.iautos.crawler.analysis.util.test;

import com.alibaba.fastjson.JSONObject;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import org.junit.Test;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * <br/>
 * Created on 2017/10/13 16:25.
 *
 * @author zhubenle
 */
public class UtilTest {

    public final static String SCRIPT_PRE = "var rules = '';var document = {};document.createElement = function() {return {sheet: {insertRule: " +
            "function(rule, i) {if (rules.length == 0) {rules = rule;} else {rules = rules + '|' + rule;}}}}};document.getElementsByTagName = " +
            "function() {};document.querySelectorAll = function() {return {};};document.head = {};document.head.appendChild = function() " +
            "{};var window = {};window.decodeURIComponent = decodeURIComponent;window.location = {};window.location.href = 'car.m.autohome.com.cn';";

    public final static Pattern CSS_PATTERN = Pattern.compile("\\.(.*)::before.*content:\"(.*)\".*");
    @Test
    public void testScript() throws Exception {
        String url = "https://car.autohome.com.cn/config/series/692.html";
        Connection.Response response = Jsoup.connect(url).validateTLSCertificates(false).ignoreContentType(true).ignoreHttpErrors(true).execute();
        System.out.println(response.statusCode());
        Document document = response.parse();
        Elements scripts = document.select("script:containsData(insertRule)");

        ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
        ScriptEngine engine = scriptEngineManager.getEngineByName("JavaScript");
        Map<String, String> cssKeyValue = new HashMap<>();
        System.out.println("------------css資料------------");
        scripts.forEach(element -> {
            String script = SCRIPT_PRE + element.html();
            try {
                engine.eval(script);
            } catch (ScriptException e) {
                e.printStackTrace();
            }
            String css = (String) engine.get("rules");
            System.out.println(css);
            for (String str : css.split("\\|")) {
                Matcher cssMatcher = CSS_PATTERN.matcher(str);
                if (cssMatcher.find()) {
                    cssKeyValue.put("<span class='" + cssMatcher.group(1) + "'></span>", cssMatcher.group(2));
                }
            }
        });
        Elements contents = document.select("script:containsData(keyLink)");
        String content = contents.html();
        System.out.println("------------用css混淆的配置資料------------");
        System.out.println(content);
        //把混淆資料裡的樣式用上面解析的樣式給替代
        for(Map.Entry<String, String> entry : cssKeyValue.entrySet()) {
            content = content.replaceAll(entry.getKey(), entry.getValue());
        }
        System.out.println("------------用css替換後的資料------------");
        System.out.println(content);
        engine.eval(content);
        System.out.println("------------每個配置結果------------");
        String keyLink = JSONObject.toJSONString(engine.get("keyLink"));
        String config = JSONObject.toJSONString(engine.get("config"));
        String option = JSONObject.toJSONString(engine.get("option"));
        String bag = JSONObject.toJSONString(engine.get("bag"));
        String color = JSONObject.toJSONString(engine.get("color"));
        String innerColor = JSONObject.toJSONString(engine.get("innerColor"));
        System.out.println(keyLink);
        System.out.println(config);
        System.out.println(option);
        System.out.println(bag);
        System.out.println(color);
        System.out.println(innerColor);
        //最後的資料,解析json就ok
    }
}

(謝謝,有問題發郵箱聯絡[email protected])