Python SDK
脱离桌面端的纯 Python 自动化 SDK。底层与 Desktop 共用同一套 Chrome 内核、sidecar、gost 代理链路。
安装
pip install https://pub-69fcb37602174d10b2152f09439de470.r2.dev/sdk/linege_sdk-0.3.21-py3-none-any.whl验证:
python -c "import linege_sdk; print(linege_sdk.__version__)"
# 输出: 0.3.21核心流程
登录 → 申请环境(envId) → 分配代理+指纹 → 启动 Chrome → sidecar 连接 → 业务操作 → 自动清理SDK 内部自动处理:环境创建、指纹下发、gost 代理挂载、sidecar 启动、进程清理。用户只需关注业务逻辑。
Quick Start
云端模式 — 全自动
from linege_sdk import LinegeClient
client = LinegeClient(username="你的用户名", password="你的密码")
# 查看配额
profile = client.get_user_profile()
print(f"配额: {profile['used']}/{profile['total']}")
# 查看可用国家
countries = client.list_available_countries()
print(countries) # [{"code": "US", "proxies_available": 42, ...}, ...]
# 打开浏览器 — 内部自动: 申请环境→拿envId→分配代理+指纹→启动Chrome
with client.open_cloud_browser(
start_url="https://www.wikipedia.org",
preferred_countries=["US", "JP"], # 优先选这些国家的代理
) as browser:
browser.find("input#searchInput").wait().input("hello")
browser.find("button[type='submit']").click() # 表单提交用按钮点击
print(browser.find("#firstHeading").text())with 结束自动清理一切(Chrome + gost 代理 + sidecar + 云端环境)。
本地模式 — 指定 envId + 代理
# 创建本地环境,指定代理,自动通过 gost 挂载
with client.open_local_browser(
env_id="shop-account-01",
proxy="socks5://user:pass@proxy-host:1080",
start_url="https://example.com",
) as browser:
print(browser.find("h1").text())说明:
- 本机已经存在 Chrome 内核时,本地模式不需要云端账号
- 如果传入 country 拉取云端指纹,或本机尚未安装内核,则仍然需要账号凭据
本地模式 — 复用已有环境
from linege_sdk import list_local_environments
# 列出本地所有环境(和桌面端看到的一样)
envs = list_local_environments()
for env in envs:
print(f"{env['id']} proxy={env['proxy']} country={env['country']}")
# 用已有环境的 envId 启动
target = envs[0]
with client.open_local_browser(
env_id=target["id"],
proxy=target["proxy"] or None,
start_url="https://example.com",
) as browser:
print(browser.find("h1").text())查找元素
三种方式,返回惰性 Element 对象(调用动作时才查询 DOM):
| 方法 | 用途 | 示例 |
|---|---|---|
find(selector) | CSS 选择器 | browser.find("input#email") |
find_by_xpath(xpath) | XPath 选择器 | browser.find_by_xpath("//div[@id='main']") |
find_by_text(text) | 按可见文字查找 | browser.find_by_text("登录") |
find_by_text — 抗 DOM 结构变化
# 限定标签
browser.find_by_text("提交", tag="button").click()
# 精确匹配
browser.find_by_text("登录", exact=True).click()
# 多语言候选 — 不管页面显示哪种语言,任一匹配即可
browser.find_by_text(["Login", "登录", "ログイン"], tag="button").click()| 参数 | 默认 | 说明 |
|---|---|---|
text | 必填 | 字符串或字符串列表(多语言候选) |
tag | "*" | 限定标签:"button" / "a" / "span" 等 |
exact | False | True = 精确匹配,False = 包含匹配 |
Element 链式操作
| 方法 | 作用 | 返回 |
|---|---|---|
.click() | 拟人点击(贝塞尔曲线) | Element |
.input(text) | 拟人输入(TypeText 真实键盘) | Element |
.press_key(key) | 键盘按键 | Element |
.scroll(delta_y) | 滚动 | Element |
.text() | 读取文本 | Optional[str] |
.wait(state, timeout) | 等待元素 | Element |
.attribute(name) | 读取属性 | Optional[str] |
.is_visible() | 检查可见性 | bool |
browser.find("input#email").wait().input("user@example.com")
browser.find("input#password").click().input("secret123")
browser.find("button[type='submit']").click() # 表单提交用按钮点击
title = browser.find("h1").text()浏览器操作 API
| 方法 | 作用 | 示例 |
|---|---|---|
navigate(url) | 兼容旧行为:发起跳转(默认不等待) | browser.navigate("https://example.com") |
goto(url) | 推荐:跳转并等待页面就绪/URL 命中 | browser.goto("https://polymarket.com", match="host") |
wait_for_url(url) | 等待 URL 命中(exact/prefix/contains/host) | browser.wait_for_url("https://example.com", match="host") |
scroll(delta_y) | 滚动 | browser.scroll(delta_y=500) |
eval_js(expr) | 执行 JS 并返回结果 | browser.eval_js("document.title") |
click(selector) | 直接点击(可设 required) | browser.click("#btn", required=False) |
text(selector) | 直接读文本(可设 required) | browser.text("#title", required=False) |
input(selector, value) | 直接输入 | browser.input("#email", "a@b.com") |
press_key(key) | 键盘按键(普通字符) | browser.press_key("a") |
new_tab(url) | 新建标签页并返回 index | idx = browser.new_tab("https://outlook.live.com") |
list_tabs() | 查看标签页摘要(count/active) | browser.list_tabs() |
switch_tab(index) | 切换到指定标签页 | browser.switch_tab(idx) |
close_tab(index) | 关闭指定标签页 | browser.close_tab(idx) |
导航稳定模式(推荐)
navigate() 为向下兼容保留旧行为(默认只发起跳转,不阻塞等待)。
客户脚本建议改为:
# 站点跳转 + 等待 + URL 校验(host 模式适合反爬重定向场景)
browser.goto(
"https://polymarket.com/event/bitcoin-above-on-march-4",
timeout=45,
wait_until="domcontentloaded",
ensure_url=True,
match="host",
)如需保留旧写法,也可显式开启等待:
browser.navigate(
"https://polymarket.com/event/bitcoin-above-on-march-4",
wait=True,
ensure_url=True,
match="host",
)多标签页 API(同一环境跨站切换)
# 主标签:Polymarket
browser.goto("https://polymarket.com", match="host")
# 新开标签:Outlook
outlook_tab = browser.new_tab("https://outlook.live.com")
browser.switch_tab(outlook_tab)
browser.wait_for_url("https://outlook.live.com", match="host")
# 切回主标签
browser.switch_tab(0)
browser.wait_for_url("https://polymarket.com", match="host")eval_js — 复杂 DOM 交互
当 find_by_text 无法精确匹配(如 React 嵌套文字、弹窗内按钮)时,用 eval_js 直接操作 DOM:
# 在弹窗内精确点击按钮(限定作用域到 [role=dialog])
result = browser.eval_js("""
(function() {
var dialog = document.querySelector('[role=dialog]');
if (!dialog) return null;
var btns = dialog.querySelectorAll('button');
for (var i = 0; i < btns.length; i++) {
if (btns[i].textContent.trim() === 'Continue' && !btns[i].disabled) {
btns[i].click();
return 'OK';
}
}
return 'not found';
})()
""")设计原则
| 原则 | 说明 |
|---|---|
表单提交用 .click() | 点击提交按钮,不用 press_key("Enter") |
| 键盘只输入普通字符 | press_key 仅用于字母/数字/符号 |
优先 .wait() | 代替 time.sleep(),元素就绪后立即继续 |
导航优先 goto() | navigate 仅做兼容入口;业务脚本推荐 goto |
| 跨站切换优先 tab API | 同一环境内用 new_tab/switch_tab 管理标签页 |
eval_js 处理复杂 DOM | 弹窗内按钮、React 嵌套文字等用 JS 精确定位 |
完整示例 — 面向对象多实例业务流
example_full.py 现在是面向用户真实业务场景的 OOP 编排脚本:
- 账号预检(配额 / 国家 / 内核)
- 云端业务流(自动环境 + 代理 + 采集)
- 本地业务流(固定 envId 工作台)
- 多实例并行监控(3 实例独立导航)
- 异常护栏(静默模式与严格模式)
默认仅需账号密码,不依赖邮箱验证码等条件分支。
完整源码(站内下载):example_full.py
全量源码(与下载文件同源,自动渲染):
"""
Phantomshield SDK 完整示例(面向对象 + 多实例真实业务流)
=================================================
定位:
- 面向业务开发者:账号密码登录后,直接跑一个完整业务编排
- 结构是 OOP:一个编排类管理预检、业务流、多实例与异常护栏
- 不包含邮箱验证码等条件分支,默认只要求账号密码
运行:
python examples/example_full.py --username alice --password secret
LINEGE_USERNAME=alice LINEGE_PASSWORD=secret python examples/example_full.py
"""
from __future__ import annotations
import argparse
import json
import os
import sys
import time
import uuid
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
from typing import Any, Callable, Dict, List, Optional
from linege_sdk import LinegeClient, LinegeSDKError, list_local_environments
def now_iso() -> str:
return datetime.now(timezone.utc).isoformat(timespec="seconds").replace("+00:00", "Z")
def jlog(trace_id: str, event: str, status: str = "OK", **payload: Any) -> None:
print(
json.dumps(
{
"ts": now_iso(),
"trace_id": trace_id,
"event": event,
"status": status,
**payload,
},
ensure_ascii=False,
)
)
@dataclass
class MultiInstanceTask:
env_id: str
start_url: str
next_url: str
@dataclass
class MultiInstanceResult:
env_id: str
start_url: str
title_before: str
url_before: str
title_after: str
url_after: str
class FullBusinessWorkflow:
def __init__(self, username: str, password: str) -> None:
self.trace_id = f"full-{uuid.uuid4().hex[:10]}"
self.client = LinegeClient(username=username, password=password)
self.report: Dict[str, Any] = {
"trace_id": self.trace_id,
"status": "running",
"steps": [],
"artifacts": {},
}
def run(self) -> int:
jlog(self.trace_id, "flow.start")
try:
self._run_step("preflight.profile", self._preflight_profile, required=True)
self._run_step("preflight.core", self._preflight_core, required=True)
self._run_step("preflight.countries", self._preflight_countries, required=True)
self._run_step("preflight.local_envs", self._preflight_local_envs, required=False)
self._run_step("biz.cloud_research", self._biz_cloud_research, required=True)
self._run_step("biz.local_workspace", self._biz_local_workspace, required=True)
self._run_step("biz.multi_instance_watch", self._biz_multi_instance_watch, required=True)
self._run_step("biz.error_guardrail", self._biz_error_guardrail, required=True)
self.report["status"] = "ok"
jlog(self.trace_id, "flow.success", step_count=len(self.report["steps"]))
return 0
except LinegeSDKError as exc:
self.report["status"] = "failed"
self.report["error"] = {
"type": "LinegeSDKError",
"code": exc.code,
"message": exc.message,
"hint": exc.hint,
"detail": exc.detail,
}
jlog(self.trace_id, "flow.failed", status="ERROR", code=exc.code, message=exc.message)
return 1
except Exception as exc:
self.report["status"] = "failed"
self.report["error"] = {
"type": type(exc).__name__,
"message": str(exc),
}
jlog(self.trace_id, "flow.failed", status="ERROR", error_type=type(exc).__name__, message=str(exc))
return 1
finally:
try:
self.client.close_all()
except Exception:
pass
jlog(self.trace_id, "flow.finish", status=self.report["status"])
print(json.dumps(self.report, ensure_ascii=False, indent=2))
def _run_step(self, name: str, fn: Callable[[], Any], required: bool) -> Any:
started = time.perf_counter()
try:
value = fn()
elapsed_ms = round((time.perf_counter() - started) * 1000)
step_record = {
"name": name,
"required": required,
"ok": True,
"elapsed_ms": elapsed_ms,
"value": value,
}
self.report["steps"].append(step_record)
jlog(self.trace_id, "step.ok", step=name, elapsed_ms=elapsed_ms)
return value
except Exception as exc:
elapsed_ms = round((time.perf_counter() - started) * 1000)
step_record = {
"name": name,
"required": required,
"ok": False,
"elapsed_ms": elapsed_ms,
"error": str(exc),
}
self.report["steps"].append(step_record)
jlog(self.trace_id, "step.fail", status="ERROR", step=name, elapsed_ms=elapsed_ms, error=str(exc))
if required:
raise
return None
def _preflight_profile(self) -> Dict[str, Any]:
profile = self.client.get_user_profile()
return {
"username": profile["username"],
"quota": {
"used": profile["used"],
"total": profile["total"],
"remaining": profile["remaining"],
},
}
def _preflight_core(self) -> Dict[str, Any]:
core = self.client.ensure_core_latest()
return {
"updated": core["updated"],
"local_version": core["local_version"],
"remote_version": core["remote_version"],
}
def _preflight_countries(self) -> Dict[str, Any]:
countries = self.client.list_available_countries(preferred=["US", "JP"])
top = [
{
"code": c["code"],
"name": c["name"],
"proxies_available": c["proxies_available"],
"preferred": c["preferred"],
}
for c in countries[:8]
]
return {
"count": len(countries),
"top": top,
}
def _preflight_local_envs(self) -> Dict[str, Any]:
envs = list_local_environments()
return {
"count": len(envs),
"sample": [
{
"id": row.get("id"),
"proxy": bool(row.get("proxy")),
"country": row.get("country") or "-",
}
for row in envs[:8]
],
}
def _biz_cloud_research(self) -> Dict[str, Any]:
with self.client.open_cloud_browser(
start_url="https://www.wikipedia.org",
preferred_countries=["US", "JP"],
) as browser:
query = "Browser automation"
browser.find("input#searchInput").wait(timeout=30).input(query)
browser.find("button[type='submit'], input.searchButton, button.pure-button").click()
heading = browser.find("#firstHeading").wait(timeout=30).text() or ""
summary = (
browser.find_by_xpath(
"//div[contains(@class,'mw-parser-output')]//p[string-length(normalize-space()) > 30][1]"
).text()
or ""
)
refs_visible = browser.find_by_text(["References", "参考文献", "脚注"], tag="span").is_visible()
browser.find("body").scroll(delta_y=500)
result = {
"env_id": browser.env.env_id,
"mode": browser.env.mode,
"country": getattr(browser.env, "country", None),
"title": browser.eval_js("document.title"),
"url": browser.eval_js("window.location.href"),
"heading": heading,
"summary_preview": summary[:160],
"references_visible": refs_visible,
}
self.report["artifacts"]["cloud_env_id"] = browser.env.env_id
return result
def _biz_local_workspace(self) -> Dict[str, Any]:
with self.client.open_local_browser(
env_id="biz-workspace-main",
start_url="https://example.com",
) as browser:
homepage_h1 = browser.find("h1").wait(timeout=20).text() or ""
homepage_link = browser.find("a").wait(timeout=10).attribute("href")
homepage_visible = browser.find("h1").is_visible()
browser.goto("https://www.wikipedia.org", match="host")
input_box = browser.find("input#searchInput").wait(timeout=30)
input_box.click()
input_box.press_key("L")
browser.input("input#searchInput", "inege SDK", human=True)
browser.find("button[type='submit'], input.searchButton, button.pure-button").click()
browser.find("#firstHeading").wait(timeout=30)
# 多标签业务流:同一环境切到 Outlook 再切回主业务站点
outlook_tab = browser.new_tab("https://outlook.live.com", activate=True)
browser.switch_tab(outlook_tab)
outlook_hosts = [
"https://outlook.live.com",
"https://login.live.com",
"https://www.microsoft.com",
]
outlook_ready = browser.wait_for_url(
outlook_hosts,
match="host",
timeout=30,
required=False,
)
tabs_after_open = browser.list_tabs()
browser.switch_tab(0)
wiki_ready = browser.wait_for_url("https://wikipedia.org", match="host", timeout=30, required=False)
browser.find("body").scroll(delta_y=600)
result_title = browser.find("#firstHeading").text() or ""
page_title = browser.eval_js("document.title")
page_url = browser.eval_js("window.location.href")
return {
"env_id": browser.env.env_id,
"mode": browser.env.mode,
"homepage_h1": homepage_h1,
"homepage_link": homepage_link,
"homepage_visible": homepage_visible,
"result_title": result_title,
"page_title": page_title,
"page_url": page_url,
"outlook_tab_index": outlook_tab,
"outlook_ready": outlook_ready,
"wiki_ready_after_switch": wiki_ready,
"tabs_after_open": tabs_after_open,
}
def _biz_multi_instance_watch(self) -> List[Dict[str, Any]]:
tasks = [
MultiInstanceTask(env_id="watch-us", start_url="https://example.com", next_url="https://www.wikipedia.org"),
MultiInstanceTask(env_id="watch-jp", start_url="https://www.wikipedia.org", next_url="https://httpbin.org/html"),
MultiInstanceTask(env_id="watch-eu", start_url="https://httpbin.org/html", next_url="https://example.com"),
]
sessions: List[tuple[MultiInstanceTask, Any]] = []
results: List[MultiInstanceResult] = []
try:
for task in tasks:
session = self.client.open_local_browser(env_id=task.env_id, start_url=task.start_url)
sessions.append((task, session))
for task, session in sessions:
session.wait("h1, h2, body", timeout=30, required=False)
title_before = str(session.eval_js("document.title", required=False) or "")
url_before = str(session.eval_js("window.location.href", required=False) or "")
session.goto(task.next_url, timeout=30, ensure_url=True, match="host", required=False)
session.wait("h1, h2, body", timeout=30, required=False)
title_after = str(session.eval_js("document.title", required=False) or "")
url_after = str(session.eval_js("window.location.href", required=False) or "")
results.append(
MultiInstanceResult(
env_id=task.env_id,
start_url=task.start_url,
title_before=title_before,
url_before=url_before,
title_after=title_after,
url_after=url_after,
)
)
finally:
for _, session in sessions:
try:
session.close()
except Exception:
pass
return [asdict(row) for row in results]
def _biz_error_guardrail(self) -> Dict[str, Any]:
bad_auth: Dict[str, Any] = {}
try:
LinegeClient(username="wrong_user", password="bad_password").get_user_profile()
except LinegeSDKError as exc:
bad_auth = {
"code": exc.code,
"message": exc.message,
"hint": exc.hint,
}
with self.client.open_local_browser(
env_id="guardrail-silent",
start_url="https://example.com",
) as browser:
browser.find("h1").wait(timeout=10)
silent_click = browser.click("#nonexistent-element", required=False, timeout=3)
silent_text = browser.text("#nonexistent-element", required=False, timeout=3)
strict_error: Dict[str, Any] = {}
try:
with self.client.open_local_browser(
env_id="guardrail-strict",
start_url="https://example.com",
) as browser:
browser.find("h1").wait(timeout=10)
browser.click("#nonexistent-element", required=True, timeout=3)
except LinegeSDKError as exc:
strict_error = {
"code": exc.code,
"message": exc.message,
"hint": exc.hint,
}
return {
"bad_auth": bad_auth,
"silent_mode": {
"click_result": silent_click,
"text_result": silent_text,
},
"strict_mode": strict_error,
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Phantomshield SDK 面向对象多实例完整业务示例")
parser.add_argument("--username", default=os.environ.get("LINEGE_USERNAME", ""))
parser.add_argument("--password", default=os.environ.get("LINEGE_PASSWORD", ""))
return parser.parse_args()
def main() -> int:
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
if hasattr(sys.stderr, "reconfigure"):
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
args = parse_args()
if not args.username or not args.password:
print("缺少凭据:请设置 --username/--password 或 LINEGE_USERNAME/LINEGE_PASSWORD")
return 2
workflow = FullBusinessWorkflow(username=args.username, password=args.password)
return workflow.run()
if __name__ == "__main__":
raise SystemExit(main())外部机器验证流程(裸机)
# 1) 创建隔离环境
python -m venv .venv
# Windows
.venv\Scripts\python -m pip install --upgrade pip
.venv\Scripts\python -m pip install "https://pub-69fcb37602174d10b2152f09439de470.r2.dev/sdk/linege_sdk-0.3.21-py3-none-any.whl"
# 2) 下载完整示例脚本(站内)
powershell -Command "Invoke-WebRequest -Uri https://docs.sybilslayer.com/examples/example_full.py -OutFile example_full.py"
# 3) 准备凭据并运行 full example(账号密码模式)
set LINEGE_USERNAME=你的账号
set LINEGE_PASSWORD=你的密码
.venv\Scripts\python example_full.py预期结果:
- 输出 JSON 结构化日志(step.ok / step.fail)
- 正常结束时出现 flow.success
- 退出码为 0
LinegeClient 参数
client = LinegeClient(
username="...", # 或 token="..."
password="...",
auto_cleanup_cloud_env=True, # with 结束自动删除云端环境
enable_logging=True, # 结构化 JSON 日志
sidecar_base_port=7788, # 端口被占自动递增
max_startup_retries=1, # 启动失败后重试次数
desktop_compatible=False, # True 时使用桌面端兼容预设
chrome_path=None, # 自定义 Chrome 路径
render_mode="software", # "software" 或 "gpu"
http_cache_mode="isolated", # "isolated" / "global" / "off"
shared_http_cache_root=None, # 共享 HTTP 缓存目录
shared_http_cache_size_bytes=None, # 共享缓存大小(字节)
restore_last_session=None, # 恢复上次会话(默认跟随 desktop_compatible)
)| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
username | str | None | 用户名(与 token 二选一) |
password | str | None | 密码 |
token | str | None | 令牌(与 username/password 二选一) |
api_url | str | "https://api.sybilslayer.com" | API 地址 |
sidecar_base_port | int | 7788 | sidecar 起始端口,被占自动递增 |
timeout | float | 20.0 | API 请求超时(秒) |
auto_cleanup_cloud_env | bool | True | with 结束自动删除云端环境 |
enable_logging | bool | True | 结构化 JSON 日志 |
max_startup_retries | int | 1 | 浏览器启动失败后重试次数 |
desktop_compatible | bool | False | 启用桌面端兼容预设(render_mode/cache/session 等) |
chrome_path | str | None | 自定义 Chrome 可执行文件路径 |
render_mode | str | "software" | 渲染模式:"software" 或 "gpu" |
http_cache_mode | str | "isolated" | HTTP 缓存模式:"isolated" / "global" / "off" |
shared_http_cache_root | str | None | 共享 HTTP 缓存根目录 |
shared_http_cache_size_bytes | int | None | 共享缓存大小上限(字节) |
restore_last_session | bool | None | 恢复上次会话;None 时跟随 desktop_compatible |
实例方法
| 方法 | 说明 |
|---|---|
get_user_profile() | 返回 {username, used, total, remaining} |
ensure_core_latest() | 确保 Chrome 内核最新(首次自动下载) |
list_available_countries() | 查询云端可用国家列表 |
open_cloud_browser(...) | 云端模式(自动申请环境 + 代理 + 指纹) |
open_local_browser(...) | 本地模式(指定/自动生成 envId,可选代理) |
open_cloud_browser 参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
start_url | str | "about:blank" | 启动后打开的 URL |
preferred_countries | List[str] | None | 优先选择的国家代码列表 |
sidecar_port | int | None | 指定 sidecar 端口(None 自动分配) |
env_name_prefix | str | "sdk-biz" | 云端环境名称前缀 |
attempts_per_country | int | 2 | 每个国家的尝试次数 |
startup_retries | int | None | 启动重试次数(None 使用构造函数的 max_startup_retries) |
desktop_compatible | bool | None | 覆盖构造函数的 desktop_compatible |
chrome_path | str | None | 覆盖构造函数的 chrome_path |
render_mode | str | None | 覆盖构造函数的 render_mode |
http_cache_mode | str | None | 覆盖构造函数的 http_cache_mode |
shared_http_cache_root | str | None | 覆盖构造函数的 shared_http_cache_root |
shared_http_cache_size_bytes | int | None | 覆盖构造函数的缓存大小 |
restore_last_session | bool | None | 覆盖构造函数的 restore_last_session |
open_local_browser 参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
start_url | str | "about:blank" | 启动后打开的 URL |
env_id | str | None | 环境 ID(None 自动生成 local-{timestamp}-{hex}) |
proxy | str | None | 代理 URL(如 socks5://user:pass@host:port) |
sidecar_port | int | None | 指定 sidecar 端口(None 自动分配) |
env_name | str | "" | 环境显示名称 |
country | str | "" | 国家代码(非空时自动从云端拉取对应指纹) |
startup_retries | int | None | 启动重试次数(None 使用构造函数的 max_startup_retries) |
desktop_compatible | bool | None | 覆盖构造函数的 desktop_compatible |
chrome_path | str | None | 覆盖构造函数的 chrome_path |
render_mode | str | None | 覆盖构造函数的 render_mode |
http_cache_mode | str | None | 覆盖构造函数的 http_cache_mode |
shared_http_cache_root | str | None | 覆盖构造函数的 shared_http_cache_root |
shared_http_cache_size_bytes | int | None | 覆盖构造函数的缓存大小 |
restore_last_session | bool | None | 覆盖构造函数的 restore_last_session |
独立函数
| 函数 | 说明 |
|---|---|
list_local_environments() | 列出本地所有环境(同桌面端) |
create_local_environment(env_id, ...) | 手动创建本地环境目录 |
create_local_environment 参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
env_id | str | 必填 | 环境唯一标识 |
profile | Dict | None | 指纹 profile 配置字典(None 自动生成最小默认) |
proxy | str | None | 代理 URL |
name | str | "" | 环境显示名称 |
country | str | "" | 国家代码 |
base_data_dir | str | None | 自定义 Linege 数据根目录 |
get_user_profile 返回值
{
"username": "your_username",
"used": 5,
"total": 50,
"remaining": 45,
}list_available_countries 返回值
返回按可用性排序的国家列表,每项包含:
| 字段 | 类型 | 说明 |
|---|---|---|
code | str | 国家代码(如 "US") |
name | str | 国家名称 |
proxies_available | int | 可用代理数量 |
fingerprints | int | 可用指纹数量 |
preferred | bool | 是否在 preferred 列表中 |
GUI 兼容模式
当你需要复现桌面客户端 GUI 已有的登录态/余额/页面状态时,建议:
- 复用同一个 env_id
- 打开
desktop_compatible=True - 显式传入和 GUI 一致的
chrome_path - 需要恢复旧 session 时使用
start_url="about:blank"+restore_last_session=True - 如 GUI 开了共享缓存,可同时传
shared_http_cache_root/shared_http_cache_size_bytes
Troubleshooting
| 问题 | 解决 |
|---|---|
| 首次无 Chrome 内核 | client.ensure_core_latest() 自动下载 |
| 端口冲突 | SDK 自动递增;或 sidecar_base_port=8899 |
| eval_js 超时 | 确认 Chrome 已启动并连接到 sidecar |
navigate 后看起来没跳转 | 改用 browser.goto(..., match="host"),或 navigate(wait=True, ensure_url=True) |
| 需要同一环境跨站(Polymarket ↔ Outlook) | 使用 new_tab/list_tabs/switch_tab/close_tab,不要依赖手工点标签 |
Outlook 打开后不在 outlook.live.com | 可能会跳到 login.live.com 或 microsoft.com,wait_for_url 传候选 host 列表并 required=False |
find_by_text 匹配不到 React 按钮 | React 组件文字可能在嵌套 <span> 中,改用 eval_js + textContent.trim() |
| 弹窗内按钮点击错位 | eval_js 中限定作用域到 document.querySelector('[role=dialog]') |
| SPA 页面加载后元素未就绪 | 用 .wait(timeout=30) 或适当 time.sleep() 等待首屏渲染 |