The shell passed USERNAME as an environment variable to the embedded python heredoc (USERNAME=$USERNAME python3 ...), but the python block referenced USERNAME as if it were already a local variable. The result was NameError on every run, so the export script could never produce a cookies/<platform>_<account>.json file.
Fix: read the env var explicitly with os.environ.get('USERNAME', '') at the top of the embedded block, then use the local USERNAME as before.
233 lines
6.9 KiB
Bash
233 lines
6.9 KiB
Bash
#!/bin/bash
|
|
# export_douyin_cookie.sh
|
|
# 导出已登录 Chrome 的抖音 cookie 到 douyin_{8位随机字符}.json
|
|
# 使用 curl 直接调用 Chrome DevTools HTTP API
|
|
|
|
set -e
|
|
|
|
# 解析参数
|
|
USERNAME=""
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--account)
|
|
USERNAME="$2"
|
|
shift 2
|
|
;;
|
|
*)
|
|
echo "用法: $0 --account <用户名>"
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ -z "$USERNAME" ]; then
|
|
echo "未指定用户名,将使用随机文件名"
|
|
fi
|
|
|
|
DEBUG_PORT=9222
|
|
|
|
echo "=========================================="
|
|
echo " 抖音 Cookie 导出工具"
|
|
echo "=========================================="
|
|
echo ""
|
|
|
|
# 依赖: curl, python3, websocket-client
|
|
|
|
echo "检查 Chrome remote debugging..."
|
|
RESPONSE=$(curl -s "http://localhost:${DEBUG_PORT}/json" 2>/dev/null) || true
|
|
|
|
if ! echo "$RESPONSE" | grep -q "webSocketDebuggerUrl"; then
|
|
echo "Chrome remote debugging 未运行"
|
|
echo "请先启动 Chrome: chromium --remote-debugging-port=9222 ..."
|
|
exit 1
|
|
fi
|
|
|
|
echo "Chrome remote debugging 已运行"
|
|
echo ""
|
|
echo "获取抖音 Cookie..."
|
|
|
|
USERNAME="$USERNAME" python3 << 'PYEOF'
|
|
import json
|
|
import os
|
|
import uuid
|
|
import sys
|
|
|
|
# Shell 把 USERNAME 作为环境变量传过来, Python 需要显式取
|
|
USERNAME = os.environ.get('USERNAME', '')
|
|
|
|
DEBUG_PORT = 9222
|
|
if USERNAME:
|
|
COOKIE_FILE = f"cookies/douyin_{USERNAME}.json"
|
|
else:
|
|
COOKIE_FILE = f"cookies/douyin_{uuid.uuid4().hex[:8]}.json"
|
|
|
|
def get_douyin_cookies():
|
|
import urllib.request
|
|
import websocket
|
|
|
|
# 获取页面列表
|
|
url = f"http://localhost:{DEBUG_PORT}/json"
|
|
with urllib.request.urlopen(url, timeout=10) as response:
|
|
pages = json.loads(response.read().decode())
|
|
|
|
# 查找抖音创作者页面
|
|
douyin_page = None
|
|
for page in pages:
|
|
page_url = page.get("url", "")
|
|
if "creator.douyin.com" in page_url:
|
|
douyin_page = page
|
|
break
|
|
|
|
if not douyin_page:
|
|
print("未找到抖音创作者平台页面")
|
|
print("请先在 Chrome 中打开并登录 https://creator.douyin.com")
|
|
return False
|
|
|
|
page_url = douyin_page["url"]
|
|
ws_url = douyin_page.get("webSocketDebuggerUrl", "")
|
|
|
|
print(f"找到页面: {page_url}")
|
|
|
|
# 连接到目标页面的 WebSocket
|
|
print(f"连接页面 WebSocket...")
|
|
ws = websocket.create_connection(ws_url, timeout=30)
|
|
|
|
# 获取所有 Cookie
|
|
ws.send(json.dumps({"id": 1, "method": "Network.getAllCookies"}))
|
|
response = json.loads(ws.recv())
|
|
|
|
cookies = response.get("result", {}).get("cookies", [])
|
|
|
|
# 过滤抖音相关的 cookie
|
|
douyin_cookies = [
|
|
c for c in cookies
|
|
if "douyin.com" in c.get("domain", "") or ".douyin.com" in c.get("domain", "")
|
|
]
|
|
|
|
print(f"获取到 {len(douyin_cookies)} 个抖音 Cookie")
|
|
|
|
if len(douyin_cookies) == 0:
|
|
print("未获取到任何抖音 Cookie,可能未登录")
|
|
return False
|
|
|
|
# 获取 localStorage (origins)
|
|
print(f"获取 localStorage...")
|
|
local_storage_by_origin = {}
|
|
|
|
# 获取页面 frame 树
|
|
ws.send(json.dumps({"id": 2, "method": "Page.getResourceTree"}))
|
|
resp = json.loads(ws.recv())
|
|
frames = resp.get("result", {}).get("frameTree", {}).get("childFrames", [])
|
|
all_frames = [resp.get("result", {}).get("frameTree", {})] + frames
|
|
|
|
for frame in all_frames:
|
|
frame_url = frame.get("url", "")
|
|
frame_id = frame.get("id", "")
|
|
if "douyin.com" in frame_url or "bytedance.com" in frame_url:
|
|
# 执行 JavaScript 获取该 frame 的 localStorage
|
|
script = """
|
|
(function() {
|
|
var result = [];
|
|
for (var i = 0; i < localStorage.length; i++) {
|
|
var key = localStorage.key(i);
|
|
result.push([key, localStorage.getItem(key)]);
|
|
}
|
|
return result;
|
|
})()
|
|
"""
|
|
ws.send(json.dumps({
|
|
"id": 3,
|
|
"method": "Runtime.evaluate",
|
|
"params": {"expression": script, "contextId": frame.get("id")}
|
|
}))
|
|
resp = json.loads(ws.recv())
|
|
result_eval = resp.get("result", {})
|
|
if result_eval.get("result", {}).get("type") == "array":
|
|
items = result_eval.get("result", {}).get("value", [])
|
|
if items:
|
|
origin = frame_url.rsplit("/", 2)[0] + "//" + frame_url.split("/")[2]
|
|
local_storage_by_origin[origin] = items
|
|
|
|
# 获取主文档的 localStorage
|
|
script_main = """
|
|
(function() {
|
|
var result = [];
|
|
try {
|
|
for (var i = 0; i < localStorage.length; i++) {
|
|
var key = localStorage.key(i);
|
|
result.push([key, localStorage.getItem(key)]);
|
|
}
|
|
} catch(e) {}
|
|
return result;
|
|
})()
|
|
"""
|
|
ws.send(json.dumps({
|
|
"id": 4,
|
|
"method": "Runtime.evaluate",
|
|
"params": {"expression": script_main}
|
|
}))
|
|
resp = json.loads(ws.recv())
|
|
result_eval = resp.get("result", {})
|
|
if result_eval.get("result", {}).get("type") == "array":
|
|
items = result_eval.get("result", {}).get("value", [])
|
|
if items:
|
|
origin = page_url.rsplit("/", 2)[0] + "//" + page_url.split("/")[2]
|
|
local_storage_by_origin[origin] = items
|
|
|
|
ws.close()
|
|
|
|
# 构建 JSON
|
|
result = {
|
|
"cookies": [],
|
|
"origins": []
|
|
}
|
|
|
|
for c in douyin_cookies:
|
|
result["cookies"].append({
|
|
"name": c.get("name", ""),
|
|
"value": c.get("value", ""),
|
|
"domain": c.get("domain", ""),
|
|
"path": c.get("path", "/"),
|
|
"expires": c.get("expires", -1),
|
|
"httpOnly": c.get("httpOnly", False),
|
|
"secure": c.get("secure", True),
|
|
"sameSite": c.get("sameSite", "Lax")
|
|
})
|
|
|
|
# 添加 origins
|
|
for origin, items in local_storage_by_origin.items():
|
|
origin_entry = {
|
|
"origin": origin,
|
|
"localStorage": [{"name": name, "value": value} for name, value in items]
|
|
}
|
|
result["origins"].append(origin_entry)
|
|
|
|
# 保存
|
|
os.makedirs("cookies", exist_ok=True)
|
|
with open(COOKIE_FILE, "w", encoding="utf-8") as f:
|
|
json.dump(result, f, indent=2, ensure_ascii=False)
|
|
|
|
print(f"Cookie 已保存到: {COOKIE_FILE}")
|
|
print("")
|
|
|
|
# 打印关键 Cookie
|
|
key_cookies = ["sessionid", "uid_tt", "ssid", "ttwid"]
|
|
print("关键 Cookie:")
|
|
for c in douyin_cookies:
|
|
if c.get("name") in key_cookies:
|
|
val = c.get("value", "")
|
|
if len(val) > 30:
|
|
val = val[:30] + "..."
|
|
print(f" {c.get('name')}: {val}")
|
|
|
|
return True
|
|
|
|
if __name__ == "__main__":
|
|
if not get_douyin_cookies():
|
|
sys.exit(1)
|
|
PYEOF
|
|
|
|
echo ""
|
|
echo "=========================================="
|
|
echo " 完成!文件已导出为$COOKIE_FILE"
|
|
echo "==========================================" |