解决HtmlUnit执行JS报错提示ScriptException

问题描述

HtmlUnit作为一款比Selenium更轻量的HeadLess的Java版本浏览器模拟器,不需要在服务器上安装部署浏览器及其Driver程序。

但是,众所周知,HtmlUnit对JS脚本的支持并不是很友好,GitHub中大部分的issue都和JS执行错误有关,作者也一直在升级JS Engine。

笔者在实际使用过程中(HtmlUnit 4.1.0版本),也遇到了JS执行问题,并且issue、StackOverflow和google均未找到较好的解决方案。

具体问题原因,是由于原站使用了ES6中的asyncawaitthen等关键字和方法。

org.htmlunit.ScriptException: missing ; before statement (https://www.xxx.com/includes/js/common.js#322)
	at org.htmlunit.javascript.JavaScriptEngine$HtmlUnitContextAction.run(JavaScriptEngine.java:963)
	at org.htmlunit.corejs.javascript.Context.call(Context.java:585)
	at org.htmlunit.corejs.javascript.ContextFactory.call(ContextFactory.java:481)
	at org.htmlunit.javascript.HtmlUnitContextFactory.callSecured(HtmlUnitContextFactory.java:313)
	at org.htmlunit.javascript.JavaScriptEngine.compile(JavaScriptEngine.java:735)
	at org.htmlunit.javascript.JavaScriptEngine.compile(JavaScriptEngine.java:110)
	at org.htmlunit.html.HtmlPage.loadJavaScriptFromUrl(HtmlPage.java:1124)
	at org.htmlunit.html.HtmlPage.loadExternalJavaScriptFile(HtmlPage.java:1015)

解决方法

开始逆向思考,既然有这些不支持的关键字导致JS执行报错,能否在页面及JS资源加载时,修改网页及JS资源的源代码,过滤不支持的关键字。

通过翻阅HtmlUnit官网文档,发现有个ScriptPreProcessor接口,可以通过钩子修改JS的执行代码,实现代码如下:

this.browser.setScriptPreProcessor((htmlPage, sourceCode, sourceName, lineNumber, htmlElement) -> {
    // 处理HtmlUnit不支持ES6的async、await关键字,对js源码进行处理
    if (StringUtils.contains(sourceName, "tools.js")) {
        sourceCode = sourceCode.replace("async ", StringUtils.EMPTY);
        sourceCode = sourceCode.replace("await ", StringUtils.EMPTY);
    }
    
    return sourceCode;
});

大家可以根据自己遇到的具体问题,对sourceCode进行处理。

最后再引申一下,如果有些JS对象不支持,可以分析原站JS代码,通过Java代替实现JS的逻辑,再通过变量赋值、修改JS源码的方式进行处理。