Selenium Webdriver 的使用java执行js代码 解决 ScriptEngine不支持浏览器内置对象window,document的问题

原创 2017年02月27日 10:39:48

问题场景:

使用java 掉用js代码,发现 ScriptEngine不支持浏览器内置对象window,document的问题;

问题一:为什么要 用java掉用js代码?

    比如在 抓取(爬取)对方网站时,需要破解一些js逻辑代码合作加密算法,但是js混淆了,不能直接翻译出对应的逻辑,或者翻译的代价太高;

   那么 不如 直接 使用js文件,模拟调用;这是 就会 使用到 java调用js的场景;

  java 有支持调用js的解析引擎,示例代码如下:

/**
	 * 获取java 提供的 ScriptEngine 脚本执行引擎 信息
	 * @throws IOException
	 */
	@Test
	public void showScriptEngine() throws IOException {
		ScriptEngineManager manager = new ScriptEngineManager();
		List<ScriptEngineFactory> factories = manager.getEngineFactories();
		for (ScriptEngineFactory f : factories) {
			System.out.println(f.getEngineName());
			System.out.println(f.getEngineVersion());
			System.out.println(f.getLanguageName());
			System.out.println(f.getLanguageVersion());
			System.out.println(f.getExtensions());
			System.out.println(f.getMimeTypes());
			System.out.println(f.getNames());
		}
	}
	  
	/**
	 * java调用js 解析执行js脚本(js文件)代码
	 * 场景:
	 * 1,使用js特有的优势
	 * 2,js特有的一些加密方式,并且js代码进行混淆了,转换为java方式 代价比较高的情况下
	 * @throws Exception
	 */
	@Test
	public void testjava2js() throws Exception {
		ScriptEngineManager manager = new ScriptEngineManager();
		ScriptEngine engine = manager.getEngineByName("javascript");
		
		// 如果需要引用 js外部文件,可以通过流把js文件(jquery.js)读到一个String变量
		
		/*
		String jsFileName = "C:\\jquery.min.js"; // 读取js文件
		FileReader reader = new FileReader(jsFileName); // 执行指定脚本
		engine.eval(reader);
		*/
		String script = "function add(op1,op2){return op1+op2};var res1=2,res=10; "; //定义函数并调用
		engine.eval(script);
		Invocable invoke = (Invocable) engine; // 调用方法,并传入两个参数
		
		// 方式1 通过对象调用方法, 获取结果
		Object c = invoke.invokeFunction("add", 1,2);
		System.out.println(c);
		
		// 方式2 执行js脚本调用方法, 获取结果
		engine.eval("var res = add(2,3);");
		
		// 获取新定义的变量,会覆盖原有同名变量
		Object o = engine.get("res");
		System.out.println(o);
		
		// 获取 原有脚本/脚本文件 中的 变量
		Object o2 = engine.get("res1");
		System.out.println(o2);
		
		// 测试 浏览器 内置对象 是否支持
		try {
			engine.eval("alert(2);");
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		try {
			engine.eval("document.write(2);");
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		try {
			engine.eval("var innerHeight=window.innerHeight");
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		try {
			engine.eval("var userAgent=navigator.userAgent");
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		// ...so, ScriptEngine 不支持 浏览器内置对象;nodejs 也一样不支持  浏览器内置对象
	}

问题,这里有个问题,如果js代码中 有获取 浏览器 内置对象的 地方,比如 window.location, navigator.userAgent等,

那么ScriptEngine 解析的时候 就会 提示:

ReferenceError: "alert" is not defined in <eval> at line number 1
ReferenceError: "document" is not defined in <eval> at line number 1
ReferenceError: "window" is not defined in <eval> at line number 1
ReferenceError: "navigator" is not defined in <eval> at line number 1

那么 怎么 让 解析的时候 让ScriptEngine 支持 浏览器内置对象列?

首先 ScriptEngine 本身 不带 浏览器内核,那么就需要去 找其他 解析引擎;

其次 尝试 看了nodejs的文档,传说中 nodejs使用的v8引擎,应该支持吧,

const v8 = require('v8');
const vm = require('vm');

尝试了一下 发现 并不支持。他也是 后台的模式,和java的ScriptEngine 一致,只支持解析 非浏览器内核的js逻辑代码;

也对,毕竟 后台执行的js逻辑代码 怎么会 需要 获取浏览器对象列?

只能说我这个场景 太特殊了;


ok,此时 两个办法:

1,找到一个 java可以调用的 浏览器内核,模拟 js在浏览器中执行,并获取 执行之后的结果。

2,老老实实 读 js代码,读懂后 找到 java的逻辑实现方式。

分工一下 两个方式 同时开始。


小插曲:

 为什么 不可以 把js代码放在jsp 或者html中,通过http的方式调用 获取返回的结果?

 首先 把js代码放在jsp 或者html中,获取执行结果。

jsp是在 服务器端就执行好了,js代码是浏览器中执行的;

http访问的是服务器,中间没有经过浏览器,就是说 页面中jsp的代码 执行了,但是js代码是不会有机会执行。

其次:http访问jsp或者html 返回的是服务器编译执行后的 结果代码,而这些 页面代码 在通过浏览器 执行后,呈现给用户;

用户才看到的是 华丽的页面;

所以这里 不清楚 web编程 和 html js css 解析渲染时机的 同学要注意了。


所以 把js代码 挂在 web容器中 再访问 是不能解决这个问题,因为 中间 同样没有浏览器内核。

虽然 以为在浏览器 访问 web地址 可以看到 结果;但是 用java http 后台访问 是不行滴。


我来继续寻找,浏览器内核的解决方式。


这时想起了 web自动化测试的方案,曾经看到测试的同事 用java编写 web自动化测试的脚本;

可以通过 java代码打开浏览器进行 元素定位,数值校验,逻辑登陆 结果校验。。


百度了一下:WEB 自动化测试工具 看到了 selenium web自动化测试工具套 

可以看看 这个文章 ,搭建环境示例 :http://blog.csdn.net/kash_chen007/article/details/40586067

逻辑是这样的:

java 通过 selenium 工具套件,结合 本地的 浏览器驱动 与 本地的 浏览器 进行交互,来 模拟操作;

达到的效果是:

java 通过 selenium   可以打开 chrome/firefox/IE等浏览器 (装不同的驱动,打开不同的浏览器)

java 通过 selenium  打开浏览器 输入网址,获取 浏览器解析之后的 结果。

还可以通过 元素定位 ,元素解析的方式,获取指定区域的 数据。


好了 ;

原理问题解决了,废话少说 ,上代码:

方式1,显示调用;

方式2,后台执行,用于部署到正式环境(这个方式不需要依赖驱动包和本地浏览器,有jar包即可)

/**
	 * 方式1;使用 ChromeDriver 显式调用 浏览器
	 * 当然这里 也可以用firefox IE等其他 浏览器和diver,下载不同的浏览器和驱动即可
	 * java 通过 selenium 工具套-->打开本地浏览器-->输入html文件地址(支持远程和本地)-->执行html中的脚本代码-->获取结果
	 * 这里 获取的结果 不是 html代码,而是 在浏览器解析完html js代码之后展示的信息(这里可能不太好理解,一会我画个图 单独描述下,html js jsp执行的时机问题)
	 * 详细描述:
	 * java通过 selenium 工具套 ,让js脚本在浏览器内核中执行,然后 获取返回结果
	 * 这样就解决了 java ScriptEngine 不支持浏览器内置对象的问题
	 * 如此一样js代码 与实际web运行的环境一样执行
	 * @throws Exception
	 */
	@Test
	public void testSelenium0() throws Exception {
		// 第一步:因为是 使用chromediver所以 需要先安装chrome浏览器,chromediver
		// 这个博客介绍的很详细,地址:https://www.testwo.com/blog/6931
		
		// 第二步:设置环境变量
		System.setProperty("webdriver.chrome.driver",
				"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chromedriver.exe");
		ChromeDriver webDriver = new ChromeDriver();
		
		long start = System.currentTimeMillis();
		
		// 第三步:设置访问地址
		//----
		// 这里和浏览器 地址栏目 可以输入的 地址一样 支持 远程地址 和 本地地址
		//webDriver.get("http://www.51jdy.cn");
		//webDriver.get("http://localhost:8080/web/Noname2.html?pwd=98912&username=halou");
		webDriver.get("file:///Users/guangtaozhai/Documents/workspace/web/WebContent/Noname2.html?pwd=98912&username=halou");
		
		// 第四步:获取输出信息;html中的js代码执行后 在body中document.write()或者 赋值给body或者div
		// 元素定位 获取 <html> 中<body> 下面的 内容
		WebElement webElement = webDriver.findElement(By.xpath("/html/body"));
		System.out.println(webElement.getText());
		System.out.println("耗时:"+(System.currentTimeMillis()-start));
		///---
		webDriver.close();
	}
	
	/**
	 * 方式2:隐式调用(java 后台调用浏览器/selenium/webdriver)
	 * 因为 方式1 会打开本地浏览器,这样一是速度慢,二是 不满足后台执行的方式 比如 部署在linux服务器上以后
	 * 方式2 通过 selenium 提供的 HtmlUnitDriver 进行后台调用;执行效率大幅上升
	 * @throws Exception
	 */
	@Test
	public void testSelenium1() throws Exception {
		// 使用HtmlUnitDriver 是不需要 安装 浏览器 和 驱动支持
		HtmlUnitDriver webDriver = new HtmlUnitDriver();
		webDriver.setJavascriptEnabled(true); // 设置支持 js脚本解析 ,是不是跟 安卓的 webview设置很像?
		
		long start = System.currentTimeMillis();
		//---- 
		// 这里和浏览器 地址栏目 可以输入的 地址一样 支持 远程地址 和 本地地址
		//webDriver.get("http://www.51jdy.cn");
		//webDriver.get("http://localhost:8080/web/Noname2.html?pwd=98912&username=halou");
		webDriver.get("file:///Users/guangtaozhai/Documents/workspace/java2js/WebContent/Noname2.html?pwd=98912&username=halou");
				
		WebElement webElement = webDriver.findElement(By.xpath("/html/body"));
		System.out.println(webElement.getText() );
		System.out.println(System.currentTimeMillis()-start);
		///---
		webDriver.close();
	}

注意:

在实际开发的时候 最好 单独 起一个项目,发布成服务的方式;

1,单独项目 避免jar包冲突,在实际 开发的时候 我遇到了这个问题,企图通过maven 解决 也没有搞定。

2,发布成独立服务 避免了 性能问题 影响其他业务代码,这个 还是比较 耗费资源的。


项目测试代码git地址:https://git.oschina.net/s9/jdy-test

项目关键字:
java调用js
ScriptEngine
ScriptEngine不支持浏览器内置对象window,document等
selenium
Selenium Webdriver
web自动化测试
java打开浏览器执行并获取结果
java调用浏览器内核执行js
ChromeDriver
HtmlUnitDriver
java后台调用浏览器内核
web爬虫
selenium remote
Caused by: java.lang.ClassNotFoundException: org.openqa.selenium.remote.SessionNotFoundException

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

使用selenium抓取JS动态生成的页面

在抓取网页数据时,传统jsoup方案只能对静态页面有效,而有些网页数据往往是js生成的,所以这时候需要其它的方案。 首先的思路是分析js程序,对js的请求进行再次抓取,这适合于特定的页面抓取,要做到...
  • LGCSSX
  • LGCSSX
  • 2015-07-31 19:55
  • 5632

使用HtmlUnit模拟浏览器执行JS来获取网页内容

import com.gargoylesoftware.htmlunit.BrowserVersion; import com.gargoylesoftware.htmlunit.WebClient;...
  • earbao
  • earbao
  • 2014-11-30 20:07
  • 25277

selenium 执行 js

想测试 zTree ,那么大量的测试工作都是要依靠 js 的,所以让 Selenium 与 js 通信是至关重要的,查一下文档,建立起来简单的通信是不难的。      Java代码   ...

在selenium(webdriver)中执行js

代码 JavascriptExecutor js = (JavascriptExecutor) driver; js.executeScript(String script, object... a...

在 Java 程序中内嵌 Mozilla 浏览器

简介 SWT 浏览器部件是 SWT 所提供的众多部件中较为复杂的一个,它提供了在 Java 应用程序中内嵌浏览器应用并通过 Java API 与之进行交互的能力。一个典型的应用场景是,开发者可以在客...
  • aerchi
  • aerchi
  • 2015-03-20 15:23
  • 2895

自己动手做J浏览器——基于JAVA和火狐内核(gecko)

网上有关于JAVA调用火狐内核的资料不多,而且比较杂乱。我在此处整理出JAVA调用火狐内核制作浏览器最简单的方案,以供参考。 教程中所涉及到需要下载的资源,都在代码示例中,如果您不想一个一个下载...

java使用jdic组件调用ie内核

转载自:http://ppjava.com/?p=1878 JDIC(JDesktop Integration Components)是一个开源的项目,目的是构建消除本机应用程序和 Java 等价物...
  • memray
  • memray
  • 2013-09-28 12:52
  • 7096

超漂亮的纯JAVA浏览器(附源码)

因为公司某产品是客户端内嵌javaee应用,新版又想用java替代原c++客户端方案。 所以花了一个星期的时间调研了java内嵌式开源浏览器。 归结如下: MozSwing 独立运行,不依赖...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)