自定义脚本
内置脚本覆盖了常见流程。当你需要的功能它们没有提供时——换一个执行顺序、操作它们没触及的界面,或者自动化 TikTok / Instagram 以外的应用——你可以用任意语言自己写,由 TikMatrix 把手机交给你。
使用要求
自定义脚本需要 Pro、Team 或 Business 套餐。 Starter 套餐不提供此功能。
套餐的设备数同时也是并发上限:Pro 套餐(20 台设备)最多同时驱动 20 台手机,无论走内 置任务、自定义脚本,还是两者混用。
两种运行方式
独立运行
你自己运行程序,TikMatrix 只负责把设备借给你。
from tikmatrix import TikMatrix
client = TikMatrix()
for device in client.devices():
if device["busy"]:
continue
with client.device(device["serial"], label="my crawler") as d:
d.press("home")
print(d.info())
适合一次性任务、数据采集,以及任何你想用自己的调度器来跑的场景。
托管运行
把程序注册到 TikMatrix,它就变成一个普通任务,可以使用任务队列、按套餐限制并发、自动重试、任务日志和定时计划模板。TikMatrix 会在启动你的程序之前先占用设备,并把租约 ID 通过环境变量传进去。
from tikmatrix import TikMatrix
with TikMatrix.from_env() as d: # 设备已经占用好了
d.click(text="Log in")
print("done") # 这一行会出现在任务日志里
适合需要重复执行、定时执行或跨大量设备执行的场景。
该选哪一种
| 独立运行 | 托管运行 | |
|---|---|---|
| 谁来启动 | 你自己 | TikMatrix 任务队列 |
| 设备租约 | 你自己申请 | 程序启动时已经持有 |
| 重试、定时、任务日志 | 你自己实现 | 内置提供 |
| 跨多台设备运行 | 你自己写循环 | 每台设备一个任务,并行下发 |
| 适合场景 | 探索调试、采集、一次性任务 | 任何需要反复执行的工作 |
你可以先用独立模式把流程调通,然后把同一个文件注册成托管脚本——需要改的只有 TikMatrix.from_env() 这一行。
快速开始
1. 安装客户端库
pip install requests
然后把 SDK 目录里的 tikmatrix.py 复制到你的脚本旁边。这个库只有单个文件,没有其它依赖。
你也可以不用它——这套 API 就是普通的 HTTP + JSON,下面有原始接口文档。
2. 编写脚本
from tikmatrix import TikMatrix
client = TikMatrix()
with client.device("192.168.1.5:5555") as d:
d.press("home")
d.adb("shell", "am", "start", "-a", "android.settings.SETTINGS")
d.wait_for(text="Settings", timeout=15)
d.screenshot("settings.png")
在 TikMatrix 已打开、手机已连接的情况下运行。如果它打印出了设备信息字典,说明整条链路已经通了。
3. 注册脚本(仅托管模式)
进入 设备 → 自定义脚本 → 添加脚本:
| 字段 | 含义 |
|---|---|
| 名称 | 显示在脚本列表和任务日志里 |
| 命令 | 要执行的程序命 令行,例如 python C:/scripts/my_flow.py |
| 工作目录 | 可选。程序的启动目录 |
| 平台 | 见下方平台模式 |
| 超时时间 | 超过多少秒后脚本被杀掉、任务标记为失败。默认 1800 |
| 额外环境变量 | 可选的 JSON 对象,会合并进程序的环境变量 |
| 启用 | 不删除脚本也能停用。停用的脚本无法被下发 |
然后点击脚本行上的 ▶,选择设备即可,和内置脚本完全一样。
AI 助手可以根据一句话描述起草一个自定义脚本并一步注册好。在任何文件写入磁盘之前,它都会先把完整代码给你看。
设备租约
一台手机同一时刻只能被一个程序驱动。占用它相当于告诉 TikMatrix 这台设备正忙,于是:
- 任务队列不会再往同一块屏幕上派发任务;
- 你的 JSON-RPC 调用会像内置脚本一样上报 agent 健康状态,看门狗看到的是一个忙碌的 agent,而不是一个失联的 agent。
租约同样会占用套餐中的一个设备名额。
租约会过期——默认 120 秒,最长 600 秒。Python 库会在后台线程自动续期,并在 with 代码块结束时释放,所以脚本崩溃后几秒内设备就会被释放,而不是一直占着直到你重启应用。如果你直接调用 API,就需要自己发送心跳。
在 设置 → 开发者 API → 设备占用会话 中可以查看所有存活的租约,也可以手动释放。
平台模式
注册脚本时需要声明它面向什么:
通用(Generic) —— 设备原样交给你。不启动任何应用、不切换账号、不检查输入法,结束后也不关闭任何东西。用它来自动化 TikTok / Instagram 之外的一切。
TikTok / Instagram —— 在你的程序启动前先打开应用并切换好账号,结束后关闭应用,和内置脚本的处理完全一致。TIKMATRIX_PACKAGE 会告诉你实际解析出的包名。用它来补上内置脚本没有覆盖的步骤。
环境变量
托管脚本会收到:
| 变量 | 含义 |
|---|---|
TIKMATRIX_API_BASE | 服务地址,例如 http://127.0.0.1:50809 |
TIKMATRIX_SESSION_ID | 已经替你持有的租约 |
TIKMATRIX_SERIAL | 本次任务下发到的设备 |
TIKMATRIX_PACKAGE | 解析出的应用包名 |
TIKMATRIX_PLATFORM | tiktok、instagram 或 generic |
TikMatrix.from_env() 会替你读取这些变量。
独立脚本拿不到这些变量——请自己显式申请设备租约。
你在额外环境变量里填的内容会覆盖合并到上面这些之上,这也是同一个已注册脚本在不同运行之间传递参数、又不用改代码的常用做法。
Python 库参考
TikMatrix —— 连接
| 调用 | 作用 |
|---|---|
TikMatrix(base_url=None, timeout=30.0) | 建立连接。依次回退到 TIKMATRIX_API_BASE、http://127.0.0.1:50809 |
client.devices() | 在线设备,每项包含 serial、real_serial 和 busy |
client.sessions() | 所有存活的租约,包括不属于本进程的 |
client.device(serial, label=..., ttl_secs=120) | 占用一台设备并返回 Device |
TikMatrix.from_env() | 接管托管脚本启动时已占用的设备 |
Device —— 手机
| 调用 | 作用 |
|---|---|
d.info() | UIAutomator2 设备信息 |
d.window_size() | (宽, 高) |
d.screenshot(path=None) | PNG 字节,可选写入 path |
d.hierarchy() | 当前界面树(XML) |
d.find(text=, resource_id=, description=, class_name=) | 匹配到的节点,每项带 bounds 和 center |
d.exists(**criteria) | 是否存在匹配项 |
d.wait_for(timeout=10.0, interval=1.0, **criteria) | 阻塞直到出现,然后返回该节点 |
d.click(timeout=10.0, **criteria) | 等待元素出现后点击其中心 |
d.click_xy(x, y) | 点击坐标 |
d.swipe(sx, sy, ex, ey, steps=20) | 滑动 |
d.press(key) | back、home、recent、enter …… |
d.input_text(text) | 通过内置快速输入法向焦点输入框输入文本 |
d.jsonrpc(method, params=None, timeout=10) | 任意 UIAutomator2 方法 |
d.adb(*args, timeout_ms=None) | 执行 ADB 命令 |
d.release() | 释放租约。用 with 时会自动调用 |
find 是在导出的界面树上匹配的,所以选择器没命中时,可以 print(d.hierarchy()) 看看它到底搜索了什么。设备 视图里的元素检查器会以可视化方式展示同一棵树,通常是找 resource-id 最快的办法。
input_text 需要 ADB它是向内置输入法发送广播,走的是 adb shell。使用前请先开启 ADB 访问,否则会返回 403。
错误
这个库会抛出两种异常,都继承自 RuntimeError:
| 异常 | 出现时机 |
|---|---|
DeviceBusyError | HTTP 409 —— 设备已被占用,或套餐没有空闲设备名额 |
TikMatrixError | 其它所有情况:套餐等级不足、租约过期、ADB 未开启、选择器始终没命中 |
from tikmatrix import TikMatrix, TikMatrixError, DeviceBusyError
client = TikMatrix()
try:
with client.device("192.168.1.5:5555") as d:
d.click(text="Log in", timeout=20)
except DeviceBusyError:
print("这台手机被别人占着,换一台吧")
except TikMatrixError as exc:
print("失败:", exc)
在托管脚本里,直接让异常抛出去通常才是对的:非零退出码会把任务标记为失败,堆栈也会写进任务日志。
HTTP 接口
设备类操作需要在请求头里带上 x-session-id,指向一个存活的租约。这里没有 API key:和本地 API 的其余部分一样,这些接口是不做鉴权的——能访问到这台机器本身就是访问控制。它们不返回任何 CORS 头,所以请从程序里调用(curl、Python,或任何服务端代码),而不是从浏览器页面里调用。
| 方法 | 路径 | 用途 |
|---|---|---|
GET | /api/v1/rpc/devices | 列出在线设备以及是否正忙 |
POST | /api/v1/rpc/session | 占用设备 → session_id |
POST | /api/v1/rpc/session/{id}/heartbeat | 续期 |
DELETE | /api/v1/rpc/session/{id} | 释放 |
GET | /api/v1/rpc/session | 列出存活的租约 |
POST | /api/v1/rpc/jsonrpc | 调用 UIAutomator2 方法 |
POST | /api/v1/rpc/adb | 执行 ADB 命令 |
GET | /api/v1/rpc/hierarchy?serial= | 当前界面树(XML) |
GET | /api/v1/rpc/screenshot?serial= | 当前屏幕(PNG) |
JSON 响应使用与本地 API 其余部分一致的信封结构 —— {"code": 0, "message": "success", "data": ...},失败时 code 非零。hierarchy 和 screenshot 直接返回原始内容。
示例
# 占用设备
curl -X POST http://127.0.0.1:50809/api/v1/rpc/session \
-H "Content-Type: application/json" \
-d '{"serial":"192.168.1.5:5555","label":"curl test","ttl_secs":120}'
# {"code":0,"message":"success","data":{"session_id":"ff3ae079-...","serial":"192.168.1.5:5555", ...}}
# 驱动设备
curl -X POST http://127.0.0.1:50809/api/v1/rpc/jsonrpc \
-H "x-session-id: ff3ae079-..." \
-H "Content-Type: application/json" \
-d '{"serial":"192.168.1.5:5555","method":"deviceInfo","params":[]}'
# 工作期间保持租约
curl -X POST http://127.0.0.1:50809/api/v1/rpc/session/ff3ae079-.../heartbeat \
-H "Content-Type: application/json" \
-d '{"ttl_secs":120}'
# 归还设备
curl -X DELETE http://127.0.0.1:50809/api/v1/rpc/session/ff3ae079-...
错误码
| 状态码 | 含义 |
|---|---|
| 403 | 套餐低于 Pro、没有租约、租约已过期,或 ADB 访问未开启 |
| 409 | 设备已被占用,或套餐没有空闲设备名额 |
用其它语言编写
这里没有任何东西是 Python 专属的。任何能发 HTTP 请求的运行时都可以——托管模式的约定只有一条:「读三个环境变量,成功时以 0 退出」。
// my_flow.js —— 注册命令:node C:/scripts/my_flow.js
const base = process.env.TIKMATRIX_API_BASE || "http://127.0.0.1:50809";
const serial = process.env.TIKMATRIX_SERIAL;
const session = process.env.TIKMATRIX_SESSION_ID;
async function jsonrpc(method, params = []) {
const res = await fetch(`${base}/api/v1/rpc/jsonrpc`, {
method: "POST",
headers: { "content-type": "application/json", "x-session-id": session },
body: JSON.stringify({ serial, method, params }),
});
const body = await res.json();
if (!res.ok || body.code !== 0) throw new Error(body.message || res.statusText);
return body.data;
}
console.log(await jsonrpc("deviceInfo"));
如果解释器不在 PATH 中,就在命令里写完整路径,例如 C:/Program Files/nodejs/node.exe C:/scripts/my_flow.js。
通过 API 触发自定义脚本
已注册的脚本也可以通过任务管理 API 启动,因此一个脚本可以给自己排后续工作:
curl -X POST http://127.0.0.1:50809/api/v1/task \
-H "Content-Type: application/json" \
-d '{
"serials": ["192.168.1.5:5555"],
"script_name": "custom_script",
"script_config": {
"custom_script_id": 1,
"custom_script_platform": "generic"
}
}'
custom_script_id 就是你注册的脚本 ID。
ADB 访问
/api/v1/rpc/adb 会给脚本一个设备 shell —— 推送素材、安装 APK、修改系统设置都需要它。正因为它等同于完整的 shell 权限,而这个接口本身没有 API key,所以默认是关闭的。当你确实有脚本需要它时,再到 设置 → 开发者 API → 允许执行 ADB 命令 中打开;只做界面自动化的脚本走 /rpc/jsonrpc 即可,无需开启。
关闭状态下 /api/v1/rpc/adb 会返回 403,其余接口照常工作。脚本执行的每一条 ADB 命令都会写进日志文件。