You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何使用Puppeteer结合无头Chrome获取Iframe的Inner Html?

解决Puppeteer获取Google广告Iframe文档的问题

我明白你遇到的困扰了——在Chrome控制台能正常拿到广告iframe的contentWindow.document,但用Puppeteer执行时只得到了一堆奇怪的序列化属性。这其实是Puppeteer运行机制导致的,下面给你拆解原因和解决方案:

问题根源

Chrome控制台是直接在浏览器的页面上下文里操作,能直接访问完整的document对象;但Puppeteer的page.evaluate()方法会把你返回的内容序列化后,从浏览器上下文传递到Node.js环境。像document这种复杂的DOM对象,只能被序列化出部分可枚举的属性,所以你得到的是不完整的JSON快照,而不是真正的文档对象。

解决方案

你不需要返回整个document对象,而是应该在浏览器上下文里直接操作它,提取你需要的具体数据;或者用Puppeteer专门的frame API来处理iframe,这两种方法都能解决问题。

方法1:在evaluate里直接提取所需数据

修改page.evaluate()的逻辑,不要返回document,而是提取你需要的内容(比如innerHTML、特定元素文本等):

const result = await page.evaluate(() => {
  const iframe = document.getElementById('google_ads_iframe_175840252/90-min/Homepage/Index/Top_0');
  const iframeDoc = iframe.contentWindow.document;
  // 提取你需要的具体数据,比如文档标题、body内容等
  return {
    iframeTitle: iframeDoc.title,
    iframeBodyText: iframeDoc.body.innerText,
    iframeBodyHTML: iframeDoc.body.innerHTML
  };
});

方法2:使用Puppeteer的Frame API(更可靠)

对于iframe操作,Puppeteer提供了更直接的Frame接口,能避免跨域或序列化问题。你可以通过iframe的URL或关联标识找到对应的Frame对象,然后在这个Frame的上下文里执行操作:

// 先等待iframe元素加载完成
await page.waitForSelector('#google_ads_iframe_175840252/90-min/Homepage/Index/Top_0');
// 遍历页面所有frame,找到目标广告iframe对应的frame
const targetFrame = page.frames().find(frame => 
  frame.url().includes('tpc.googlesyndication.com/safeframe')
);
// 在目标frame的上下文里执行操作
const result = await targetFrame.evaluate(() => {
  // 这里直接访问当前frame的document
  return {
    title: document.title,
    bodyContent: document.body.innerText
  };
});

额外注意事项

  • 你启用了--disable-web-security,这能绕过跨域限制,但正常环境下谷歌广告iframe大概率是跨域的,直接访问contentWindow.document会被浏览器阻止,这时候用Frame API是更稳妥的方案。
  • 固定等待page.waitFor(2000)不够可靠,建议用page.waitForSelector()等待iframe元素出现,或者targetFrame.waitForNavigation()确保iframe内容加载完毕。

修改后的完整代码示例:

const puppeteer = require('puppeteer'); 

let scrape = async () => { 
  const browser = await puppeteer.launch({ 
    args: [ '--start-maximized','--disable-web-security' ], 
    headless: false, 
    userDataDir: 'test-profile-dir', 
  }); 
  const page = await browser.newPage(); 
  try { 
    await page.setViewport({width: 1920, height: 1080}); 
    await page.setUserAgent('Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36'); 
    await page.goto('https://www.90min.com/'); 
    // 等待目标iframe加载
    await page.waitForSelector('#google_ads_iframe_175840252/90-min/Homepage/Index/Top_0');
  } catch(error) { 
    console.error(error); 
    browser.close();
    return;
  } 
  try { 
    // 找到目标iframe对应的frame
    const targetFrame = page.frames().find(frame => 
      frame.url().includes('tpc.googlesyndication.com/safeframe')
    );
    if (!targetFrame) {
      throw new Error('无法找到目标广告iframe对应的frame');
    }
    const result = await targetFrame.evaluate(() => { 
      return { 
        title: document.title,
        bodyText: document.body.innerText,
        bodyHTML: document.body.innerHTML
      }; 
    }); 
    console.log(result);
    browser.close(); 
    return result; 
  } catch(error) { 
    console.error(error); 
    browser.close();
  } 
}; 

scrape().then((value) => { 
  console.log(value); 
});

内容的提问来源于stack exchange,提问作者kevin micol

火山引擎 最新活动