修复A2C工单蜘蛛
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Turnstile 内置求解:等待 widget 自动完成 + token 轮询
|
||||
* Turnstile 内置求解:等待 widget 自动完成 + token 轮询 + 多 frame sitekey 提取
|
||||
*/
|
||||
const { detectCloudflare, isTurnstileSolved, urlIndicatesCfChallenge } = require('./cf-detector');
|
||||
|
||||
@@ -10,6 +10,115 @@ const { detectCloudflare, isTurnstileSolved, urlIndicatesCfChallenge } = require
|
||||
* @property {number} elapsedMs
|
||||
*/
|
||||
|
||||
/**
|
||||
* 在单个 frame 内提取 sitekey
|
||||
* @param {import('puppeteer').Frame} frame
|
||||
* @returns {Promise<string|null>}
|
||||
*/
|
||||
async function extractSitekeyFromFrame(frame) {
|
||||
return frame.evaluate(() => {
|
||||
const widget = document.querySelector('.cf-turnstile[data-sitekey], [data-sitekey]');
|
||||
if (widget) {
|
||||
const key = widget.getAttribute('data-sitekey');
|
||||
if (key) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
const iframes = document.querySelectorAll('iframe[src*="challenges.cloudflare.com"], iframe[src*="challenge-platform"]');
|
||||
for (const iframe of iframes) {
|
||||
const src = iframe.getAttribute('src') || '';
|
||||
const match = src.match(/[?&]sitekey=([^&]+)/i);
|
||||
if (match) {
|
||||
return decodeURIComponent(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
const scripts = document.querySelectorAll('script');
|
||||
for (const script of scripts) {
|
||||
const text = script.textContent || '';
|
||||
const match = text.match(/sitekey['"\s:=]+['"]([0-9x_a-zA-Z-]{10,})['"]/i)
|
||||
|| text.match(/data-sitekey=['"]([0-9x_a-zA-Z-]{10,})['"]/i);
|
||||
if (match) {
|
||||
return match[1];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}).catch(() => null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从主文档及所有子 frame 提取 Turnstile sitekey
|
||||
* @param {import('puppeteer').Page} page
|
||||
* @returns {Promise<string|null>}
|
||||
*/
|
||||
async function extractTurnstileSitekey(page) {
|
||||
const mainKey = await extractSitekeyFromFrame(page.mainFrame());
|
||||
if (mainKey) {
|
||||
return mainKey;
|
||||
}
|
||||
|
||||
for (const frame of page.frames()) {
|
||||
if (frame === page.mainFrame()) {
|
||||
continue;
|
||||
}
|
||||
const frameKey = await extractSitekeyFromFrame(frame);
|
||||
if (frameKey) {
|
||||
return frameKey;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Captcha API 调用前:等待 Turnstile 挑战面出现或 URL 进入 CF 挑战态
|
||||
*
|
||||
* @param {import('puppeteer').Page} page
|
||||
* @param {number} [timeoutMs]
|
||||
* @returns {Promise<string|null>} 若已提取到 sitekey 则返回,否则 null
|
||||
*/
|
||||
async function waitForTurnstileSurface(page, timeoutMs = 20000) {
|
||||
const started = Date.now();
|
||||
const pollMs = 400;
|
||||
|
||||
while (Date.now() - started < timeoutMs) {
|
||||
const sitekey = await extractTurnstileSitekey(page);
|
||||
if (sitekey) {
|
||||
console.log(`[CF] 已检测到 Turnstile sitekey (${sitekey.slice(0, 8)}...)`);
|
||||
return sitekey;
|
||||
}
|
||||
|
||||
const url = page.url();
|
||||
if (urlIndicatesCfChallenge(url)) {
|
||||
await new Promise((r) => setTimeout(r, pollMs));
|
||||
continue;
|
||||
}
|
||||
|
||||
const state = await detectCloudflare(page);
|
||||
if (!state.blocked && state.hasCfClearance) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hasSurface = await page.evaluate(() => {
|
||||
return !!document.querySelector(
|
||||
'.cf-turnstile, [data-sitekey], [name="cf-turnstile-response"], '
|
||||
+ 'iframe[src*="challenges.cloudflare.com"], iframe[src*="challenge-platform"]'
|
||||
);
|
||||
}).catch(() => false);
|
||||
|
||||
if (hasSurface) {
|
||||
await new Promise((r) => setTimeout(r, pollMs));
|
||||
continue;
|
||||
}
|
||||
|
||||
await new Promise((r) => setTimeout(r, pollMs));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待 Turnstile 通过(内置点击 / 自动跳转)
|
||||
* @param {import('puppeteer').Page} page
|
||||
@@ -49,26 +158,6 @@ async function waitForTurnstile(page, options = {}) {
|
||||
return { success: false, stage: 'timeout', elapsedMs: Date.now() - started };
|
||||
}
|
||||
|
||||
/**
|
||||
* 从页面提取 Turnstile sitekey
|
||||
* @param {import('puppeteer').Page} page
|
||||
* @returns {Promise<string|null>}
|
||||
*/
|
||||
async function extractTurnstileSitekey(page) {
|
||||
return page.evaluate(() => {
|
||||
const widget = document.querySelector('.cf-turnstile[data-sitekey], [data-sitekey]');
|
||||
if (widget) {
|
||||
return widget.getAttribute('data-sitekey');
|
||||
}
|
||||
const iframe = document.querySelector('iframe[src*="challenges.cloudflare.com"]');
|
||||
if (iframe && iframe.src) {
|
||||
const match = iframe.src.match(/[?&]sitekey=([^&]+)/);
|
||||
if (match) return decodeURIComponent(match[1]);
|
||||
}
|
||||
return null;
|
||||
}).catch(() => null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Captcha API 返回的 token 注入页面
|
||||
* @param {import('puppeteer').Page} page
|
||||
@@ -91,6 +180,8 @@ async function injectTurnstileToken(page, token) {
|
||||
|
||||
module.exports = {
|
||||
waitForTurnstile,
|
||||
waitForTurnstileSurface,
|
||||
extractTurnstileSitekey,
|
||||
extractSitekeyFromFrame,
|
||||
injectTurnstileToken,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user