推荐使用定位器来选择元素并与其交互。定位器封装了有关如何选择元素的信息,它们允许 Puppeteer 自动等待元素出现在 DOM 中并处于执行操作的正确状态。您始终使用 page.locator() 或 frame.locator() 函数实例化定位器。如果定位器 API 未提供您需要的功能,您仍然可以使用较低级别的 API,例如 page.waitForSelector() 或 ElementHandle。
locator()
下表总结了我对和之间区别的理解$()
:
Page 方法 |
返回类 | 等待元素出现在 DOM 中? | 需要await ? |
输入法 |
---|---|---|---|---|
locator() |
Locator |
是(重试直到成功或超时) | 不 | 只有click ,,,hover scroll |
$() |
ElementHandle |
不 | 是的 | 各种各样的 |
我有一些问题:
- 如果
locator()
等待元素出现在 DOM 中,那么是不是必须有await
?同样,如果$()
不等待,那么是不是就不需要await
? - 为什么
$()
一个不等待元素出现在 DOM 中的方法比等待元素出现在 DOM 中的方法拥有更多的设备输入方法? - 是不是在引擎盖下面是和
locator()
的组合?waitForSelector()
$()
在深入研究您的具体问题之前,我建议您查看有关定位器基础知识的类似主题:了解 Playwright 定位器承诺。Playwright 最初继承了 Puppeteer 风格的方法,例如
page.$()
,然后采用了定位器,并弃用或不鼓励使用传统的page.$()
风格方法。Puppeteer 后来从 Playwright 借用了定位器。这些库对定位器的方法截然不同,但基本思想是相同的。术语:由于
page.$()
和page.waitForSelector()
其他函数返回 ElementHandles,我将其称为“元素句柄 API”。定位器和元素句柄 API 之间的主要区别在于,定位器是惰性的,在对其调用操作(例如
.click()
或)之前不会执行任何操作。调用声明了一种选择策略,以后可能会对其调用操作。没有,因为还没有操作,也没有异步 CDP(Chrome Devtools 协议)浏览器自动化网络调用。.fill()
page.locator()
await
另一方面,基于元素句柄的调用(例如)
$()
运行即时查询并返回元素句柄,该元素句柄表示指向真实 DOM 元素的链接,可以使用诸如 之类的方法进一步操作elementHandle.click()
。在元素句柄 API 中,与定位器的部分waitForSelector()
功能相当。默认情况下自动等待是定位器相对于元素句柄 API 的第一个优势。当您调用 时
await page.locator("p").click()
,该.click()
部分是一个链式操作。考虑:相对
在上面的例子中,差异可能看起来微不足道,但定位器只对浏览器进行一次 CDP 调用而不是两次,并在操作的确切时刻查询元素,从而避免了在 CDP 调用之间改变页面的机会。
相反,该
waitForSelector()
版本会进行两次调用,由于查询和点击操作是分开的,因此会出现竞争条件。如果元素无效或意外更改,则可能会发生难以调试的故障。定位器版本只需声明一次即可轻松重复使用,声明和使用之间留有大量“空间”(就代码和时间而言)。这有利于最佳实践测试方法(如POM),并使大型测试更易于维护。
定位器易于组合,因此您可以随意链接进一步的过滤器,并且每个操作仍运行一个无竞争条件的查询。
请注意,由于 Playwright 比 Puppeteer 更注重测试,因此定位器的优势在 Playwright 中更加明显。在撰写本文时,Playwright 的定位器实现比 Puppeteer 先进得多,而且 Playwright 更倾向于鼓励用户只使用定位器。
至于您的具体问题:
是否自动等待与您是否需要使用无关
await
。await
每当有网络调用、系统调用或进程间通信需要向另一个进程或操作系统发送命令并等待响应时,就会使用。Node 是单线程的,因此为了高效运行,它使用异步代码构造(如承诺),使其能够在网络调用进行时执行其他任务。由于您使用 Puppeteer 自动化的浏览器是独立于 Node 的进程,并且公开了用于网络通信的套接字,因此任何 CDP 调用都是异步的,无论调用是否涉及等待条件。因此唯一不需要的调用
await
是不涉及 CDP 调用的调用,例如page.locator()
。不要混淆“wait”和“await”这两个词——这里完全不同!有关详细信息,请参阅为什么几乎所有 Puppeteer 调用都是异步的?如果您指的是“更多输入选项/参数”。这是因为
page.$()
这是一种“选择并执行”方法,其中包括一个选择器(字符串参数)并立即采取 CDP 操作,转到浏览器并查询元素,然后将元素句柄返回给 Node。操作通常具有超时和其他配置参数,这些参数与调用分离
locator()
,这严格来说是一种选择配置。在定位器 API 中,超时和其他配置选项是通过链接和操作调用来指定的,从而增强了可重用性。如果您是认真的(根据评论,听起来您是认真的),Puppeteer 定位器 API 不如元素句柄 API 成熟,因此目前可用的方法较少。但如果 Puppeteer 效仿 Playwright,其定位器 API 最终可能会变得足够完整,让维护人员弃用元素句柄 API,就像 Playwright 所做的那样。
希望上面已经回答了。
locator()
只是声明了一个选择策略,但除此之外什么都不做。所以,不,locator()
与这两种方法都不相似,尽管一旦定位器被调用了一个操作,它就会像一样自动等待waitForSelector()
。您可以将定位器视为类似于"p"
传递给的字符串参数page.$()
,只是增加了可链接/可组合的方法。虽然我同意元素句柄 API 比定位器 API 级别低,但从技术上讲,不可能像
waitForSelector()
您一样通过元素句柄调用来实现定位器 API。原因是查询元素和对其执行操作之间存在时间间隔——定位器在单个 CDP 调用中同步选择和执行(就浏览器上下文中发生的事情而言),从而避免使用过时的句柄。但这是一个相当狭窄的失败窗口,因此实际上您可以使用元素句柄来近似定位器(我曾尝试对我编写的抓取库执行此操作)。顺便说一句,Puppeteer 和 Playwright 提供了第三种值得注意的 API 样式。我将其称为“评估 API”,它由 、 、 、 等组成
page.evaluate()
。page.$eval()
与元素句柄 API 一样,没有自动等待,查询和操作合并为一个操作。page.$$eval()
page.waitForFunction()
评估 API 甚至比句柄的级别更低,尽管它不是子集并且不能执行句柄可以执行的许多操作,例如发出受信任的事件。
但与元素句柄 API 不同的是,评估 API 中的每个查询和操作都是同步且原子的,并且在浏览器中同步工作的语法通常比处理元素句柄更简单。评估调用可让您对页面进行大量精确控制。
在 Puppeteer 中,我的网页抓取和 PDF 生成脚本几乎只使用评估 API,除了填充输入字段时,元素句柄或定位器变得必不可少。随着 Puppeteer 的定位器 API 逐渐成熟,这种情况可能会发生变化,但即使它与 Playwright 实现了同等水平,定位器也更倾向于测试。可重用性、可组合性、可信事件和 DRYness 在抓取脚本中的重要性不如在测试中重要。
async
关于定位器的最后一点想法是,方法链在/环境中符合语法await
,避免了非链式异步环境中的大量const foo = await ...
中间变量赋值和令人讨厌的await (await page.$()).click()
-type 语法。Puppeteer 可能将链式定位器发挥得太过了,将超时等配置移到了链式调用而不是配置对象中,这在其他方面很麻烦。但我在这里有点偏离主题,所以我暂时把这个讨论放在一边。