Tài liệuTự động hóa
Tự động hóa bằng script
Một script trên cùng máy tính có thể mở một hồ sơ và điều khiển trình duyệt của nó. Hồ sơ mà script nhận được chính là hồ sơ bạn mở bằng tay — cùng dấu vân tay, cùng kết nối, cùng cookie.
Giao diện ứng dụng hiện chỉ có tiếng Anh và tiếng Nga. Tên nút và cài đặt trên trang này được ghi đúng như giao diện tiếng Anh hiển thị.
API cục bộ là gì
API cục bộ là một dịch vụ nhỏ bên trong ứng dụng. Nó chỉ trả lời trên máy tính này và chỉ trả lời những yêu cầu có kèm khóa của bạn. Nhờ nó, script có thể liệt kê hồ sơ, khởi chạy và dừng chúng, và nhận địa chỉ để thư viện tự động hóa kết nối vào trình duyệt đã khởi chạy.
- Mọi gói, kể cả gói miễn phí.
- Chỉ máy tính này: nó lắng nghe trên
127.0.0.1và không thể mở ra mạng. - Tắt cho đến khi bạn bật.
- Hồ sơ được tạo trong ứng dụng. API chạy những hồ sơ bạn đang có; nó không tạo hay chỉnh sửa hồ sơ.
Bật lên
- Mở Settings › API & Automation và bật Local API. Dòng bên cạnh đổi thành Running on this computer only.
- Giữ nguyên Port —
48361nếu bạn chưa đổi — hoặc nhấn Random free port. - Sao chép API key ngay bằng Copy: bản thân khóa không bao giờ được hiển thị, và khi bạn rời trang thì không thể sao chép lại — chỉ có thể thay khóa mới. Khóa dài 64 ký tự.

- Address
- Nơi dịch vụ trả lời, kèm nút Copy — mặc định là
http://127.0.0.1:48361. - Regenerate…
- Tạo một khóa mới. Khóa cũ ngừng hoạt động ngay lập tức, và các địa chỉ trình duyệt đã cấp cùng với nó cũng vậy.
- API documentation
- Mở tài liệu tham chiếu.
Ghi chú. Khóa được lưu trong kho thông tin xác thực của hệ thống, không nằm trong tệp. Script nên đọc địa chỉ từ tệp mà ứng dụng ghi ra — Local API/status.json trong thư mục dữ liệu của Liminal — thay vì ghi cứng một cổng; tài liệu tham chiếu giải thích tệp đó nằm ở đâu.
Yêu cầu đầu tiên
Mọi yêu cầu đều mang khóa trong một header. Hãy hỏi dịch vụ xem nó đã chạy chưa:
curl -H 'Authorization: Bearer <key>' http://127.0.0.1:48361/v1/status{"status":"running","version":"<version>","signedIn":true}signedIn cho biết có ai đang đăng nhập vào ứng dụng hay không. Các thao tác đụng đến hồ sơ cần điều đó: nếu không, chúng trả về not_signed_in.
Tìm một hồ sơ và khởi chạy nó
Liệt kê các hồ sơ — kết quả trả về theo trang và có thể lọc theo tên, không gian làm việc, thư mục và trạng thái:
curl -H 'Authorization: Bearer <key>' http://127.0.0.1:48361/v1/profilesLấy id của hồ sơ bạn muốn và khởi chạy nó:
curl -X POST -H 'Authorization: Bearer <key>' http://127.0.0.1:48361/v1/profiles/<profile-id>/start{
"id": "<profile-id>",
"status": "running",
"cdpDiscoveryUrl": "http://127.0.0.1:48361/devtools/<token>/json/version",
"cdpBrowserUrl": "ws://127.0.0.1:9222/devtools/browser/<token>",
"seleniumDebuggerUrl": "127.0.0.1:9222",
"chromedriverPath": "/Applications/Liminal.app/.../chromedriver",
"debugPort": 9222,
"debugAddress": "127.0.0.1:9222",
"externalIp": "203.0.113.10",
"countryCode": "DE"
}cdpBrowserUrl- Địa chỉ mà Puppeteer và Playwright kết nối tới.
seleniumDebuggerUrl,chromedriverPath- Những gì Selenium cần: địa chỉ trình gỡ lỗi và driver khớp với trình duyệt, đi kèm ứng dụng.
externalIp,countryCode- Kết nối của hồ sơ trông như thế nào khi nó khởi chạy.
Ứng dụng khởi chạy hồ sơ như thể bạn đã nhấn Open. Hồ sơ đang mở sẽ không được khởi chạy lần nữa — tài liệu tham chiếu mô tả cách gắn vào một hồ sơ đang chạy.
Kết nối thư viện của bạn
Mỗi ví dụ đều hoàn chỉnh: đọc tệp địa chỉ, tìm hồ sơ theo tên, khởi chạy nó và mở một trang.
import { readFileSync } from 'node:fs'
import puppeteer from 'puppeteer-core'
// Local API/status.json, inside Liminal's user-data directory. The settings
// screen shows the absolute path; that path and the key are your script’s to
// keep — the app puts neither into the environment for you.
const { address } = JSON.parse(readFileSync(process.env.LIMINAL_ADDRESS_FILE, 'utf8'))
async function call(path, init) {
const response = await fetch(address + path, {
...init,
headers: { Authorization: `Bearer ${process.env.LIMINAL_API_KEY}` },
})
const body = await response.json()
if (!response.ok) throw new Error(body.error.code)
return body
}
const { items } = await call('/v1/profiles?name=' + encodeURIComponent('Shop EU'))
const started = await call(`/v1/profiles/${items[0].id}/start`, { method: 'POST' })
const browser = await puppeteer.connect({ browserWSEndpoint: started.cdpBrowserUrl })
const page = await browser.newPage()
await page.goto('https://example.com')
console.log(await page.title())
await browser.disconnect()import { readFileSync } from 'node:fs'
import { chromium } from 'playwright'
// Local API/status.json, inside Liminal's user-data directory. The settings
// screen shows the absolute path; that path and the key are your script’s to
// keep — the app puts neither into the environment for you.
const { address } = JSON.parse(readFileSync(process.env.LIMINAL_ADDRESS_FILE, 'utf8'))
async function call(path, init) {
const response = await fetch(address + path, {
...init,
headers: { Authorization: `Bearer ${process.env.LIMINAL_API_KEY}` },
})
const body = await response.json()
if (!response.ok) throw new Error(body.error.code)
return body
}
const { items } = await call('/v1/profiles?name=' + encodeURIComponent('Shop EU'))
const started = await call(`/v1/profiles/${items[0].id}/start`, { method: 'POST' })
const browser = await chromium.connectOverCDP(started.cdpBrowserUrl)
const page = await browser.contexts()[0].newPage()
await page.goto('https://example.com')
console.log(await page.title())
await browser.close()import json
import os
import urllib.parse
import urllib.request
from playwright.sync_api import sync_playwright
# Local API/status.json, inside Liminal's user-data directory. The settings
# screen shows the absolute path; that path and the key are your script’s to
# keep — the app puts neither into the environment for you.
with open(os.environ["LIMINAL_ADDRESS_FILE"], encoding="utf-8") as handle:
address = json.load(handle)["address"]
def call(path, method="GET"):
request = urllib.request.Request(address + path, method=method)
request.add_header("Authorization", "Bearer " + os.environ["LIMINAL_API_KEY"])
with urllib.request.urlopen(request) as response:
return json.load(response)
profile = call("/v1/profiles?name=" + urllib.parse.quote("Shop EU"))["items"][0]
started = call(f"/v1/profiles/{profile['id']}/start", method="POST")
with sync_playwright() as playwright:
browser = playwright.chromium.connect_over_cdp(started["cdpBrowserUrl"])
page = browser.contexts[0].new_page()
page.goto("https://example.com")
print(page.title())import json
import os
import urllib.parse
import urllib.request
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
# Local API/status.json, inside Liminal's user-data directory. The settings
# screen shows the absolute path; that path and the key are your script’s to
# keep — the app puts neither into the environment for you.
with open(os.environ["LIMINAL_ADDRESS_FILE"], encoding="utf-8") as handle:
address = json.load(handle)["address"]
def call(path, method="GET"):
request = urllib.request.Request(address + path, method=method)
request.add_header("Authorization", "Bearer " + os.environ["LIMINAL_API_KEY"])
with urllib.request.urlopen(request) as response:
return json.load(response)
profile = call("/v1/profiles?name=" + urllib.parse.quote("Shop EU"))["items"][0]
started = call(f"/v1/profiles/{profile['id']}/start", method="POST")
options = webdriver.ChromeOptions()
options.debugger_address = started["seleniumDebuggerUrl"]
driver = webdriver.Chrome(service=Service(started["chromedriverPath"]), options=options)
driver.get("https://example.com")
print(driver.title)
driver.quit()import { readFileSync } from 'node:fs'
import { Builder } from 'selenium-webdriver'
import chrome from 'selenium-webdriver/chrome.js'
// Local API/status.json, inside Liminal's user-data directory. The settings
// screen shows the absolute path; that path and the key are your script’s to
// keep — the app puts neither into the environment for you.
const { address } = JSON.parse(readFileSync(process.env.LIMINAL_ADDRESS_FILE, 'utf8'))
async function call(path, init) {
const response = await fetch(address + path, {
...init,
headers: { Authorization: `Bearer ${process.env.LIMINAL_API_KEY}` },
})
const body = await response.json()
if (!response.ok) throw new Error(body.error.code)
return body
}
const { items } = await call('/v1/profiles?name=' + encodeURIComponent('Shop EU'))
const started = await call(`/v1/profiles/${items[0].id}/start`, { method: 'POST' })
const driver = await new Builder()
.forBrowser('chrome')
.setChromeOptions(new chrome.Options().debuggerAddress(started.seleniumDebuggerUrl))
.setChromeService(new chrome.ServiceBuilder(started.chromedriverPath))
.build()
await driver.get('https://example.com')
console.log(await driver.getTitle())
await driver.quit()Mẹo. Hãy kết nối vào trình duyệt mà hồ sơ đã có. Đừng để thư viện tự khởi chạy một trình duyệt riêng — trình duyệt đó sẽ không có dấu vân tay hay cookie của hồ sơ.
Dừng hồ sơ
curl -X POST -H 'Authorization: Bearer <key>' http://127.0.0.1:48361/v1/profiles/<profile-id>/stopDừng sẽ đóng hồ sơ giống như Close. Cũng có lệnh dừng cưỡng bức cho hồ sơ không chịu đóng.
Điều nên biết
| Câu hỏi | Trả lời |
|---|---|
| Script có thấy một trình duyệt khác với trình duyệt tôi thấy không? | Không. Dấu vân tay và kết nối được áp dụng đúng như khi bạn mở hồ sơ bằng tay, và các lớp bảo vệ vẫn giữ nguyên. |
| Trang web có thấy trình duyệt đang được tự động hóa không? | Những dấu hiệu tự động hóa thường gặp được che đi với hồ sơ khởi chạy qua API. Không thể tắt điều này. |
| Có chế độ headless không? | Không. API khởi chạy hồ sơ giống hệt cách ứng dụng làm. |
| Có giới hạn số yêu cầu không? | Không có hạn mức nào gắn với gói. |
| Có công cụ dòng lệnh hay kịch bản ghi sẵn không? | Không. API cục bộ là cách để tự động hóa. |
| Tôi nhận được gì khi có lỗi? | Một mã trạng thái và một code ngắn để rẽ nhánh — invalid_key, not_signed_in, profile_already_running và các mã khác. Chúng được liệt kê theo từng thao tác trong tài liệu tham chiếu. |
Đã đối chiếu với ứng dụng đã phát hành: tháng 10 năm 2026. Nếu ứng dụng hoạt động khác với những gì trang này viết thì trang này sai — hãy viết cho [email protected]. Những thay đổi của từng phiên bản nằm trong mục Có gì mới.