Headless模式下Puppeteer爬虫.stdImg选择器返回空问题求助
问题描述
我正在开发一款基于Puppeteer的网页爬虫,用于提取商品详情(图片、标题、价格)。该爬虫在非无头模式(non-headless mode)下运行正常,但切换至无头模式(headless mode)时,用于提取图片的.stdImg选择器始终返回null。
观察到的行为
- Headless: true + 设置User Agent:标题和价格提取正常,但
.stdImg返回null - Headless: true + 空User Agent:标题、价格提取失败,
.stdImg也返回null - Headless: false + 空User Agent:所有内容提取正常
由于该爬虫最终将作为Chrome扩展的一部分运行,必须使用无头模式,避免弹出浏览器窗口。
已尝试的解决方法
- 通过移除
navigator.webdriver、设置真实User Agent模拟非无头模式 - 滚动页面处理懒加载图片
- 通过截图调试两种模式下的DOM结构
爬虫代码
const { connect } = require("puppeteer-real-browser"); let browser = null; let page = null; //Configs const connectOptions = { headless: false, devtools: true, args: [ "--disable-features=site-per-process", "--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu", "--hide-scrollbars", "--disable-extensions", "--disable-blink-features=AutomationControlled", ], customConfig: {}, turnstile: true, connectOption: {}, disableXvfb: false, ignoreAllFlags: false, }; const viewPortOptions = { width: 1024, height: 768 }; const ExtraHTTPHeadersOptions = { "accept-language": "tr-TR,tr;q=0.9,en-US,en;q=0.8" }; const userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"; //Load page async function safeGoto(page, url, retries = 3) { for (let attempt = 1; attempt <= retries; attempt++) { try { await page.goto(url, { waitUntil: "domcontentloaded", timeout: 60000 }); return; } catch (error) { console.error(`Attempt ${attempt} failed:`, error.message); if (attempt === retries) throw error; } } } //Extract data async function safeEvaluate(page, selector, evaluateFn, errorMessage) { try { const element = await page.waitForSelector(selector, { timeout: 10000 }); return await element.evaluate(evaluateFn); } catch (error) { console.error(errorMessage, error.message); return null; } } async function scrapeWebsite(url, selectors) { try { const { browser: connectedBrowser, page: connectedPage } = await connect(connectOptions); browser = connectedBrowser; const [page] = await browser.pages(); await page.setUserAgent(""); await page.setViewport(viewPortOptions); await page.setExtraHTTPHeaders(ExtraHTTPHeadersOptions); await page.waitForSelector("body", { timeout: 10000 }); await safeGoto(page, url); await page.evaluate(() => { const imgs = document.querySelectorAll("img"); imgs.forEach((img) => img.scrollIntoView({ behavior: "smooth", block: "center" })); }); // Extract data const src = await safeEvaluate( page, selectors.image, (el) => el.src, `Failed to extract image from selector: ${selectors.image}` ); const title = await safeEvaluate( page, selectors.title, (el) => el.textContent.trim().toLowerCase(), `Failed to extract title from selector: ${selectors.title}` ); const value = await safeEvaluate( page, selectors.price, (el) => el.textContent.trim(), `Failed to extract price from selector: ${selectors.price}` ); // Return extracted data const color = selectors.color; return { src, value, title, color, url }; } catch (error) { console.error("Scraping failed:", error.message); throw error; } finally { if (page && !page.isClosed()) { try { await page.close(); } catch (closeError) { console.error("Failed to close page:", closeError.message); } } if (browser && browser.isConnected()) { try { await browser.close(); } catch (closeError) { console.error("Failed to close browser:", closeError.message); } } if (browser && browser.process() != null) browser.process().kill("SIGINT"); } } const getFromDomain = (url) => scrapeWebsite(url, { image: ".stdImg", title: ".classifiedDetailTitle h1", price: ".classified-price-wrapper", color: "#FFE800", });
选择器说明
.stdImg:图片选择器.classifiedDetailTitle h1:标题选择器.classified-price-wrapper:价格选择器
疑问
- 为何
.stdImg选择器在无头模式下失效,但在空User Agent的非无头模式下正常? - 如何修复该问题,实现无头模式下可靠提取图片?
解决方案
问题1的原因分析
无头模式下的Chrome与非无头模式存在核心差异,导致图片选择器失效:
- 浏览器特征检测:网站可能通过
navigator.webdriver、窗口尺寸、渲染特性等识别无头浏览器,返回差异化DOM结构——比如延迟加载图片,甚至不渲染图片元素。 - 资源加载策略:无头模式默认会限制非关键资源(如图片)的加载优先级,
.stdImg对应的图片可能被延迟或跳过;非无头模式下浏览器会自动加载所有可见资源,因此能正常获取。 - UA标识泄露:即使设置了真实User Agent,无头模式下Chrome的UA会隐含
HeadlessChrome标识,网站识别后会调整资源加载逻辑。
问题2的修复方案
针对上述原因,通过以下步骤调整代码,确保无头模式下正常提取图片:
1. 完善无头模式配置
修改connectOptions,启用新版无头模式并补充参数消除自动化特征:
const connectOptions = { headless: "new", // 使用新版无头模式,行为更接近真实浏览器 devtools: false, // 无头模式无需开启开发者工具 args: [ "--disable-features=site-per-process", "--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu", "--hide-scrollbars", "--disable-extensions", "--disable-blink-features=AutomationControlled", "--start-maximized", // 最大化窗口,避免尺寸检测 "--disable-web-security", // 规避部分网站的跨域限制 "--allow-running-insecure-content", ], customConfig: {}, turnstile: true, connectOption: {}, disableXvfb: false, ignoreAllFlags: false, };
2. 正确设置User Agent并隐藏自动化特征
使用真实UA,同时在页面加载前清除自动化标识:
async function scrapeWebsite(url, selectors) { try { const { browser: connectedBrowser, page: connectedPage } = await connect(connectOptions); browser = connectedBrowser; const [page] = await browser.pages(); await page.setUserAgent(userAgent); // 使用定义好的真实UA,不要设为空 await page.setViewport(viewPortOptions); await page.setExtraHTTPHeaders(ExtraHTTPHeadersOptions); // 隐藏自动化特征,避免被网站检测 await page.evaluateOnNewDocument(() => { delete navigator.__proto__.webdriver; Object.defineProperty(navigator, 'maxTouchPoints', { get: () => 1 }); }); // 等待页面完全加载,包括所有资源 await safeGoto(page, url, 3); await page.waitForLoadState("networkidle2"); // 等待网络空闲,确保资源加载完成 // 针对懒加载图片,等待元素出现并加载完成 await page.waitForSelector(selectors.image, { timeout: 15000 }); await page.evaluate((selector) => { const img = document.querySelector(selector); if (img) { img.scrollIntoView({ behavior: "instant", block: "center" }); // 等待图片加载完成 return new Promise(resolve => { if (img.complete) resolve(); else img.onload = resolve; }); } }, selectors.image); // 提取数据,兼容懒加载的data-src属性 const src = await safeEvaluate( page, selectors.image, (el) => el.src || el.dataset.src || el.getAttribute('src'), `Failed to extract image from selector: ${selectors.image}` ); // 其余提取逻辑保持不变...
3. 优化资源等待策略
- 将
waitUntil从domcontentloaded改为networkidle2,确保页面所有资源加载完成; - 针对图片元素额外等待
load事件触发,确认图片已渲染到DOM中; - 兼容懒加载图片的
data-src等属性,避免只读取src导致为空。
4. 调试验证
如果问题仍存在,可在无头模式下生成页面截图,对比非无头模式的DOM结构:
await page.screenshot({ path: 'headless-screenshot.png', fullPage: true });
根据截图调整选择器或等待逻辑。
内容的提问来源于stack exchange,提问作者mr.sun
相关产品推荐
相关产品推荐

