- 場景描述:一些網(wǎng)站的 response 信息是加密數(shù)據(jù)队他,頁面顯示的時候通過調(diào)用js函數(shù)進(jìn)行解密开缎,我們爬到這些加密數(shù)據(jù)是毫無用處的
- 分析:如果我們用 Java 去模擬解密腳本難度系數(shù)極大,那么如果我們可以在 Java 端運行js腳本呢?
- 解決方案:可以可利用 Java 8 中的 Nashorn 引擎解決协怒。
Nashorn通過在JVM上,以原生方式運行動態(tài)的JavaScript代碼來擴(kuò)展Java的功能卑笨。
可以通過 Java 8 Nashorn 教程 來簡單了解一下
Nashorn 的使用
下面看Nashorn 使用實例:
import java.io.FileReader;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
public class JavaScriptEngine {
private static JavaScriptEngine instance = null;
private ScriptEngine engine;
/**
* 調(diào)用js函數(shù)所需
*/
private static final String DECODE_KEY = "abc";
/**
* 返回單例
*
* @return
*/
public static JavaScriptEngine getInstance() {
if (instance == null)
instance = new JavaScriptEngine();
return instance;
}
/**
* 無參構(gòu)造器 初始化需要的js引擎
*
*/
private JavaScriptEngine() {
try {
//調(diào)用Java8 nashorn 運行JavaScript腳本
this.engine = new ScriptEngineManager().getEngineByName("nashorn");
//讀取文件對象
Resource aesJs = new ClassPathResource("js/aes.js");
Resource modeEcbJs = new ClassPathResource("js/ecb.js");
Resource rnavJs = new ClassPathResource("js/nav.js");
//執(zhí)行腳本
this.engine.eval(new FileReader(aesJs.getFile()));
this.engine.eval(new FileReader(modeEcbJs.getFile()));
this.engine.eval(new FileReader(rnavJs.getFile()));
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("js腳本初始化失敗");
}
}
/**
* 調(diào)用JavaScript的解密函數(shù)
*
* @param word
* @return
* @throws NoSuchMethodException
* @throws ScriptException
*/
public String decodeData(String word) throws NoSuchMethodException, ScriptException {
if (StringUtils.isBlank(word)) {
throw new RuntimeException();
}
Invocable invocable = (Invocable) engine;
//Decrypt是js函數(shù)名, word, DECODE_KEY是參數(shù)
return (String) invocable.invokeFunction("Decrypt", word, DECODE_KEY);
}
}
執(zhí)行engine.eval()
讀取文件后孕暇,就可以用invocable.invokeFunction()
來調(diào)用js腳本中的function
了
注意:Nashorn無法執(zhí)行 包含window等瀏覽器對象的js腳本,例如jquery
下面是爬蟲代碼:
import java.io.IOException;
import javax.script.ScriptException;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.taven.web.hy88crawler.config.Constant;
import com.taven.web.hy88crawler.entity.Shop99Company;
import com.taven.web.hy88crawler.utils.RegularUtils;
public class Shop99Converter {
private static Shop99Converter instance = null;
private final static String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36";
private Logger log = LoggerFactory.getLogger(this.getClass());
/**
* 返回單例
*
* @return
*/
public static Shop99Converter getInstance() {
if (instance == null)
instance = new Shop99Converter();
return instance;
}
/**
* 將抓取到的html信息轉(zhuǎn)為公司實體
*
* @param url
* @throws Exception
*/
public Shop99Company html2Company(String url, Integer currentPage) throws Exception {
try {
Document doc = Jsoup.connect(url).userAgent(USER_AGENT).get();
//根據(jù)html結(jié)構(gòu)赤兴,抓取有效數(shù)據(jù)
String companyName = doc.getElementsByAttributeValue("class", "companyname").text();
String contacts = doc.select("div.contxt p").eq(0).text();
String encodePhone = doc.getElementsByAttributeValue("class", "phoneNumber").text();
String area = doc.getElementById("detialAddr").text();
//使用js引擎妖滔,調(diào)用js函數(shù)解密
JavaScriptEngine jsEngine = JavaScriptEngine.getInstance();
String mobile = jsEngine.decodeData(encodePhone);
if (StringUtils.isBlank(companyName) || !RegularUtils.isValidMobile(mobile)
|| StringUtils.isBlank(contacts)) {
return null;
} else {
Shop99Company shop99 = new Shop99Company();
shop99.setMobile(mobile);
shop99.setCompanyName(companyName);
shop99.setContacts(contacts);
return shop99;
}
} catch (NoSuchMethodException | ScriptException | IOException e) {
e.printStackTrace();
log.error(e.getMessage());
return null;
}
}
}
通過Document doc = Jsoup.connect(url).userAgent(USER_AGENT).get();
請求url返回Document
(即頁面response響應(yīng)的html,這樣似乎也挺方便的)桶良。
Jsoup語法和jquery選擇器類似
-
doc.getElementsByAttributeValue("class", "companyname").text();
根據(jù)class屬性值獲取元素座舍,.text()
轉(zhuǎn)換為字符串 -
doc.select("div.contxt p")
獲取所有class='contxt '
下的<p>
-
doc.getElementById("detialAddr");
根據(jù)id獲取元素 - 更多使用參考 jsoup 中文api
轉(zhuǎn)載請注明出處,原文作者:殷天文