爬虫专题程序员Java学习笔记

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选择器类似

转载请注明出处,原文作者:殷天文

系列教程
Java爬虫入门篇(一)HttpClient+jsoup,以及防盗链简述

上一篇下一篇

猜你喜欢

热点阅读