美文网首页爬虫专题程序员Java学习笔记
Java爬虫入门篇(二)Java 8 Nashorn 动态执行j

Java爬虫入门篇(二)Java 8 Nashorn 动态执行j

作者: 殷天文 | 来源:发表于2018-02-17 21:57 被阅读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

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

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

    相关文章

      网友评论

      • 鸿仔_5013:有办法让js引擎支持调用window对象吗?如navigator
        鸿仔_5013:@鸿仔_5013 主要是想模拟登录,但是密码是加密的,需要将密码解密
        殷天文:https://www.jianshu.com/p/054b50026f9a
        殷天文:@鸿仔_5013 你看看用cdp4j,能不能实现你想要的,他是通过chrome浏览器模拟请求

      本文标题:Java爬虫入门篇(二)Java 8 Nashorn 动态执行j

      本文链接:https://www.haomeiwen.com/subject/eucvtftx.html