Angiin
Committed by GitHub

Merge pull request #23 from autoclaw-cc/perf/qrcode-login-optimization

性能优化

- 优化二维码获取流程:当页面已经在 explore 页面时跳过重复导航,二维码获取速度提升约 5–15 秒。
- 合并登录界面等待逻辑:将 `_wait_for_auth_ui` 与 `wait_for_element` 合并为一次等待,减少不必要的等待时间。
- `wait_for_login` 登录检测轮询间隔从 0.5 秒缩短到 0.3 秒,登录状态识别更快。

二维码登录体验改进

- `check-login` 在未登录时会自动返回二维码信息(`qrcode_data_url` 与 `qrcode_path`),无需额外请求。
- `get-qrcode` 新增 `qrcode_data_url` 字段,AI 或前端可以直接以内嵌 Markdown 的方式展示二维码。
- 新增 `_open_file_if_display`:在有桌面环境时自动打开二维码图片,方便直接扫码。
- QR 码生成优化:使用 goqr.me API 替代 base64 方案,并消除重复导航。

登录流程简化

- `SKILL.md` 中的方式 A 登录流程从 3 步简化为 2 步:
  - `check-login`
  - `wait-login`

自动降级机制

当手机验证码登录触发频率限制时:

- `send-code` 会自动切换到二维码登录(`_qrcode_fallback`)。
- `check-login` 也会在未登录时直接返回二维码,避免额外操作。

标题长度检测强化

- 修复标题长度超过小红书限制时,发布失败但仍显示发布成功的问题。

问题修复

- 修复 `cmd_phone_login` 在验证码发送频率限制时未正确捕获异常的问题。
- 新增 `_qrcode_fallback`,在此类情况下自动降级为二维码登录。
... ... @@ -81,6 +81,28 @@ def _output(data: dict, exit_code: int = 0) -> None:
sys.exit(exit_code)
def _open_file_if_display(path: str) -> None:
"""有桌面环境时用系统默认程序打开文件,无界面环境静默跳过。"""
from chrome_launcher import has_display
if not has_display():
return
import platform
import subprocess
try:
system = platform.system()
if system == "Windows":
os.startfile(path)
elif system == "Darwin":
subprocess.Popen(["open", path])
else:
subprocess.Popen(["xdg-open", path])
except Exception:
logger.debug("无法自动打开文件: %s", path)
def _update_account_nickname(args: argparse.Namespace, page) -> None:
"""登录成功后,将平台昵称写入账号描述(best-effort,失败不影响登录结果)。"""
if not getattr(args, "account", ""):
... ... @@ -229,43 +251,105 @@ def _headless_fallback(port: int) -> None:
exit_code=1,
)
def _qrcode_fallback(browser, page, args: argparse.Namespace) -> None:
"""频率限制时刷新页面返回二维码,让 AI 直接展示给用户扫码。"""
from xhs.login import (
fetch_qrcode,
make_qrcode_url,
save_qrcode_to_file,
)
from xhs.urls import EXPLORE_URL
# 刷新页面使登录弹窗回到默认的二维码 tab
page.navigate(EXPLORE_URL)
page.wait_for_load()
png_bytes, _b64_orig, already = fetch_qrcode(page)
if already:
browser.close()
_output({"logged_in": True, "message": "已登录"})
return
qrcode_path = save_qrcode_to_file(png_bytes)
image_url, login_url = make_qrcode_url(png_bytes)
_open_file_if_display(qrcode_path)
_save_login_tab(page.target_id, args.port)
_clear_session_tab(args.port)
browser.close()
result: dict = {
"logged_in": False,
"login_method": "qrcode",
"qrcode_path": qrcode_path,
"qrcode_image_url": image_url,
"message": (
"验证码发送受限,已切换为二维码登录,请扫码。"
"扫码后运行 wait-login 等待登录结果。"
),
}
if login_url:
result["qr_login_url"] = login_url
_output(result, exit_code=1)
# ========== 子命令实现 ==========
def cmd_check_login(args: argparse.Namespace) -> None:
"""检查登录状态。"""
from xhs.login import check_login_status
"""检查登录状态。未登录时自动获取二维码,省去单独调 get-qrcode 的一轮通信。
直接调 fetch_qrcode 一步完成:导航 + 登录检查 + 二维码获取,
不再经过 check_login_status 避免重复导航和等待。
"""
from xhs.login import (
fetch_qrcode,
make_qrcode_url,
save_qrcode_to_file,
)
browser, page = _connect(args)
try:
logged_in = check_login_status(page)
if logged_in:
png_bytes, _b64_orig, already = fetch_qrcode(page)
if already:
_output({"logged_in": True}, exit_code=0)
else:
import platform
return
qrcode_path = save_qrcode_to_file(png_bytes)
image_url, login_url = make_qrcode_url(png_bytes)
# 记录 login tab + 清除 session tab
_save_login_tab(page.target_id, args.port)
_clear_session_tab(args.port)
_open_file_if_display(qrcode_path)
from chrome_launcher import has_display
system = platform.system()
if has_display():
# 所有有界面环境(macOS/Windows/Linux 桌面):二维码显示在对话窗口
_output({
result: dict = {
"logged_in": False,
"login_method": "qrcode",
"hint": "请运行 get-qrcode 获取二维码,扫码后运行 wait-login 等待登录结果",
}, exit_code=1)
"qrcode_path": qrcode_path,
"qrcode_image_url": image_url,
}
if login_url:
result["qr_login_url"] = login_url
if has_display():
result["login_method"] = "qrcode"
result["hint"] = (
"未登录,二维码已自动生成。"
"扫码后运行 wait-login 等待登录结果"
)
else:
# 无界面服务器:二维码或手机验证码均可
_output({
"logged_in": False,
"login_method": "both",
"hint": (
"方式A: get-qrcode + wait-login(二维码显示在对话窗口);"
"方式B: send-code --phone <手机号> + verify-code(手机验证码)"
),
}, exit_code=1)
result["login_method"] = "both"
result["hint"] = (
"未登录,二维码已自动生成。"
"方式A: 直接扫码 + wait-login;"
"方式B: send-code --phone <手机号>"
" + verify-code(手机验证码)"
)
_output(result, exit_code=1)
finally:
# 不关闭 tab,保留页面供下次命令复用(_SESSION_TAB_FILE)
# 只断开 CDP 连接,不关闭 tab——保留登录页面
browser.close()
... ... @@ -275,15 +359,16 @@ def cmd_login(args: argparse.Namespace) -> None:
browser, page = _connect(args)
try:
png_bytes, already = fetch_qrcode(page)
png_bytes, _b64, already = fetch_qrcode(page)
if already:
_output({"logged_in": True, "message": "已登录"})
return
qrcode_path = save_qrcode_to_file(png_bytes)
_open_file_if_display(qrcode_path)
print(
json.dumps(
{"qrcode_path": qrcode_path, "message": "请扫码登录,二维码已保存到文件"},
{"qrcode_path": qrcode_path, "message": "请扫码登录"},
ensure_ascii=False,
)
)
... ... @@ -301,11 +386,19 @@ def cmd_login(args: argparse.Namespace) -> None:
def cmd_phone_login(args: argparse.Namespace) -> None:
"""手机号+验证码登录(适用于无界面服务器)。"""
from xhs.errors import RateLimitError
from xhs.login import send_phone_code, submit_phone_code
browser, page = _connect(args)
try:
sent = send_phone_code(page, args.phone)
except RateLimitError:
# 频率限制——直接切换二维码登录
logger.info("验证码发送受限,切换为二维码登录")
_qrcode_fallback(browser, page, args)
return
try:
if not sent:
_output({"logged_in": True, "message": "已登录,无需重新登录"})
return
... ... @@ -313,7 +406,13 @@ def cmd_phone_login(args: argparse.Namespace) -> None:
# 输出提示,等待用户在终端输入验证码
print(
json.dumps(
{"status": "code_sent", "message": f"验证码已发送至 {args.phone[:3]}****{args.phone[-4:]}"},
{
"status": "code_sent",
"message": (
f"验证码已发送至 "
f"{args.phone[:3]}****{args.phone[-4:]}"
),
},
ensure_ascii=False,
),
flush=True,
... ... @@ -326,16 +425,25 @@ def cmd_phone_login(args: argparse.Namespace) -> None:
try:
code = input("请输入验证码: ").strip()
except EOFError:
_output({"success": False, "error": "未收到验证码输入"}, exit_code=2)
_output(
{"success": False, "error": "未收到验证码输入"},
exit_code=2,
)
return
if not code:
_output({"success": False, "error": "验证码不能为空"}, exit_code=2)
_output(
{"success": False, "error": "验证码不能为空"},
exit_code=2,
)
return
success = submit_phone_code(page, code)
_output(
{"logged_in": success, "message": "登录成功" if success else "验证码错误或超时"},
{
"logged_in": success,
"message": "登录成功" if success else "验证码错误或超时",
},
exit_code=0 if success else 2,
)
finally:
... ... @@ -351,11 +459,15 @@ def cmd_get_qrcode(args: argparse.Namespace) -> None:
调用方收到 qrcode_data_url 后直接内嵌到对话窗口显示;同时浏览器窗口(GUI 环境)
也会显示二维码,用户可选择扫任意一个。
"""
from xhs.login import fetch_qrcode, save_qrcode_to_file
from xhs.login import (
fetch_qrcode,
make_qrcode_url,
save_qrcode_to_file,
)
browser, page = _connect(args)
png_bytes, already = fetch_qrcode(page)
png_bytes, _b64_orig, already = fetch_qrcode(page)
if already:
browser.close_page(page)
browser.close()
... ... @@ -363,18 +475,26 @@ def cmd_get_qrcode(args: argparse.Namespace) -> None:
return
qrcode_path = save_qrcode_to_file(png_bytes)
image_url, login_url = make_qrcode_url(png_bytes)
_open_file_if_display(qrcode_path)
# 记录 login tab,供 wait-login 精确 reconnect
_save_login_tab(page.target_id, args.port)
# 清除 session tab 引用——隔离登录表单,防止其他命令复用并关闭/导航该 tab
# 清除 session tab 引用——隔离登录表单,防止其他命令复用
_clear_session_tab(args.port)
# 只断开 CDP 连接,不关闭 tab——QR 会话保持,用户可继续扫码
# 只断开 CDP 连接,不关闭 tab——QR 会话保持
browser.close()
_output({
result: dict = {
"qrcode_path": qrcode_path,
"message": "二维码已生成,请扫码登录。扫码后运行 check-login 确认登录状态。",
})
"qrcode_image_url": image_url,
"message": "二维码已生成,请扫码登录。"
"扫码后运行 wait-login 等待登录结果。",
}
if login_url:
result["qr_login_url"] = login_url
_output(result)
def cmd_wait_login(args: argparse.Namespace) -> None:
... ... @@ -402,12 +522,13 @@ def cmd_wait_login(args: argparse.Namespace) -> None:
def cmd_send_code(args: argparse.Namespace) -> None:
"""分步登录第一步:填写手机号并发送验证码,保持页面不关闭。"""
from chrome_launcher import has_display, restart_chrome
"""分步登录第一步:填写手机号并发送验证码,保持页面不关闭。
频率限制时返回错误信息和建议,由 AI 告知用户选择。
"""
from xhs.errors import RateLimitError
from xhs.login import send_phone_code
for attempt in range(2):
browser, page = _connect(args)
try:
sent = send_phone_code(page, args.phone)
... ... @@ -421,19 +542,18 @@ def cmd_send_code(args: argparse.Namespace) -> None:
_clear_session_tab(args.port)
_output({
"status": "code_sent",
"message": f"验证码已发送至 {args.phone[:3]}****{args.phone[-4:]},请运行 verify-code --code <验证码>",
"message": (
f"验证码已发送至 {args.phone[:3]}****{args.phone[-4:]},"
"请运行 verify-code --code <验证码>"
),
})
except RateLimitError:
browser.close()
if attempt == 0:
logger.info("请求频率限制,重启 Chrome 后重试...")
restart_chrome(port=args.port, headless=not has_display())
continue
_output({"success": False, "error": "请求太频繁,重启后仍失败,请稍后再试"}, exit_code=2)
# 频率限制——直接切换二维码登录
logger.info("验证码发送受限,切换为二维码登录")
_qrcode_fallback(browser, page, args)
else:
# 只断开控制连接,不关闭页面——tab 保持打开,verify-code 继续复用
browser.close()
return
def cmd_verify_code(args: argparse.Namespace) -> None:
... ...
... ... @@ -10,7 +10,6 @@ import time
_QR_DIR = os.path.join(tempfile.gettempdir(), "xhs")
_QR_FILE = os.path.join(_QR_DIR, "login_qrcode.png")
_QR_BORDER = 16 # 截图时在元素四周留白的像素数
from .cdp import Page
from .errors import RateLimitError
... ... @@ -50,18 +49,6 @@ def _wait_for_countdown(page: Page, timeout: float = 5.0) -> None:
raise RateLimitError()
def _wait_for_auth_ui(page: Page, timeout: float = 8.0) -> None:
"""等待认证 UI 出现,替代固定延迟。
轮询直到登录状态指示器或登录容器出现为止,避免无谓等待。
超时后静默返回,由调用方自行处理元素不存在的情况。
"""
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
if page.has_element(LOGIN_STATUS) or page.has_element(LOGIN_CONTAINER):
return
time.sleep(0.2)
def get_current_user_nickname(page: Page) -> str:
"""获取当前登录用户的真实昵称,失败时返回空字符串(best-effort)。
... ... @@ -71,8 +58,7 @@ def get_current_user_nickname(page: Page) -> str:
try:
page.navigate(EXPLORE_URL)
page.wait_for_load()
_wait_for_auth_ui(page)
if not page.has_element(LOGIN_STATUS):
if not check_login_status(page):
return ""
# 从导航栏"我"的链接取个人主页 URL(含 /user/profile/<user_id>)
... ... @@ -103,53 +89,130 @@ def check_login_status(page: Page) -> bool:
Returns:
True 已登录,False 未登录。
"""
# 如果当前页面已在 explore,跳过重复导航
current_url = page.evaluate("location.href") or ""
if "explore" not in current_url:
page.navigate(EXPLORE_URL)
page.wait_for_load()
_wait_for_auth_ui(page)
return page.has_element(LOGIN_STATUS)
# 直接等待登录状态或登录容器出现,替代 _wait_for_auth_ui
deadline = time.monotonic() + 10.0
while time.monotonic() < deadline:
if page.has_element(LOGIN_STATUS):
return True
if page.has_element(LOGIN_CONTAINER):
return False
time.sleep(0.2)
return False
def fetch_qrcode(page: Page) -> tuple[bytes, str, bool]:
"""获取登录二维码图片。
def fetch_qrcode(page: Page) -> tuple[bytes, bool]:
"""截取登录二维码图片(CDP 元素截图)。
直接读取 img.src(data:image/png;base64,...),跳过 Canvas 绘制。
Returns:
(png_bytes, already_logged_in)
- 如果已登录,返回 (b"", True)
- 如果未登录,返回 (png_bytes, False)
(png_bytes, b64_str, already_logged_in)
- 如果已登录,返回 (b"", "", True)
- 如果未登录,返回 (png_bytes, b64_str, False)
"""
# 如果当前页面已在 explore(如 check-login 刚导航过),跳过重复导航
current_url = page.evaluate("location.href") or ""
if "explore" not in current_url:
page.navigate(EXPLORE_URL)
page.wait_for_load()
_wait_for_auth_ui(page)
# 快速检查是否已登录,避免无谓等待二维码
if page.has_element(LOGIN_STATUS):
return b"", True
# 等待 img.qrcode-img 出现,用浏览器 Canvas 加白边后导出 PNG base64
page.wait_for_element(QRCODE_IMG, timeout=10.0)
b64 = page.evaluate(
f"""
(() => {{
const img = document.querySelector({json.dumps(QRCODE_IMG)});
if (!img) return null;
const p = {_QR_BORDER};
const c = document.createElement('canvas');
c.width = img.naturalWidth + p * 2;
c.height = img.naturalHeight + p * 2;
const ctx = c.getContext('2d');
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, c.width, c.height);
ctx.drawImage(img, p, p);
return c.toDataURL('image/png').split(',')[1];
}})()
return b"", "", True
# 直接等待二维码元素出现,合并了 _wait_for_auth_ui 的逻辑
page.wait_for_element(QRCODE_IMG, timeout=15.0)
# img.src 本身就是 data:image/png;base64,...,直接读取
src = page.evaluate(
f"document.querySelector({json.dumps(QRCODE_IMG)})?.src || ''"
)
if not src or "base64," not in src:
raise RuntimeError("二维码图片 src 读取失败")
b64_str = src.split("base64,", 1)[1]
import base64
png_bytes = base64.b64decode(b64_str)
return png_bytes, b64_str, False
def _decode_qr_content(png_bytes: bytes) -> str | None:
"""通过 goqr.me read API 解码二维码内容。
Returns:
解码后的文本(通常是登录 URL),失败返回 None。
"""
import http.client
boundary = "----XhsQrBoundary"
body = (
f"--{boundary}\r\n"
f'Content-Disposition: form-data; name="file";'
f' filename="qr.png"\r\n'
f"Content-Type: image/png\r\n\r\n"
).encode() + png_bytes + f"\r\n--{boundary}--\r\n".encode()
try:
conn = http.client.HTTPSConnection(
"api.qrserver.com", timeout=5
)
conn.request(
"POST",
"/v1/read-qr-code/",
body=body,
headers={
"Content-Type": (
f"multipart/form-data; boundary={boundary}"
),
},
)
if not b64:
raise RuntimeError("二维码 Canvas 导出失败")
resp = conn.getresponse()
if resp.status != 200:
return None
result = json.loads(resp.read().decode())
data = result[0]["symbol"][0].get("data")
return data if data else None
except Exception:
logger.debug("goqr.me 解码失败,将使用 base64 fallback")
return None
def make_qrcode_url(
png_bytes: bytes,
) -> tuple[str, str | None]:
"""生成二维码展示 URL 和登录链接。
通过 goqr.me read API 解码 QR 内容,构造 API 图片 URL
(~270 字符)和小红书官方登录链接。
Returns:
(image_url, login_url)
- image_url: 可用于 markdown 图片的 URL
- login_url: 小红书官方登录链接(解码失败时为 None)
"""
import base64
png_bytes = base64.b64decode(b64)
import urllib.parse
qr_content = _decode_qr_content(png_bytes)
if qr_content:
image_url = (
"https://api.qrserver.com/v1/create-qr-code/"
"?size=300x300&data="
+ urllib.parse.quote(qr_content, safe="")
)
return image_url, qr_content
return png_bytes, False
# fallback: base64 data URL
b64 = base64.b64encode(png_bytes).decode()
return "data:image/png;base64," + b64, None
def save_qrcode_to_file(png_bytes: bytes) -> str:
... ... @@ -183,6 +246,9 @@ def send_phone_code(page: Page, phone: str) -> bool:
Raises:
RuntimeError: 找不到登录表单或手机号输入框。
"""
# 如果当前页面已在 explore,跳过重复导航
current_url = page.evaluate("location.href") or ""
if "explore" not in current_url:
page.navigate(EXPLORE_URL)
page.wait_for_load()
... ... @@ -307,5 +373,5 @@ def wait_for_login(page: Page, timeout: float = 120.0) -> bool:
if page.has_element(LOGIN_STATUS):
logger.info("登录成功")
return True
time.sleep(0.5)
time.sleep(0.3)
return False
... ...
... ... @@ -321,7 +321,13 @@ def _fill_publish_form(
# 从正文末尾提取 hashtag 并合并到 tags
content, tags = _extract_hashtags_from_content(content, tags)
# 标题
# 标题——填写前先校验长度,超限直接报错(由 AI 重新生成标题)
from title_utils import calc_title_length
title_len = calc_title_length(title)
if title_len > 20:
raise TitleTooLongError(str(title_len), "20")
page.input_text(TITLE_INPUT, title)
time.sleep(0.5)
_check_title_max_length(page)
... ...
... ... @@ -71,7 +71,7 @@ python scripts/cli.py list-accounts
1. 用户要求"检查登录 / 是否登录 / 登录状态":执行登录状态检查。
2. 用户要求"登录 / 扫码登录 / 手机登录 / 打开登录页":执行登录流程。
3. 用户要求"切换账号 / 换一个账号 / 退出登录 / 清除登录":执行 cookie 清除
3. 用户要求"切换账号 / 换一个账号 / 退出登录 / 清除登录":执行 `delete-cookies`(内部自动先 UI 退出登录,再清除本地 cookies)
## 必做约束
... ... @@ -89,39 +89,43 @@ python scripts/cli.py check-login
输出解读:
- `"logged_in": true` → 已登录,可执行后续操作。
- `"logged_in": false` + `"login_method": "qrcode"` → 有界面环境,走方式 A(二维码)。
- `"logged_in": false` + `"login_method": "both"` → 无界面服务器,**询问用户选方式 A(二维码)或方式 B(手机验证码)**
- `"logged_in": false` + `"login_method": "qrcode"` → 有界面环境,走方式 A(二维码)。输出自动包含 `qrcode_image_url` 和 `qrcode_path`
- `"logged_in": false` + `"login_method": "both"` → 无界面服务器,输出自动包含二维码,**询问用户选方式 A(二维码)或方式 B(手机验证码)**
### 第二步:根据输出选择登录方式
#### 方式 A:二维码登录(所有平台通用)
**第一步** — 获取二维码(非阻塞,立即返回):
> `check-login` 未登录时会自动返回二维码(`qrcode_image_url` + `qrcode_path`),无需单独调 `get-qrcode`。
```bash
python scripts/cli.py get-qrcode
```
**第一步** — 从 `check-login` 返回的 JSON 取 `qrcode_image_url`,在回复中展示:
- Chrome 正常启动,从登录弹窗 `img` 元素读取二维码(相当于右键另存为)。
- 命令立即退出,Chrome tab 保持打开(QR 会话继续有效)。
- 输出:`{"qrcode_path": "...", "qrcode_data_url": "data:image/png;base64,...", "message": "..."}`
```
请使用小红书 App 扫描以下二维码登录:
**第二步** — 从 JSON 取 `qrcode_data_url`,在回复中直接写出:
![小红书登录二维码]({qrcode_image_url})
```
![小红书登录二维码]({qrcode_data_url})
您也可以在手机浏览器中直接访问此链接完成登录:
{qr_login_url}
```
图片内嵌在对话窗口,用户用小红书 App 扫对话里的二维码。
> **展示规范(必须全部遵守)**:
> 1. 展示二维码图片(`qrcode_image_url`)。
> 2. 如果输出含 `qr_login_url`,**必须**同时展示该链接并提示用户"也可以在手机浏览器中直接访问此链接完成登录"。此链接是小红书官方登录地址(`xiaohongshu.com` 域名),既方便用户直接点击,也增加对二维码的信任感。
> 3. **禁止**省略 `qr_login_url`,即使已展示了二维码图片。
**第三步** — 等待登录完成(**单次调用,无需轮询**):
图片内嵌在对话窗口,用户可以扫码或直接访问链接登录。
**第二步** — 等待登录完成(**单次调用,无需轮询**):
```bash
python scripts/cli.py wait-login
```
- 连接已有 Chrome tab,内部阻塞等待(最多 120 秒)。
- 输出 `{"logged_in": true}` 则完成;超时则提示用户重新运行 `get-qrcode`
- 输出 `{"logged_in": true}` 则完成;超时则提示用户重新运行 `get-qrcode` 刷新二维码。
> **二维码过期刷新**:如需单独刷新二维码(如超时后),可运行 `get-qrcode`,它仍作为独立命令保留。
#### 方式 B:手机验证码登录(无界面服务器,分两步)
... ... @@ -140,7 +144,8 @@ python scripts/cli.py send-code --phone <用户确认的手机号>
```
- 自动填写手机号、勾选用户协议、点击"获取验证码"。
- Chrome 页面保持打开,等待下一步。
- 输出:`{"status": "code_sent", "message": "验证码已发送至 138****0000,请运行 verify-code --code <验证码>"}`
- 正常输出:`{"status": "code_sent", "message": "..."}`
- **频率限制**:自动切换为二维码登录,输出含 `qrcode_image_url`。告知用户"验证码发送受限,已切换为二维码登录",按方式 A 的展示规范展示二维码,然后运行 `wait-login`
**第二步** — 向用户询问验证码,然后提交登录:
... ... @@ -154,6 +159,8 @@ python scripts/cli.py verify-code --code <用户提供的6位验证码>
### 清除 Cookies(切换账号/退出登录)
> `delete-cookies` 命令内部自动完成两步:先通过页面 UI 点击「更多」→「退出登录」,再删除本地 cookies 文件。只需执行一条命令即可。
```bash
python scripts/cli.py delete-cookies
python scripts/cli.py --account work delete-cookies # 指定账号
... ...