我知道程序是可以调用chrome浏览器的,我以为很复杂需要安装除了chrome以外的driver程序才能驱动chrome,但是经过研究发现我错了,无论使用java或python或其他程序操作都一样简单,方法也一样。当然也有现成封装好的api,例如nodejs pyppeteer,前介绍过的Java操作Chrome浏览器的API库-cdp4j。但我还是觉得直接去操作chrome很直接方便。下面就介绍java如何直接操作chrome。
1.程序操作chrome浏览器原理
1.1Chrome DevTools Protocol 浏览器开发工具协议
使用程序操作chrome,你不需要干其他任何事,除了Chrome DevTools Protocol 。
官方文档:https://chromedevtools.github.io/devtools-protocol/。
https://vanilla.aslushnikov.com/
打开官网可以看到协议有三个版本
The latest (tip-of-tree) protocol (tot) 最新版
v8-inspector protocol (v8) nodejs版
stable 1.2 protocol (1-2) 稳定版
stable 1.3 protocol (1-3) 稳定版
使用tot最新版一般没什么问题,稳定版可能功能没那么多,上线前测试好久行
Chrome DevTools Protocol按名称理解就是一个协议,我只要按照这个协议来操作chrome就可以了,无论什么程序都按照同样的这个协议来操作chrome.只要使用http和webSocket按照他的文档编写json来和chrome通信。
1.2那怎么能操作到chrome?
Chrome Debug API包括两个部分:标签管理和页面管理。首先我们介绍标签管理部分,它是Rest形式的API接口,而页面管理是webSocket+json就能和html页面通信,json的格式按照官方文档的格式。
首先你要安装chrome,参考:linux centos安装google chrome浏览器使用headless无头模式,然后你可以用命令启动
chrome --headless --disable-gpu --user-data-dir= --remote-debugging-port=9222
--headless表示无头浏览器,不用看到界面,windows调试可以不用这个参数,正式使用的时候要使用这个,特别是linux服务器
--disable-gpu参数在未来可能不需要
--user-data-dir 指定不同目录,代表启动不同的实例,这个并发操作很有用。
--remote-debugging-port 远程调试模式打开浏览器,关键是这个
先把浏览器关掉,再用命令启动,先不使用--headless测试一下,浏览器起来后访问
浏览器访问http://127.0.0.1:9222/json
[ {
"description": "",
"devtoolsFrontendUrl": "/devtools/inspector.html?ws=127.0.0.1:9222/devtools/page/5FCC37E7406644DEEFB12F840E904419",
"id": "5FCC37E7406644DEEFB12F840E904419",
"title": "127.0.0.1:9222/json",
"type": "page",
"url": "http://127.0.0.1:9222/json",
"webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/page/5FCC37E7406644DEEFB12F840E904419"
}, {
"description": "",
"devtoolsFrontendUrl": "/devtools/inspector.html?ws=127.0.0.1:9222/devtools/page/F44194E32AEA4076FC645C86B0B66DFB",
"faviconUrl": "http://bcxw.net/favicon.ico",
"id": "F44194E32AEA4076FC645C86B0B66DFB",
"title": "文章编辑_编程学问网",
"type": "page",
"url": "http://bcxw.net/user/articleEditor.html?id=703",
"webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/page/F44194E32AEA4076FC645C86B0B66DFB"
} ]
看到这个页面说明你已经成功了
chrome支持简单操作使用http+json
常见的访问命令如下:
-
http://127.0.0.1:9222/json :查看已经打开的Tab列表
-
http://127.0.0.1:9222/json/version : 查看浏览器版本信息
-
http://127.0.0.1:9222/json/new?http://www.baidu.com : 新开Tab打开指定地址
-
http://127.0.0.1:9222/json/close/92615aad-5862-48d5-983d-248468e9741a : 关闭指定Id的Tab页面
-
http://127.0.0.1:9222/json/activate/92615aad-5862-48d5-983d-248468e9741a : 切换到指定Id的Tab页面
复杂的操作就需要使用webSocket请求webSocketDebuggerUrl对应的地址来操作html页面,具体的访问参数参考上面官方文档,下面介绍如何通过程序和chrome通信
2.windows cmd 命令测试操作chrome
我这里主要用windows来演示,linux上面道理也一样,参考:linux centos安装google chrome浏览器使用headless无头模式
请先下载安装google chrome谷歌浏览器,我安装在C:\Users\Administrator\AppData\Local\Google\Chrome\Application
2.1配置环境变量
windows搜索编辑系统环境变量-点击环境变量-选择系统变量path-点击编辑-点击新建-把google浏览器的安装地址粘贴进去(C:\Users\Administrator\AppData\Local\Google\Chrome\Application)
然后重新打开cmd命令-直接输入chrome-发现打开弹出浏览器
2.2测试和chrome浏览器通信
2.2.1启动chrome debug模式
cmd命令输入(先不使用无头模式方便看效果,且指定了user-data-dir表示启动新的实例,不要和你当前打开的浏览器冲突了):
chrome --disable-gpu --user-data-dir=d:/test/chrome --remote-debugging-port=9222
linux的命令是:
google-chrome --headless --disable-gpu --user-data-dir=/test/chrome --remote-debugging-port=9222
输入命令回车发现马上弹出google浏览器,不要管理,最小化就可以
2.2.2 http和chrome浏览器通信
上面说到和chrome浏览器通信分为标签管理和页面管理,标签管理用http就能做到,而页面管理则要使用websocket.这里我们先测试http通信
浏览器打开地址:http://127.0.0.1:9222/json
页面会输出
[ {
"description": "",
"devtoolsFrontendUrl": "/devtools/inspector.html?ws=127.0.0.1:9222/devtools/page/33BFC127FDD36501A0DD0202D4FC9A01",
"id": "33BFC127FDD36501A0DD0202D4FC9A01",
"title": "欢迎使用 Chrome",
"type": "page",
"url": "chrome://welcome/",
"webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/page/33BFC127FDD36501A0DD0202D4FC9A01"
} ]
这就是你刚才命令打开的浏览器,说明你和浏览器通信成功
-
Id:页面的id信息
-
devtoolsFrontendUrl:开发工具Url,可以通过chrome访问这个url来实现内置的调试工具访问
-
type:当前进程的类型,只有类型为page的才是浏览器页面,其余类型的是后台背景进程,我们不需要操作这类对象
-
url:当前页面访问的地址
-
webSocketDebuggerUrl:当前页面的调试接口地址
2.2.3 websocket操作chrome浏览器
这里举一个websocket例子,进行页面跳转,来测试操作chrome浏览器,要用到webSocketDebuggerUrl这个属性,使用在线websocket工具来测试http://www.websocket-test.com/。
复制你的webSocketDebuggerUrl属性值,粘贴到工具里点击断开再点击连接,看是否能连接上。
找到文档中页面跳转的章节https://chromedevtools.github.io/devtools-protocol/tot/Page#method-navigate
按照文档的要求组装json
{"method":"Page.navigate","params":{"url":"http://bcxw.net"},"id":1}
复制到消息窗口,发送,发现刚才命令打开的浏览器页面跳转了,这下激动了吧。
需要注意的是除了官方协议文档要求的参数,我们还必须带id参数,这个参数是你自己用来表示你自己的指令,否则会报错
{error:{code:-32600,message:"Message must have integer 'id' property"}}
不论多复杂的操作,都能在文档里面找到,然后发websocket消息给浏览器,就能实现。好了我们已经会了如何操纵通信chrome浏览器,下面就是java的案例
3.java操作通信chrome浏览器
首先要安装google浏览器,配置环境变量,前面详细介绍了
在写代码前还是要多看看官方文档:https://chromedevtools.github.io/devtools-protocol/
因为我们会面临一个问题,就是浏览器browser-标签页target-页面page-子页面iframe,这是一个有层级的结构,我们调用到某一层级,得从上级拼接参数然后连接websocket,然后发起操作信息,这很不方便,我们希望得是扁平化的,一个通信地址传不同的参数就能操作不同的对象,chromedevtools官方页有这样的考虑,就是传sessionId,
参考:https://github.com/aslushnikov/getting-started-with-cdp/blob/master/README.md,
如果不传sessionId,你拿browser的通信地址是操作不到page页面的。
下面写一个java操作浏览器的完整案例
3.1pom
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
3.2 java代码
//浏览器标签Id
static String targetId="";
//页面sessionId
static String sessionId="";
public static void main(String[] args) {
try {
//命令启动浏览器
StringBuffer strCmd=new StringBuffer();
String os = System.getProperty("os.name");
if(os.toLowerCase().startsWith("windows")){
strCmd.append("chrome ");
}else if(os.toLowerCase().startsWith("linux")){
strCmd.append("google-chrome ");
}
//strCmd.append("--headless ");
strCmd.append("--disable-gpu ");
strCmd.append("--enable-automation ");
strCmd.append("--user-data-dir=chromeHome22 ");
//port=0 表示自动分配端口并返回启动信息
strCmd.append("--remote-debugging-port=0 ");
Process process=Runtime.getRuntime().exec(strCmd.toString());
//获取启动返回信息
InputStreamReader isr=new InputStreamReader(process.getErrorStream());
BufferedReader br=new BufferedReader(isr);
long startTime=new Date().getTime();
String browserWs="";
while(true){
if(new Date().getTime()-startTime>=5000){
break;
}
browserWs=br.readLine();
if(browserWs!=null&&browserWs.startsWith("DevTools")){
browserWs=browserWs.substring(browserWs.indexOf("ws:"));
break;
}else{
Thread.sleep(100);
}
}
if(StringUtils.isEmpty(browserWs)){
throw new RuntimeException("初始化chrome失败");
}
System.out.println(browserWs);
//websocket 通信
URI uri=new URI(browserWs);
WebSocketClient webSocketClient=new WebSocketClient(uri) {
@Override
public void onOpen(ServerHandshake serverHandshake) {
System.out.println("已经连接");
}
@Override
public void onMessage(String s) {
System.out.println("接受到消息:"+s);
JSONObject message=JSON.parseObject(s);
if(message.getIntValue("id")==1){
//如果是 获取 target的回调
targetId=message.getJSONObject("result").getJSONArray("targetInfos").getJSONObject(0).getString("targetId");
}else if(message.getIntValue("id")==2){
//如果是 获取 target的回调
sessionId=message.getJSONObject("result").getString("sessionId");
}
}
@Override
public void onClose(int i, String s, boolean b) {
}
@Override
public void onError(Exception e) {
}
};
webSocketClient.connect();
//我这里先简单停留方法获取消息返回值,实际可以使用 sleep和notify来实现
Thread.sleep(2000);
//发送消息 获取target 我在消息接受里面简单处理了一下
webSocketClient.send("{\"method\":\"Target.getTargets\",\"id\":1}");
Thread.sleep(2000);
//获取sessionId
webSocketClient.send("{\"id\":2,\"method\":\"Target.attachToTarget\",\"params\":{\"flatten\":true,\"targetId\":\""+targetId+"\"}}");
Thread.sleep(2000);
//使用sessionId 操作页面
webSocketClient.send("{\"sessionId\":\""+sessionId+"\",\"method\":\"Page.navigate\",\"params\":{\"url\":\"http://bcxw.net\"},\"id\":3}");
Thread.sleep(10000);
} catch (Exception e) {
e.printStackTrace();
}
}
这已经是一个很完善的案例,只不过实际开发要把代码结构化,任何操作都可以从这个案例出发来找解决方案,前面的介绍文档部分花时间最多,很有价值,后面还会有更多复杂操作的案例!