Skip to content

页面管理

页面对应了浏览器中的选项卡,我们大多数任务都是操作页面

通常我们需要首先使用 CSS 选择器来在 DOM 上获取指定的元素,然后通过鼠标、键盘等与元素进行交互。当然获取指定元素的内容也是常见的操作。

整个页面管理中最为复杂的就是如何判断页面是否加载完毕,Puppeteer 的核心逻辑就是等待指定元素可见

创建页面

使用Browser对象上的 newPage() 方法来创建一个新的页面:

JavaScript
import puppeteer from "puppeteer";

const browser = await puppeteer.launch();
const page = await browser.newPage();

// page

await browser.close();

如果创建了多个页面或者点击自动创建了选项卡,可以使用 browser.pages() 来返回所有 Page。

Page 类

Page对象提供了与浏览器中的单个选项卡或插件后台页面进行交互的方法。我们大多数操作都是基于该对象之上的,对 Page 操作的基本流程可以概括为:

  1. 页面导航,说白了就是输入 URL 来获取指定内容,通常会返回响应对象(HTTPResponse)
  2. 获取Frame,他代表一个 DOM 框架,理论上
  3. 获取 Element ,这可以通过选择器来实现,还有一个进一步的抽象就是Locator
  4. 对特定 Element 执行操作,这包括点击、输入等交互操作(通过 Locator 或者直接在 Page 上使用选择器进行操作)
  5. 其他特殊情况操作,截图、pdf、上传文件等等

对于简单的爬虫应用,通常第一步获取响应对象就结束了,因为不需要和特定元素交互。只需要获取响应对象的文本来解析就好了(Element、Locator 都没有提供返回对应元素文本的方法)。当然如果是比较复杂的爬虫应用需要进行一些键盘、鼠标操作可能就需要 Locator 相关的知识。这其中的一个非常核心的知识点就是如何等待指定的元素加载完成

页面导航就是对 URL 的操作,以及前进、后退、刷新等:

  • Page.goto(url, opts) -> Promise<HTTPResponse|null>: 导航到给定的 url,通常是 page 操作的第一步
  • Page.reload(opts) -> Promise<HTTPResponse|null>: 刷新
  • Page.goBack(opts) -> Promise<HTTPResponse|null>: 退后
  • Page.goForward(opts) -> Promise<HTTPResponse|null>: 前进

这类操作统称为导航操作,对这些操作还有一些全局的设置:

  • Page.DefaultNavigationTimeout(timeout: number): 设置导航操作的超时时间(ms)

获取框架(Frame)

每个页面都必然包含一个主框架(mainFrame),如果页面中还包含 iframe 元素也会包含其他框架。所谓的框架实际上就是指代一个完整的 DOM 树。我们的获取 Element 的你操作都是在 DOM 框架上完成的:

  • Page.mainFrame() -> Frame: 获取页面的主框架
  • Page.frames() -> Frame[]: 获取页面的所有框架

获取元素(Element)

通过选择器来获取指定元素,提供了类似 Jquery 的接口:

  • Page.$(selector) -> Promise<ElementHandle | null>: 获取与选择器匹配的第一个元素
  • Page.$$(selector) -> Promise<Array<ElementHandle>>: 获取与选择器匹配的所有元素
  • Page.$eval(selector, func, args) -> Promise<Awaited<ReturnType<Func>>>: 对获取的元素执行 func 函数
  • Page.$$eval(selector, func, args) -> Promise<Awaited<ReturnType<Func>>>: 对获取的元素列表执行 func 函数(注意是对列表本身,而不是列表中的每个值)

Tips

这些函数本质上是 Frame 中提供的,相当于 Page.mainFrame().xxx 方法的映射

Tips

更好的是通过 Page.locator(selector) 来获取Locator实现与元素的交互

获取或设置页面属性

隶属于该页面有很多属性可以读取和设置:

  • Page.content() -> Promise<string>/Page.setContent(html:string, opt) -> Promise<void>: 获取/设置页面,包括 DOCTYPE
  • Page.cookies(urls?:string[]) -> Promise<Cookie[]>/Page.setCookie(cookies) -> Promise<void>/Page.deleteCookie(cookies) -> Promise<void>: 返回/设置/删除当前页面的 Cookie
  • Page.title() -> Promise<string>: 返回页面的标题
  • Page.url() -> string: 返回页面的 URL
  • isClosed() -> boolean: 判断该页面是否关闭
  • isJavaScriptEnabled() -> boolean: 判断页面是否启用了 JavaScript
  • setJavaScriptEnabled(enabled: boolean) -> Promise<void>: 启动/停止页面的 JavaScript

用户代理和视口(模拟特定设备)

通过调整用户代理(UserAgent)和视口(ViewPort)来模拟手机请求:

  • Page.setViewport({height:number, width:number, isMobile:boolean}) -> Promise<void>: 设置视口
  • Page.setUserAgent(userAgent:string) -> Promise<void>: 设置用户代理

大多数情况下我们不需要设置这两个,如果是为了模拟特定设备主要是手机,可以使用 emulate 来实现:

JavaScript
import { KnownDevices } from "puppeteer";
const iPhone = KnownDevices["iPhone 15 Pro"];

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.emulate(iPhone); // 一定要在 goto 之前调用
  await page.goto("https://www.google.com");
  // other actions...
  await browser.close();
})();

获取父对象(Browser/BrowserContext)

Page 提供了两个方法来获取他所属的浏览器或者浏览器上下文对象:

  • Page.browser() -> Browser: 获取 Page 隶属的 Browser 对象
  • Page.browserContext() -> BrowserContext: 获取 Page 隶属的 BrowserContext 对象

评估(evaluate)

所谓的评估就是在当前上下文执行一段 JS 语句。他类似于在浏览器开发者工具的控制台中执行语句:

  • Page.evaluate(func, args) -> Promise<Awaited<ReturnType<Func>>>
JavaScript
const aHandle = await page.evaluate("1 + 2");

// 可以传入参数
const bodyHandle = await page.$("body");
const html = await page.evaluate((body) => body.innerHTML, bodyHandle);
await bodyHandle.dispose();

交互

所谓的交互就是点击、悬停、输入等:

  • Page.click(selector, opts) -> Promise<void>: 触发 selector 指定的 Element 的 click 事件
  • Page.hover(selector) -> Promise<void>: 触发 selector 指定的 Element 的悬停
  • Page.type(selector, text, opts) -> Promise<void>: 要发送 text 字符的键盘按键

这其中比较特殊的就是按键:

JavaScript
// Types instantly
await page.type("#mytextarea", "Hello");
// Types slower, like a user
await page.type("#mytextarea", "World", { delay: 100 });

如果需要输入 Control、ArrowDown 等特殊字符就必须使用 Keyboard 相关的方法。