由于汽车之家没做防爬策略,只是数据给混淆了,这里主要说解析数据。
通过保存页面,配置项的数据是通过JS动态生成的。在页面的第572行左右,有配置项的json格式数据
主要的配置数据是config和option,车身颜色color和内部颜色innerColor。一开始以为汽车之家的数据还挺好抓取,直接页面上就有。但是通过格式化工具后发现,配置项的名称和值的部分字,被随机的用span标签加CSS给替换了,例如下面一个:
那就去页面里面看看有没有.hs_kw72_configKE的CSS样式,然而并没有找到,然后再找找引入的CSS文件里也没有,这就头疼了。最后发现这些用于替代文字的CSS样式是通过JS代码生成的,并且JS代码也在页面中,在保存下来页面中有两行。
上面这个第一部分,下面这个是第二部分,注意第二部分是两个块,我这只截取了开头
一开始到这步的时候觉得没戏了,一开始也想着怎么能执行这段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;
/**
*
* 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 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("", 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 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])