Puppeteer:如何处理多个标签?

Posted

技术标签:

【中文标题】Puppeteer:如何处理多个标签?【英文标题】:Puppeteer: How to handle multiple tabs? 【发布时间】:2018-01-30 02:14:56 【问题描述】:

场景:用于开发者应用注册的 Web 表单,包含两部分工作流程。

第 1 页:填写开发者应用详细信息并单击按钮以创建应用程序 ID,该应用程序 ID 在新选项卡中打开...

第 2 页:App ID 页面。我需要从这个页面复制 App ID,然后关闭选项卡并返回第 1 页并填写 App ID(从第 2 页保存),然后提交表单。

我了解基本用法 - 如何打开页面 1 并单击打开页面 2 的按钮 - 但是当页面 2 在新标签页中打开时,如何获取页面 2 的句柄?

例子:

const puppeteer = require('puppeteer');

(async() => 
    const browser = await puppeteer.launch(headless: false, executablePath: '/Applications/Google Chrome.app');
    const page = await browser.newPage();

    // go to the new bot registration page
    await page.goto('https://register.example.com/new', waitUntil: 'networkidle');

    // fill in the form info
    const form = await page.$('new-app-form');

    await page.focus('#input-appName');
    await page.type('App name here');

    await page.focus('#input-appDescription');
    await page.type('short description of app here');

    await page.click('.get-appId'); //opens new tab with Page 2

    // handle Page 2
    // get appID from Page 2
    // close Page 2

    // go back to Page 1
    await page.focus('#input-appId');
    await page.type(appIdSavedFromPage2);

    // submit the form
    await form.evaluate(form => form.submit());

    browser.close();
)();

2017 年 10 月 25 日更新

Browser.pages 的工作是completed 和merged 修复了Emit new Page objects when new tabs created #386 和Request: browser.currentPage() or similar way to access Pages #443。

仍在寻找一个好的用法示例。

【问题讨论】:

【参考方案1】:

两天前已经提交了一个新补丁,现在您可以使用browser.pages() 访问当前浏览器中的所有页面。 工作正常,昨天自己试过了:)

编辑:

如何获取打开为“目标:_blank”链接的新页面的 JSON 值的示例。

const page = await browser.newPage();
await page.goto(url, waitUntil: 'load');

// click on a 'target:_blank' link
await page.click(someATag);

// get all the currently open pages as an array
let pages = await browser.pages();

// get the last element of the array (third in my case) and do some 
// hucus-pocus to get it as JSON...
const aHandle = await pages[3].evaluateHandle(() => document.body);

const resultHandle = await pages[3].evaluateHandle(body => 
  body.innerhtml, aHandle);

// get the JSON value of the page.
let jsonValue = await resultHandle.jsonValue();

// ...do something with JSON

【讨论】:

【参考方案2】:

这将在最新的 alpha 分支中为您工作:

const newPagePromise = new Promise(x => browser.once('targetcreated', target => x(target.page())));
await page.click('my-link');
// handle Page 2: you can access new page DOM through newPage object
const newPage = await newPagePromise;
await newPage.waitForSelector('#appid');
const appidHandle = await page.$('#appid');
const appID = await page.evaluate(element=> element.innerHTML, appidHandle );
newPage.close()
[...]
//back to page 1 interactions

通过将 package.json 依赖设置为

,确保使用最后一个 puppeteer 版本(来自 Github master 分支)
"dependencies": 
    "puppeteer": "git://github.com/GoogleChrome/puppeteer"
,

来源:JoelEinbinder@https://github.com/GoogleChrome/puppeteer/issues/386#issuecomment-343059315

【讨论】:

【参考方案3】:

根据Official Documentation:

browser.pages()

返回:<Promise<Array<Page>>> Promise,它解析为所有打开页面的数组。此处不会列出不可见的页面,例如 "background_page"。您可以使用target.page() 找到它们。

浏览器内所有页面的数组。在多个浏览器上下文的情况下,该方法将返回一个数组,其中包含所有浏览器上下文中的所有页面。

示例用法:

let pages = await browser.pages();
await pages[0].evaluate(() =>  /* ... */ );
await pages[1].evaluate(() =>  /* ... */ );
await pages[2].evaluate(() =>  /* ... */ );

【讨论】:

你如何使用 target.page() ,这在文档中没有明确说明?【参考方案4】:

理论上,您可以覆盖 window.open 函数以始终在当前页面上打开“新标签”并通过历史记录导航。

您的工作流程将是:

    覆盖window.open 函数:

    await page.evaluateOnNewDocument(() => 
      window.open = (url) => 
        top.location = url
      
    )
    

    转到您的第一页并执行一些操作:

    await page.goto(PAGE1_URL)
    // ... do stuff on page 1
    

    通过单击按钮导航到您的第二页并在那里执行一些操作:

    await page.click('#button_that_opens_page_2')
    await page.waitForNavigation()
    // ... do stuff on page 2, extract any info required on page 1
    // e.g. const handle = await page.evaluate(() =>  ... )
    

    返回您的第一页:

    await page.goBack()
    // or: await page.goto(PAGE1_URL)
    // ... do stuff on page 1, injecting info saved from page 2
    

显然,这种方法有其缺点,但我发现它极大地简化了多选项卡导航,如果您已经在多个选项卡上运行并行作业,这将特别有用。不幸的是,当前的 API 并不容易。

【讨论】:

【参考方案5】:

如果它是由target="_blank" 属性引起的,您可以消除切换页面的需要 - 通过设置target="_self"

例子:

element = page.$(selector)

await page.evaluateHandle((el) => 
        el.target = '_self';
 , element)

element.click()

【讨论】:

【参考方案6】:

如果您的点击操作正在发出页面加载,那么任何后续正在运行的脚本都会有效地丢失。要解决此问题,您需要触发操作(在本例中为单击),但 不是 await 。相反,等待页面加载:

page.click('.get-appId');
await page.waitForNavigation();

这将使您的脚本在继续执行进一步操作之前有效地等待下一个页面加载事件。

【讨论】:

【参考方案7】:

您目前不能 - 关注 https://github.com/GoogleChrome/puppeteer/issues/386 以了解该能力何时添加到 puppeteer(希望很快)

【讨论】:

【参考方案8】:

it looks like there's a simple 'page.popup' event

“弹出”窗口对应的页面 当页面打开新选项卡或窗口时发出。

const [popup] = await Promise.all([
  new Promise(resolve => page.once('popup', resolve)),
  page.click('a[target=_blank]'),
]);
const [popup] = await Promise.all([
  new Promise(resolve => page.once('popup', resolve)),
  page.evaluate(() => window.open('https://example.com')),
]);

credit to this github issue for easier 'targetcreated'

【讨论】:

以上是关于Puppeteer:如何处理多个标签?的主要内容,如果未能解决你的问题,请参考以下文章

如何处理角度 6 中的多个 mat-slide-toggle 标签?

实例:使用puppeteer headless方式抓取JS网页

如何用 puppeteer 延迟这个评估

Node Js & Puppeteer - 如何选择包裹在 Anchor 标签内的文本

Puppeteer 如何拦截多个请求

如何在多个文件中运行 Jest-Puppeteer 测试