Java爬虫入门篇(二)Java 8 Nashorn 动态执行js脚本

  • 场景描述:一些网站的 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,以及防盗链简述

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,991评论 19 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,778评论 18 399
  • 文/陈安若 来到简书两个月,收获多多,学到很多好习惯,感受到浓厚的学习、积极向上的氛围,让我也不觉跟着成长。 在此...
    陈安若阅读 412评论 6 5
  • 记录一段真实生活中人物的语言或者是两三个人物的对话。建议大家先录音,然后逐字打出来。 大学老师授课内容也算是真实生...
    一个文字狗阅读 236评论 1 0
  • 文/熟格 我想, 最美好的生活方式, 不是躺在床上每天睡到自然醒, 也不是坐在办公室里无所事事, 更不是四海云游、...
    是舒格阅读 206评论 0 2