feat: finist chromium download douyin media
This commit is contained in:
+75
-18
@@ -1,26 +1,83 @@
|
||||
// node run.js https://example.com/dashboard
|
||||
import path from 'path';
|
||||
import { chromium } from 'playwright';
|
||||
|
||||
const userDataDir = './profiles/site1';
|
||||
const targetUrl = process.argv[2] || 'https://baidu.com/';
|
||||
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
||||
|
||||
const context = await chromium.launchPersistentContext(userDataDir, {
|
||||
headless: true, // 无头
|
||||
});
|
||||
await context.addInitScript({ path: './userscripts/my-script.js' });
|
||||
export async function downloadDouyinMedia(targetUrl: string, target_id?: string) {
|
||||
const USER_DATA_DIR = path.join(__dirname, 'profiles/site1');
|
||||
const TARGET_URL = targetUrl;
|
||||
const HEADLESS = true;
|
||||
const TIMEOUT = 10000;
|
||||
|
||||
const page = context.pages()[0] || await context.newPage();
|
||||
await page.goto(targetUrl, { waitUntil: 'networkidle' });
|
||||
const context = await chromium.launchPersistentContext(USER_DATA_DIR, {
|
||||
headless: HEADLESS,
|
||||
});
|
||||
|
||||
// === 你的自动化逻辑(示例)===
|
||||
await page.click('text=开始任务', { timeout: 10_000 }).catch(()=>{});
|
||||
await page.waitForTimeout(1000);
|
||||
try {
|
||||
const page = context.pages()[0] || (await context.newPage());
|
||||
await page.goto(TARGET_URL, { waitUntil: 'load' });
|
||||
|
||||
// @ts-ignore
|
||||
console.log('当前用户:', await page.evaluate(() => document.title));
|
||||
await page.waitForSelector('.xgplayer', { timeout: TIMEOUT }).catch(() => {
|
||||
});
|
||||
|
||||
// 示例:截图/导出结果
|
||||
await page.screenshot({ path: './out.png', fullPage: true });
|
||||
if (target_id) {
|
||||
await page.evaluate(`window.f_down_target_id = "${target_id}"; window.f_down_server_port = ${process.env.PORT || 6100}`)
|
||||
}
|
||||
|
||||
await context.close();
|
||||
console.log('Done.');
|
||||
await page.evaluate(async () => {
|
||||
const d = (globalThis as any).document;
|
||||
const vEle = d.querySelector('.xgplayer video source')
|
||||
const isVideo = !!vEle && !vEle.src.includes('.mp3');
|
||||
|
||||
const urls: string[] = isVideo ? [d.querySelector(".xgplayer video source").src] : [
|
||||
...new Set(
|
||||
Array.from(d.querySelectorAll('.xgplayer img'))
|
||||
// @ts-ignore
|
||||
.map(img => img?.src)
|
||||
.filter(Boolean)
|
||||
),
|
||||
];
|
||||
|
||||
async function urlToBlob(url: string) {
|
||||
const res = await fetch(url, { credentials: "omit" });
|
||||
if (!res.ok) throw new Error(`fetch image failed: ${res.status}`);
|
||||
return await res.blob(); // 保持原 MIME
|
||||
}
|
||||
|
||||
const fd = new FormData();
|
||||
// 附带标题作为一个普通字段
|
||||
fd.append("title", d.title || "");
|
||||
|
||||
let idx = 1;
|
||||
for (const src of urls) {
|
||||
const blob = await urlToBlob(src); // ✅ 直接用字符串
|
||||
const ext = (blob.type && blob.type.split("/")[1]) || "bin";
|
||||
// @ts-ignore
|
||||
fd.append(`file_${idx}`, blob, `media_${idx}.${ext}`);
|
||||
idx++;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const url = new URL(`http://127.0.0.1:${window.f_down_server_port}/upload`);
|
||||
url.searchParams.set("title", d.title || "");
|
||||
url.searchParams.set("type", isVideo ? "video" : "image");
|
||||
// @ts-ignore
|
||||
url.searchParams.set("target_id", window.f_down_target_id || "");
|
||||
|
||||
const r = await fetch(url.toString(), {
|
||||
method: "POST",
|
||||
body: fd,
|
||||
});
|
||||
|
||||
// 为了在 evaluate 内确保完成整个请求-响应流程,读取下响应
|
||||
const json = await r.json().catch(() => ({}));
|
||||
|
||||
return json;
|
||||
});
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
} finally {
|
||||
await context.close();
|
||||
console.log('Done.');
|
||||
}
|
||||
}
|
||||
|
||||
+1
-5
@@ -5,21 +5,17 @@ const userDataDir = './profiles/site1';
|
||||
const startUrl = process.argv[2] || 'https://douyin.com';
|
||||
|
||||
const context = await chromium.launchPersistentContext(userDataDir, {
|
||||
headless: false, // 首次建议“有头”,方便手工/2FA
|
||||
headless: false,
|
||||
args: ['--disable-blink-features=AutomationControlled'],
|
||||
});
|
||||
await context.addInitScript({ path: './userscripts/my-script.js' });
|
||||
|
||||
const page = context.pages()[0] || await context.newPage();
|
||||
await page.goto(startUrl, { waitUntil: 'domcontentloaded' });
|
||||
|
||||
// 在这一步完成你的登录(手输或自动化都可)
|
||||
console.log('>>> 请在打开的浏览器里完成登录,完成后回到终端按回车...');
|
||||
process.stdin.resume();
|
||||
await new Promise(r => process.stdin.once('data', r));
|
||||
|
||||
// 可选:导出 storageState 做备份(即使不用它,持久化目录也保存了)
|
||||
await context.storageState({ path: `${userDataDir}/storageState.json` });
|
||||
await context.close();
|
||||
|
||||
console.log('>>> 登录态已写入 profiles/site1/,后续可无头复用。');
|
||||
|
||||
Reference in New Issue
Block a user