使用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執行引擎的上下文中,但是沒能成功,後來在網上看到七月流光
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])