Open API
很多同学希望能够通过程序/脚本控制花漾客户端,如:通过程序/脚本访问某个浏览器分身以打开/关闭花漾指纹浏览器等,从而方便与其它系统的整合。 基于此,花漾提供了 Open API 的特性,其实现原理是基于 Access Key 认证体系的 Http 请求/响应机制,理论上允许用户使用任意语言如 Java、Python、C# 等与花漾客户端进行交互。
1、开启 Open API 并获得 Access Key
在使用 Open API 之前,首先需要开启API,请切换至“团队”主页签,并在“团队资源”处,开启 Open API:
《在团队资源中开启Open API》
当 Open API 开启后,可以点击“新增”按钮,创建一个新的 Access Key,请注意,每一个Access Key都需要指定一个用户身份, 换言之,当您使用某个Access Key并通过 Open API 来操纵花漾客户端时,本质上相当于使用与此Access Key绑定的用户身份进行的操作:
《创建 Access Key》
需要提醒您的是,生成的 Access Key 请务必妥善保存,以避免造成信息安全风险。
2、如何调用花漾 Open API
3.1 调用端点(Endpoint)与时间格式
花漾Open API的调用端点(Endpoint)是:https://api.szdamai.com/openapi/; 输入/输出的时间格式是:"yyyy-MM-dd'T'HH:mm:ss'Z'",采用UTC+0时区。举例,当输出"2022-09-05T11:12:28Z"时, 代表东8区(北京时间)的2022-09-05 19:12:28。
3.2 认证方式
目前仅支持OAuth Token的方式进行校验,即客户使用 AccessKeyId + AccessKeySecret,跟 Endpoint 换取一个 Token, 在后续请求中,把 Token 放入http头中:'Authorization: YOUR_Token',后续所有请求将以 Access Key 代表的用户身份进行操作。
以下举例说明:
curl -X 'GET' 'https://api.szdamai.com/openapi/token?accessKeyId=YOUR_AccessKeyId&accessKeySecret=YOUR_AccessKeySecret&expireSeconds=7200' -H 'accept: */*
其中 expireSeconds 是 Token 超时时间,单位:秒,取值范围为[120,7200];当Token超时以后,请求会失败, 建议您在Token超时之前重新获取Token。
上述http请求的返回结果如下所示:
{
"success": true, "code": 0, "message": null,
"data": {
"token": "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0b2MiLCJqdGkiOiI2MTY1ODY4MjEyMWI0NzQ1OTNmZDBjYzVjYzAzOTk1NyIsInN1YiI6IkFLLUFLUVpQY3Z4YVRsMU85bkwwUUhjSHp0IiwiZXhwIjoxNjYyMzc2MzQ4fQ.TjG0VLwNrJrn8-XyxHz-OfhNjlLkalan3EBr92cdKc8GGaKwFNEqUpBInKiUJ6ixybIpKnYNpsjxktliw2nIbjYgTnCRbT1G7jHogx2Y_7eDigJjyrpyZwdlZ5ZEGHPn01fNWFS0RkgHqeraQoNwbgVDU3T2rbJfZtG3W5vGuhRrmYaC-_pXjkvWhXgc4ZkIDV7j-364hyyZXM9W326iIoW9aaj1BhAWuttdXt-sl8aEDsbESEZxCziBE6_Q_MdJ4BvBi8yGp9M2yhjlkh2pNjF_nkl68QwVpsR8HW--2jTwjZLdHhsOl45Fp55ELV_y8czLUuo0-X5Sp6443UBXGA",
"expireTime": "2022-09-05T11:12:28Z"
}
}
其中,那一长串基于Base64编码的字符串,就是认证Token,而 expireTime 则是此Token的超时时间。
当认证成功获得 Token 后,我们就可以将 Token 放入到 Http头中,以调用花漾 Open API 了。
我们以查看当前用户信息这个 API 接口为例, 请求内容如下:
curl -X 'GET' 'https://api.szdamai.com/openapi/user/current' -H 'accept: */*'
-H 'Authorization: eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0b2MiLCJqdGkiOiI2MTY1ODY4MjEyMWI0NzQ1OTNmZDBjYzVjYzAzOTk1NyIsInN1YiI6IkFLLUFLUVpQY3Z4YVRsMU85bkwwUUhjSHp0IiwiZXhwIjoxNjYyMzc2MzQ4fQ.TjG0VLwNrJrn8-XyxHz-OfhNjlLkalan3EBr92cdKc8GGaKwFNEqUpBInKiUJ6ixybIpKnYNpsjxktliw2nIbjYgTnCRbT1G7jHogx2Y_7eDigJjyrpyZwdlZ5ZEGHPn01fNWFS0RkgHqeraQoNwbgVDU3T2rbJfZtG3W5vGuhRrmYaC-_pXjkvWhXgc4ZkIDV7j-364hyyZXM9W326iIoW9aaj1BhAWuttdXt-sl8aEDsbESEZxCziBE6_Q_MdJ4BvBi8yGp9M2yhjlkh2pNjF_nkl68QwVpsR8HW--2jTwjZLdHhsOl45Fp55ELV_y8czLUuo0-X5Sp6443UBXGA'
返回结果如下所示:
{
"success": true,
"code": 0,
"message": null,
"data": {
"id": 52394196996096,
"tenant": null,
"status": "ACTIVE",
"nickname": "子非鱼",
"createTime": "2021-08-27T07:02:51Z",
"avatar": null,
"gender": "UNSPECIFIC",
"signature": "",
"residentCity": "",
"userType": "NORMAL",
"partnerId": null,
"phone": "186****2179",
"email": "c****u@1*6.com",
"weixin": null
}
}
我们可以看到,通过 “https://api.szdamai.com/openapi/user/current” 接口能够成功查询当前用户的基本信息。
综上,我们已经完整的了解了如何通过Access Key获得认证Token,以及将Token放入到http头中以调用 Open API, 再通过参考花漾目前公开的API列表,理论上您可以通过任何语言/脚本来和花漾客户端进行交互了。
3、花漾 Open API 参考指引
目前花漾 Open API 仅提供了一些有限的接口列表,主要包括查询 RPA 任务详情、打开/关闭某个账号对应的浏览器等等。 目前花漾已经开放的API列表请参考:https://app.szdamai.com/open-api/ 。 需要提醒您的是,上述API参考既是一个在线文档,同时也是花漾 Open API 的调试工具,关于其更详细的介绍,请参考 API参考及Swagger在线调试工具。
4、示例:通过 Java 调用花漾 Open API
我们为您准备了一个简单的基于Java语言的示例程序,如下所示:
package com.donkey.openapi.example;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class BasicHttpClientDemo {
static String accessKeyId = "REPLACE_WITH_YOUR_AKID";
static String accessKeySecret = "REPLACE_WITH_YOUR_AKSECRET";
static String endpoint = "https://api.szdamai.com/openapi";
public static void main(String[] args) throws IOException {
//获取认证token
Map<String, Object> resp = request("GET",
String.format("/token?accessKeyId=%s&accessKeySecret=%s&expireSeconds=%d", accessKeyId, accessKeySecret, 7200), null);
String token = (String) resp.get("token");
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", token);
//请求当前用户信息
Map<String, Object> userObj = request("GET", "/user/current", headers);
System.out.println(userObj);
}
public static Map<String, Object> request(String method, String path, Map<String, String> headers) throws IOException {
URL url = new URL(endpoint + path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(method);
if (headers != null) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
conn.setRequestProperty(entry.getKey(), entry.getValue());
}
}
try {
conn.connect();
if (200 <= conn.getResponseCode() && conn.getResponseCode() < 300) {
String responseJson = copy2String(conn.getInputStream());
Map<String, Object> resp = JsonHelper.jsonDecode(responseJson);
if (Boolean.TRUE.equals(resp.get("success"))) {
return (Map<String, Object>) resp.get("data");
} else {
String msg = (String) resp.get("message");
if (msg == null) {
msg = conn.getResponseMessage();
}
throw new IOException("access openapi error :" + msg);
}
} else {
throw new IOException("access openapi error :" + conn.getResponseCode() + " - " + conn.getResponseMessage());
}
} finally {
conn.disconnect();
}
}
public static String copy2String(InputStream inStream) throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = inStream.read(buffer)) != -1) {
result.write(buffer, 0, len);
}
String str = result.toString(StandardCharsets.UTF_8.name());
return str;
}
}
如有需要,您可以下载 Demo程序的源码 。
5、示例:通过 Python 调用花漾 Open API
同时,我们也为您准备了一个简单的基于Python语言的示例程序,如下所示:
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import re
import sys
import requests
import json
import os
apiEndpoint = "https://api.szdamai.com/openapi"
'''
openApi: function to access api url and post parameters
'''
def openApi(url: str, params: dict = None, headers: dict = None, method: str = "GET", contentType: str = "json", timeout: int = 10) -> dict:
if method == "GET":
response = requests.get(apiEndpoint + url, params=params, headers=headers, timeout=timeout)
elif method == "POST":
if not headers:
headers = dict()
if contentType == "json":
headers["Content-Type"] = "application/json"
params = json.dumps(params) if params else None
response = requests.post(apiEndpoint + url, data=params, headers=headers, timeout=timeout)
elif method == "DELETE":
response = requests.delete(apiEndpoint + url, data=params, headers=headers, timeout=timeout)
else:
return None
if not response:
return None
if not 200 <= response.status_code <= 299:
return None
result=json.loads(response.text)
if result["success"]:
return result["data"]
else:
print("openApi %s error %s"%(url,result["message"]))
return None
def getToken(accessKeyId, accessKeySecret, expireSeconds=300):
tokenJson = openApi(
"/token",
params={
"accessKeyId": accessKeyId,
"accessKeySecret": accessKeySecret,
"expireSeconds": expireSeconds
},
)
return tokenJson["token"]
def getCurrentUser(token):
hostJson = openApi(
"/user/current",
params={},
headers={"Authorization": token}
)
return hostJson
def main(args, **kwargs):
accessKeyId =args[0]
accessKeySecret =args[1]
if not accessKeyId or not accessKeySecret:
print("AccessKeyId and AccessKeySecret are not present.\n")
return -1
token = getToken(accessKeyId, accessKeySecret)
if not token:
print("Invalid AccessKeyId and AccessKeySecret.\n")
return -1
user = getCurrentUser(token=token)
if user:
print("Current user is %s.\n" % (user))
else:
print("get Current user fail\n")
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: openapi_demo.py AccessKeyId AccessKeySecret\n")
sys.exit(-1)
sys.exit(main(sys.argv[1:3], **dict(arg.split('=') for arg in sys.argv[4:])))
如有需要,您可以点这里下载 Demo程序的源码 。
6、示例:通过 NodeJS 调用花漾 Open API
我们为您准备了一个简单的基于 NodeJS 的示例程序,如下所示:
/**
* 要求NodeJs 版本至少为16.20.2
*
* 使用方法:
* 1、在“团队”=》“团队资源”=>“Open API”页面中查看AccessKey,填入下面的open_api_key和open_api_secret两个变量;
* 2、选择目标分身点开“编辑分身”按钮,复制“分身ID”,填入下面的to_open_account_id变量;
* 3、执行npm install axios 安装相关依赖;
* 4、执行node node-client-demo.js 查看示例效果。
*/
const axios = require('axios');
// 在“团队”=》“团队资源”=>“Open API”页面中查看AccessKey
const open_api_key = '你的AKID';
const open_api_secret = '你的AK密钥';
// 要打开的分身ID,在编辑分身的对话框中查看
const to_open_account_id = 123456;
let access_token = '';
const open_api_endpoint = 'https://api.szdamai.com/api/openapi';
/**
* 获取 accessToken,之后的所有请求都需要带上这个token。在指定的有效期内这个token可以缓存
* @return {Promise<*>}
*/
async function getToken() {
const url = `${open_api_endpoint}/token?accessKeyId=${open_api_key}&accessKeySecret=${open_api_secret}&expireSeconds=300`;
const response = (await axios.request({
method: 'GET',
url: url,
})).data;
const data = response.data;
return data.token;
}
async function waitTask(taskId) {
for(let i = 0; i < 20; i++) {
await new Promise((resolve => {
setTimeout(()=>resolve(1), 1000);
}));
const url = `${open_api_endpoint}/task/${taskId}`;
const response = (await axios.request({
method: 'GET',
url: url,
headers: {
Authorization: access_token
}
})).data;
let data = response.data[0];
if(data.status != 'Pending') {
return data.resourceId;
}
}
throw '打开会话超时';
}
/**
* 通过 分身名称 打开会话
* @return {Promise<void>}
*/
async function openSession() {
const url = `${open_api_endpoint}/session/open`
const response = (await axios.request({
method: 'POST',
url: url,
headers: {
Authorization: access_token
},
params: {
accountId: to_open_account_id,
// deviceId: to_open_device_id
}
})).data;
let sessionId = await waitTask(response.requestId);
return sessionId;
}
async function closeSession(sessionId) {
const url = `${open_api_endpoint}/session/${sessionId}/close`
const response = (await axios.request({
method: 'PUT',
url: url,
headers: {
Authorization: access_token
}
})).data;
return response;
}
(async function main() {
try {
access_token = await getToken();
console.log(access_token);
const sessionId = await openSession();
console.log('打开会话成功,sessionId=' + sessionId);
await new Promise((resolve => {
setTimeout(()=>resolve(1), 10*1000);
}));
console.log('开始关闭会话,sessionId=' + sessionId);
await closeSession(sessionId);
console.log('任务完成');
} catch (e) {
console.error(e);
}
})();
7、示例:通过 Selenium 控制花漾浏览器 (Python)
在 示例二 Python 程序 的基础上,您可以通过调用花漾 Open API 打开浏览器分身,并使用 Selenium 进行控制。 这里有几点需要注意:
- 可以通过 api 指定控制端口,比如下面的程序中指定的控制端口是9222;
- 可以通过 api 的 browserSwitches 参数指定启动浏览器的参数;
- accountId 指的是分身ID,获取方式见 这里 ;
- 需要在本地准备好对应版本的chromedriver,您可以这里下载 chromedriver ,或者直接使用我们提供的 chromedriver 。
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import sys
import requests
import json
apiEndpoint = "https://api.szdamai.com/openapi"
'''
openApi: function to access api url and post parameters
'''
def openApi(url: str, params: dict = None, headers: dict = None, method: str = "GET", contentType: str = "json", timeout: int = 10) -> dict:
if method == "GET":
response = requests.get(
apiEndpoint + url, params=params, headers=headers, timeout=timeout)
elif method == "POST":
if not headers:
headers = dict()
if contentType == "json":
headers["Content-Type"] = "application/json"
params = json.dumps(params) if params else None
response = requests.post(
apiEndpoint + url, data=params, headers=headers, timeout=timeout)
elif method == "DELETE":
response = requests.delete(
apiEndpoint + url, data=params, headers=headers, timeout=timeout)
else:
return None
if not response:
return None
if not 200 <= response.status_code <= 299:
return None
result = json.loads(response.text)
if result["success"]:
return result["data"]
else:
print("openApi %s error %s" % (url, result["message"]))
return None
def getToken(accessKeyId, accessKeySecret, expireSeconds=300):
tokenJson = openApi(
"/token",
params={
"accessKeyId": accessKeyId,
"accessKeySecret": accessKeySecret,
"expireSeconds": expireSeconds
},
)
return tokenJson["token"]
def getCurrentUser(token):
hostJson = openApi(
"/user/current",
params={},
headers={"Authorization": token}
)
return hostJson
def openSession(token, accountId):
browserSwitches = "--window-size=700,600\n--window-position=100,50"
hostJson = openApi(
"/session/openV2",
params={
"accountId": accountId,
"remoteDebugPort": 9222,
"browserSwitches": browserSwitches,
},
method="POST",
headers={"Authorization": token}
)
return hostJson
def useSelenium():
chrome_options = Options()
chrome_options.add_experimental_option("debuggerAddress", "127.0.0.1:9222")
# chromedriver.exe可以在这里下载,必须是对应花漾的内核版本:
# https://storage.googleapis.com/chrome-for-testing-public/120.0.6099.109/win64/chromedriver-win64.zip
driver = webdriver.Chrome('d:/chromedriver.exe', options=chrome_options)
# 打开网站
driver.get('https://www.baidu.com') # 指定要打开的网站 URL
sleep(1)
print(driver.title)
def main(args, **kwargs):
accessKeyId = args[0]
accessKeySecret = args[1]
accountId = args[2]
if not accessKeyId or not accessKeySecret:
print("AccessKeyId and AccessKeySecret are not present.\n")
return -1
token = getToken(accessKeyId, accessKeySecret)
if not token:
print("Invalid AccessKeyId and AccessKeySecret.\n")
return -1
user = getCurrentUser(token=token)
if user:
print("Current user is %s.\n" % (user))
rs = openSession(token, accountId)
print(rs)
sleep(10)
useSelenium()
else:
print("get Current user fail\n")
if __name__ == "__main__":
if len(sys.argv) != 4:
print("Usage: python SeleniumTest.py AccessKeyId AccessKeySecret accountId\n")
sys.exit(-1)
sys.exit(main(sys.argv[1:4], **dict(arg.split('=')
for arg in sys.argv[5:])))
如有需要,您可以点这里下载 Demo程序的源码 。
8、示例:通过 Selenium 控制花漾浏览器 (Java)
您可以在 Java 中通过调用花漾 Open API 打开浏览器分身,并使用 Selenium 进行控制。 这里有几点需要注意:
- 可以通过 api 指定控制端口,比如下面的程序中指定的控制端口是9222;
- 可以通过 api 的 browserSwitches 参数指定启动浏览器的参数;
- accountId 指的是分身ID,获取方式见 这里 ;
- 需要在本地准备好对应版本的chromedriver,您可以这里下载 chromedriver ,或者直接使用我们提供的 chromedriver 。
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 假设花漾客户端安装在当前机器,这个程序也跑在这个机器。
* <p>
* 下面的Java程序演示在当前电脑上创建打开浏览器,并连接浏览器的remote debug port。
*
*/
public class SeleniumDemo {
static String accessKeyId = "你的AKID";
static String accessKeySecret = "你的AK密钥";
static String endpoint = "https://api.szdamai.com/openapi";
/**
* 需要打开的分身ID,必填
*/
private static final long accountId = 1234567890L;
/**
* 需要打开浏览器的设备ID(可选)
*/
private static String deviceId = null;
/**
* 需要浏览器监听的端口
*/
private static Integer remoteDebugPort = 9222;
/**
* 浏览器启动参数。通过换行符\n分割每个参数对。可选
*/
private static String browserSwitches = null;
/**
* chromedriver.exe可以在这里下载,必须是对应花漾的内核版本:
* https://storage.googleapis.com/chrome-for-testing-public/120.0.6099.109/win64/chromedriver-win64.zip
*/
private static String chromedriverFile = "D:\\chromedriver.exe";
public static void main(String[] args) throws IOException {
System.setProperty("webdriver.chrome.driver", chromedriverFile);
//获取认证token
String token = refrshToken();
//打开会话,获取到异步请求的requestId
String requestId = openSession(token);
//等待会话创建完成
Long sessionId = waitForSessionOpen(token, requestId);
System.out.println("会话创建完成sessionId=" + sessionId);
while (true) {//探测远程调试端口是否监听
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
if (isRemoteReachable("127.0.0.1", remoteDebugPort, 1000)) {
break;
}
}
//花漾浏览器起来之后会有一个稍长的初始化过程,需要等待一会儿
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
}
ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.setExperimentalOption("debuggerAddress", "127.0.0.1:" + remoteDebugPort);
WebDriver webDriver = new ChromeDriver(chromeOptions);
webDriver.get("https://www.baidu.com");
// Get and print the page title
String pageTitle = webDriver.getTitle();
System.out.println("Page Title: " + pageTitle);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
webDriver.quit();
}
private static Long waitForSessionOpen(String token, String requestId) throws IOException {
Long sessionId = null;
while (true) {//等待会话创建完成
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
Map<String, Object> t = findTaskByRequestId(token, requestId);
if (t != null && Boolean.TRUE.equals(t.get("done"))) {
if (!"Success".equalsIgnoreCase((String) t.get("status"))) {
throw new IOException((String) t.get("remarks"));
}
sessionId = (Long) t.get("resourceId");
System.out.println("当前打开的会话ID=" + sessionId);
break;
}
}
return sessionId;
}
public static String refrshToken() throws IOException {
Map<String, Object> resp = (Map<String, Object>) request("GET", String.format("/token?accessKeyId=%s&accessKeySecret=%s&expireSeconds=%d", accessKeyId, accessKeySecret, 7200), null, null);
String token = (String) resp.get("token");
return token;
}
public static String openSession(String token) throws IOException {
Map<String, String> headers = prepareHeaders(token);
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("accountId", accountId);
if (deviceId != null) {
requestBody.put("deviceId", deviceId);
}
if (remoteDebugPort != null) {
requestBody.put("remoteDebugPort", remoteDebugPort);
}
if (browserSwitches != null) {
requestBody.put("browserSwitches", browserSwitches);
}
Map<String, Object> resp = (Map<String, Object>) request("POST", "/session/openV2", requestBody, headers);
String requestId = (String) resp.get("requestId");
return requestId;
}
private static Map<String, String> prepareHeaders(String token) {
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", token);
headers.put("Content-Type", "application/json");
return headers;
}
public static Map<String, Object> findTaskByRequestId(String token, String requestId) throws IOException {
Map<String, String> headers = prepareHeaders(token);
List resp = (List) request("GET", "/task/" + requestId, null, headers);
if (resp.size() > 0) {
return (Map<String, Object>) resp.get(0);
}
return Collections.emptyMap();
}
public static Object request(String method, String path, Object requestBody, Map<String, String> headers) throws IOException {
URL url = new URL(endpoint + path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(method);
if (headers != null) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
conn.setRequestProperty(entry.getKey(), entry.getValue());
}
}
if (requestBody != null) {
conn.setDoOutput(true);
}
try {
conn.connect();
if (requestBody != null) {
String json = JsonHelper.GSON.toJson(requestBody);
writeToStream(json, conn.getOutputStream());
}
if (200 <= conn.getResponseCode() && conn.getResponseCode() < 300) {
String responseJson = copy2String(conn.getInputStream());
Map<String, Object> resp = JsonHelper.jsonDecode(responseJson);
if (Boolean.TRUE.equals(resp.get("success"))) {
return resp.get("data");
} else {
String msg = (String) resp.get("message");
if (msg == null) {
msg = conn.getResponseMessage();
}
throw new IOException("access openapi error :" + msg);
}
} else {
throw new IOException("access openapi error :" + conn.getResponseCode() + " - " + conn.getResponseMessage());
}
} finally {
conn.disconnect();
}
}
public static String copy2String(InputStream inStream) throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = inStream.read(buffer)) != -1) {
result.write(buffer, 0, len);
}
String str = result.toString(StandardCharsets.UTF_8.name());
return str;
}
public static void writeToStream(String str, OutputStream outputStream) {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
try {
writer.write(str);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
writer.close();
} catch (IOException e) {
}
}
}
/**
* 探测远程端口是否监听
*
* @param host
* @param port
* @param timeout
* @return
*/
public static boolean isRemoteReachable(String host, int port, int timeout) {
Socket s = null;
try {
s = new Socket();
s.connect(new InetSocketAddress(host, port), timeout);
return true;
} catch (Exception e) {
} finally {
try {
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
}
如有需要,您可以点这里下载 Demo程序的源码 。
9、示例:通过 Selenium 控制花漾浏览器 (NodeJS)
您可以在 NodeJS 中通过调用花漾 Open API 打开浏览器分身,并使用 Selenium 进行控制。 这里有几点需要注意:
- 可以通过 api 指定控制端口,比如下面的程序中指定的控制端口是9222;
- 可以通过 api 的 browserSwitches 参数指定启动浏览器的参数;
- accountId 指的是分身ID,获取方式见 这里 ;
- 需要在本地准备好对应版本的chromedriver,您可以这里下载 chromedriver ,或者直接使用我们提供的 chromedriver 。
/**
* 要求NodeJs 版本至少为16.20.2
*
* 使用方法:
* 1、在“团队”=》“团队资源”=>“Open API”页面中查看AccessKey,填入下面的open_api_key和open_api_secret两个变量;
* 2、选择目标分身点开“编辑分身”按钮,复制“分身ID”,填入下面的to_open_account_id变量;
* 3、下载https://storage.googleapis.com/chrome-for-testing-public/120.0.6099.109/win64/chromedriver-win64.zip解压出chromdriver.exe,并修改path_to_webdriver变量的值为chromdriver.exe的路径;
* 4、执行npm install 安装相关依赖;
* 5、执行node use-selenium.js 查看示例效果。
*/
const axios = require('axios');
const net = require('net');
const { Builder } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
// 在“团队”=》“团队资源”=>“Open API”页面中查看AccessKey
const open_api_key = '你的AKID';
const open_api_secret = '你的AK密钥';
// 要打开的分身ID,在编辑分身的对话框中查看
const to_open_account_id = 123456;
// chromedriver.exe可以在这里下载,必须是对应花漾的内核版本:
// https://storage.googleapis.com/chrome-for-testing-public/120.0.6099.109/win64/chromedriver-win64.zip
const path_to_webdriver = "d:\\chromedriver.exe";
let access_token = '';
const open_api_endpoint = 'https://api.szdamai.com/api/openapi';
/**
* 获取 accessToken,之后的所有请求都需要带上这个token。在指定的有效期内这个token可以缓存
* @return {Promise<*>}
*/
async function getToken() {
const url = `${open_api_endpoint}/token?accessKeyId=${open_api_key}&accessKeySecret=${open_api_secret}&expireSeconds=300`;
const response = (await axios.request({
method: 'GET',
url: url,
})).data;
const data = response.data;
return data.token;
}
async function waitTask(taskId) {
for(let i = 0; i < 20; i++) {
await new Promise((resolve => {
setTimeout(()=>resolve(1), 1000);
}));
const url = `${open_api_endpoint}/task/${taskId}`;
const response = (await axios.request({
method: 'GET',
url: url,
headers: {
Authorization: access_token
}
})).data;
let data = response.data[0];
if(data.status != 'Pending') {
return data.resourceId;
}
}
throw '打开会话超时';
}
async function waitPortOpen(remoteDebugPort) {
for(let i = 0; i < 20; i++) {
await new Promise((resolve => {
setTimeout(()=>resolve(1), 1000);
}));
let isOpen = await new Promise(resolve => {
const client = new net.Socket();
client.connect(remoteDebugPort, '127.0.0.1', function() {
client.destroy();
resolve(true);
});
client.on('error', function() {
resolve(false);
});
});
if(isOpen) {
return;
}
}
throw '等待端口打开超时';
}
/**
* 通过 分身名称 打开会话
* @return {Promise<void>}
*/
async function openSession(remoteDebugPort) {
const url = `${open_api_endpoint}/session/open`
const response = (await axios.request({
method: 'POST',
url: url,
headers: {
Authorization: access_token
},
params: {
accountId: to_open_account_id,
remoteDebugPort,
// deviceId: to_open_device_id
}
})).data;
let sessionId = await waitTask(response.requestId);
return sessionId;
}
async function closeSession(sessionId) {
const url = `${open_api_endpoint}/session/${sessionId}/close`
const response = (await axios.request({
method: 'PUT',
url: url,
headers: {
Authorization: access_token
}
})).data;
return response;
}
async function connectHuaYoung(remoteDebugPort) {
//等待浏览器ready
await new Promise((resolve => {
setTimeout(()=>resolve(1), 5000);
}));
const options = new chrome.Options();
let service = new chrome.ServiceBuilder(path_to_webdriver);
options.debuggerAddress(`127.0.0.1:${remoteDebugPort}`);
let driver = await new Builder()
.forBrowser('chrome')
.setChromeOptions(options)
.setChromeService(service)
.build();
//拿到 driver 之后就可以对花漾指纹浏览器进行任何操作了
await driver.get('https://www.qq.com');
await driver.getTitle().then((title) => {
console.log('网页标题为:', title);
})
//some code ...
await driver.quit();
}
(async function main() {
try {
access_token = await getToken();
console.log(access_token);
let remoteDebugPort = 9222; //随便什么端口都可以,后续 selenium 会通过该端口连接花漾指纹浏览器,要确保该端口未被占用
const sessionId = await openSession(remoteDebugPort);
console.log('打开会话成功,sessionId=' + sessionId);
await waitPortOpen(remoteDebugPort);
await connectHuaYoung(remoteDebugPort);
console.log('开始关闭会话,sessionId=' + sessionId);
await closeSession(sessionId);
console.log('任务完成');
} catch (e) {
console.error(e);
}
})();
如有需要,您可以点这里下载 Demo程序的源码 。
10、其它
如果您有新的 API 方面的诉求,或者还希望我们提供其它语言的的Demo,请通过在 在线客服 联络我们。