反爬虫优化,评论图片支持

This commit is contained in:
2025-10-23 13:25:51 +08:00
parent 590b26c420
commit 5a5eba19e4
25 changed files with 1082 additions and 112 deletions
+65 -5
View File
@@ -1,20 +1,22 @@
import { chromium, type BrowserContext } from 'playwright'
export const runtime = 'nodejs'
// A simple singleton manager for a persistent Chromium context with ref-counting.
// Prevents concurrent tasks from closing the shared window prematurely.
import { type Browser, type BrowserContext } from 'playwright'
import { chromium } from 'playwright-extra' // A simple singleton manager for a persistent Chromium context with ref-counting. // Prevents concurrent tasks from closing the shared window prematurely.
import stealth from 'puppeteer-extra-plugin-stealth'
chromium.use(stealth());
let contextPromise: Promise<BrowserContext> | null = null
let context: BrowserContext | null = null
let refCount = 0
let idleCloseTimer: NodeJS.Timeout | null = null
const USER_DATA_DIR = 'chrome-profile/douyin'
const USER_DATA_DIR = process.env.USER_DATA_DIR ?? 'chrome-profile/douyin'
async function launchContext(): Promise<BrowserContext> {
const ctx = await chromium.launchPersistentContext(
USER_DATA_DIR,
{
headless: Boolean(process.env.CHROMIUM_HEADLESS ?? 'false'),
headless: process.env.CHROMIUM_HEADLESS === 'true',
viewport: {
width: Number(process.env.CHROMIUM_VIEWPORT_WIDTH ?? 1280),
height: Number(process.env.CHROMIUM_VIEWPORT_HEIGHT ?? 1080)
@@ -77,3 +79,61 @@ export async function releaseBrowserContext(options?: { idleMillis?: number }):
}
}, idleMillis)
}
// --- Isolated context support for per-scrape independence ---
const isolatedMap = new WeakMap<BrowserContext, Browser>()
async function getSharedStorageState(): Promise<any | undefined> {
try {
const shared = await acquireBrowserContext()
const state = await shared.storageState()
// Do not force-close immediately; keep ref-counting behavior
await releaseBrowserContext()
return state
} catch {
// If shared context not available, proceed without storageState
return undefined
}
}
/**
* Create a brand-new, independent Browser + Context for a single scrape.
* Attempts to import cookies/localStorage from the shared persistent profile
* so you remain logged-in, but isolates network events, cache and listeners.
*/
export async function acquireIsolatedContext(): Promise<BrowserContext> {
const storageState = await getSharedStorageState()
const browser = await chromium.launch({
headless: process.env.CHROMIUM_HEADLESS === 'true'
})
const ctx = await browser.newContext({
storageState,
viewport: {
width: Number(process.env.CHROMIUM_VIEWPORT_WIDTH ?? 1280),
height: Number(process.env.CHROMIUM_VIEWPORT_HEIGHT ?? 1080)
}
})
isolatedMap.set(ctx, browser)
ctx.on('close', () => {
const b = isolatedMap.get(ctx)
if (b) {
b.close().catch(() => {})
isolatedMap.delete(ctx)
}
})
return ctx
}
export async function releaseIsolatedContext(ctx: BrowserContext | null | undefined): Promise<void> {
if (!ctx) return
try {
await ctx.close()
} finally {
const b = isolatedMap.get(ctx)
if (b) {
await b.close().catch(() => {})
isolatedMap.delete(ctx)
}
}
}