Java爬虫入门篇(二)Java 8 Nashorn 动态执行j
2018-02-17 本文已影响344人
殷天文
- 场景描述:一些网站的 response 信息是加密数据,页面显示的时候通过调用js函数进行解密,我们爬到这些加密数据是毫无用处的
- 分析:如果我们用 Java 去模拟解密脚本难度系数极大,那么如果我们可以在 Java 端运行js脚本呢?
- 解决方案:可以可利用 Java 8 中的 Nashorn 引擎解决。
Nashorn通过在JVM上,以原生方式运行动态的JavaScript代码来扩展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;
/**
* 调用js函数所需
*/
private static final String DECODE_KEY = "abc";
/**
* 返回单例
*
* @return
*/
public static JavaScriptEngine getInstance() {
if (instance == null)
instance = new JavaScriptEngine();
return instance;
}
/**
* 无参构造器 初始化需要的js引擎
*
*/
private JavaScriptEngine() {
try {
//调用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");
//执行脚本
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脚本初始化失败");
}
}
/**
* 调用JavaScript的解密函数
*
* @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函数名, word, DECODE_KEY是参数
return (String) invocable.invokeFunction("Decrypt", word, DECODE_KEY);
}
}
执行engine.eval()
读取文件后,就可以用invocable.invokeFunction()
来调用js脚本中的function
了
注意:Nashorn无法执行 包含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信息转为公司实体
*
* @param url
* @throws Exception
*/
public Shop99Company html2Company(String url, Integer currentPage) throws Exception {
try {
Document doc = Jsoup.connect(url).userAgent(USER_AGENT).get();
//根据html结构,抓取有效数据
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引擎,调用js函数解密
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响应的html,这样似乎也挺方便的)。
Jsoup语法和jquery选择器类似
-
doc.getElementsByAttributeValue("class", "companyname").text();
根据class属性值获取元素,.text()
转换为字符串 -
doc.select("div.contxt p")
获取所有class='contxt '
下的<p>
-
doc.getElementById("detialAddr");
根据id获取元素 - 更多使用参考 jsoup 中文api
转载请注明出处,原文作者:殷天文