211 lines
6.3 KiB
JavaScript
211 lines
6.3 KiB
JavaScript
// 获取抖音 Cookie 和 sec_user_id 的 Playwright 脚本
|
||
// 用法: node get-cookie.js
|
||
// 会自动打开 Chrome 浏览器,请扫码登录抖音
|
||
|
||
import { chromium } from "playwright";
|
||
import { writeFileSync, readFileSync } from "fs";
|
||
import { resolve, dirname } from "path";
|
||
import { fileURLToPath } from "url";
|
||
|
||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||
const CONFIG_PATH = resolve(__dirname, "config.json");
|
||
|
||
async function main() {
|
||
console.log("🚀 启动浏览器...");
|
||
|
||
const browser = await chromium.launch({
|
||
channel: "chrome",
|
||
headless: false,
|
||
args: ["--disable-blink-features=AutomationControlled"],
|
||
});
|
||
|
||
const context = await browser.newContext({
|
||
viewport: { width: 1366, height: 768 },
|
||
userAgent:
|
||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
|
||
});
|
||
|
||
const page = await context.newPage();
|
||
|
||
// 监听网络请求,捕获 sec_user_id
|
||
let capturedSecUserId = null;
|
||
|
||
page.on("request", (request) => {
|
||
const url = request.url();
|
||
if (url.includes("follow") || url.includes("aweme")) {
|
||
try {
|
||
const postData = request.postData();
|
||
if (postData && postData.includes("sec_user_id")) {
|
||
const parsed = JSON.parse(postData);
|
||
if (parsed.sec_user_id) {
|
||
capturedSecUserId = parsed.sec_user_id;
|
||
console.log("✅ 从请求中捕获到 sec_user_id:", capturedSecUserId);
|
||
}
|
||
}
|
||
} catch (e) {
|
||
// 忽略解析错误
|
||
}
|
||
}
|
||
});
|
||
|
||
// 也监听响应,从 body 中提取 sec_user_id
|
||
page.on("response", async (response) => {
|
||
const url = response.url();
|
||
if (url.includes("user/self") || url.includes("aweme")) {
|
||
try {
|
||
const body = await response.text();
|
||
const match = body.match(/"sec_uid":"([^"]+)"/);
|
||
if (match && !capturedSecUserId) {
|
||
capturedSecUserId = match[1];
|
||
console.log("✅ 从响应中捕获到 sec_user_id:", capturedSecUserId);
|
||
}
|
||
} catch (e) {
|
||
// 忽略
|
||
}
|
||
}
|
||
});
|
||
|
||
console.log("📱 正在打开抖音登录页...");
|
||
await page.goto("https://www.douyin.com/", {
|
||
waitUntil: "domcontentloaded",
|
||
timeout: 30000,
|
||
});
|
||
|
||
console.log("🔑 请在浏览器中扫码登录抖音...");
|
||
console.log(" (等待登录完成,检测到登录后会自动继续)");
|
||
|
||
// 等待登录成功:检测 cookie 中是否有 sessionid
|
||
await waitForLogin(context);
|
||
|
||
console.log("✅ 登录成功!");
|
||
|
||
// 导航到个人主页以触发 sec_user_id 相关请求
|
||
console.log("📋 正在访问个人主页获取 sec_user_id...");
|
||
await page.goto("https://www.douyin.com/user/self?from_tab_name=main", {
|
||
waitUntil: "networkidle",
|
||
timeout: 30000,
|
||
});
|
||
|
||
// 等待页面加载
|
||
await page.waitForTimeout(3000);
|
||
|
||
// 尝试从页面数据中提取 sec_user_id
|
||
if (!capturedSecUserId) {
|
||
capturedSecUserId = await extractSecUserIdFromPage(page);
|
||
}
|
||
|
||
// 如果还没获取到,尝试点击关注按钮触发请求
|
||
if (!capturedSecUserId) {
|
||
console.log("🖱️ 尝试触发关注列表请求以获取 sec_user_id...");
|
||
try {
|
||
// 点击关注按钮
|
||
const followBtn = page.locator('[data-e2e="follow-count"]').first();
|
||
if (await followBtn.isVisible({ timeout: 5000 })) {
|
||
await followBtn.click();
|
||
await page.waitForTimeout(3000);
|
||
}
|
||
} catch (e) {
|
||
console.log(" 未能找到关注按钮,尝试其他方式...");
|
||
}
|
||
|
||
// 再次尝试从请求中获取
|
||
if (!capturedSecUserId) {
|
||
capturedSecUserId = await extractSecUserIdFromPage(page);
|
||
}
|
||
}
|
||
|
||
// 获取所有 cookies
|
||
const cookies = await context.cookies();
|
||
const cookieString = cookies
|
||
.map((c) => `${c.name}=${c.value}`)
|
||
.join("; ");
|
||
|
||
console.log("\n========================================");
|
||
console.log("📋 获取结果:");
|
||
console.log("========================================");
|
||
console.log("Cookie:", cookieString.substring(0, 200) + "...");
|
||
console.log("sec_user_id:", capturedSecUserId || "❌ 未获取到");
|
||
console.log("========================================\n");
|
||
|
||
// 读取现有配置
|
||
let config = {};
|
||
try {
|
||
config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
||
} catch (e) {
|
||
console.log(" 未找到现有配置,将创建新配置");
|
||
}
|
||
|
||
// 更新配置
|
||
config.cookie = cookieString;
|
||
if (capturedSecUserId) {
|
||
config.sec_user_id = capturedSecUserId;
|
||
}
|
||
|
||
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 4), "utf8");
|
||
console.log(`💾 配置已保存到: ${CONFIG_PATH}`);
|
||
|
||
if (!capturedSecUserId) {
|
||
console.log("\n⚠️ 未能自动获取 sec_user_id");
|
||
console.log(" 请参考 README 手动获取:");
|
||
console.log(" 1. 按 F12 打开开发者工具 -> Network");
|
||
console.log(" 2. 筛选 'follow'");
|
||
console.log(" 3. 点击关注按钮,在请求 Payload 中找到 sec_user_id");
|
||
console.log(" 4. 手动填入 config.json");
|
||
}
|
||
|
||
await browser.close();
|
||
console.log("👋 浏览器已关闭,脚本执行完毕");
|
||
}
|
||
|
||
// 等待用户登录成功
|
||
async function waitForLogin(context) {
|
||
return new Promise((resolve) => {
|
||
const checkInterval = setInterval(async () => {
|
||
const cookies = await context.cookies();
|
||
const hasSession = cookies.some((c) => c.name === "sessionid");
|
||
if (hasSession) {
|
||
clearInterval(checkInterval);
|
||
resolve();
|
||
}
|
||
}, 2000);
|
||
});
|
||
}
|
||
|
||
// 从页面中提取 sec_user_id
|
||
async function extractSecUserIdFromPage(page) {
|
||
// 方法1: 从页面 URL 提取
|
||
const url = page.url();
|
||
const urlMatch = url.match(/user\/([^?]+)/);
|
||
if (urlMatch && urlMatch[1] !== "self") {
|
||
return urlMatch[1];
|
||
}
|
||
|
||
// 方法2: 从页面 HTML 中的 __INITIAL_STATE__ 提取
|
||
try {
|
||
const html = await page.content();
|
||
const match = html.match(/"sec_uid":"([^"]+)"/);
|
||
if (match) return match[1];
|
||
} catch (e) {
|
||
// 忽略
|
||
}
|
||
|
||
// 方法3: 从 window.__INITIAL_STATE__ 提取
|
||
try {
|
||
const secUid = await page.evaluate(() => {
|
||
const state = window.__INITIAL_STATE__;
|
||
if (state?.user?.sec_uid) return state.user.sec_uid;
|
||
return null;
|
||
});
|
||
if (secUid) return secUid;
|
||
} catch (e) {
|
||
// 忽略
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
main().catch((err) => {
|
||
console.error("❌ 脚本执行出错:", err.message);
|
||
process.exit(1);
|
||
});
|