Puppeteer常用API浅析
从CDP与源码的角度简单分析下pptr中的常用API
Should know
puppeteer
- 单词含义:puppeteer[ˌpʌpɪˈtɪr],操纵木偶的人。
- 本质:实现了遵循CDP(Chrome DevTools protocol)的Node顶层API。
Chrome DevTools Protocol
开放了用程序控制页面行为的接口。
允许工具在chromium、chrome和其他基于Blink的浏览器上插桩、监测、调试。其中插桩(instrument)操作根据特点被分成了多种域(DOM, 调试器,网络等等)。每个域中都定义了它所支持的命令及生成的事件,命令与事件都被序列化成了固定结构的JSON对象。
ws远程连接步骤
- 使用
--remote-debugging-port=0
命令启动chrome.exe
- 请求
/json/version
获取数据,得到ws连接地址:webSocketDebuggerUrl
- 连接ws,就可以访问到浏览器实例
Example
const puppeteer = require('puppeteer');
(async() => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://developers.google.com/web/');
// Type into search box.
await page.type('#searchbox input', 'Headless Chrome');
// Wait for suggest overlay to appear and click "show all results".
const allResultsSelector = '.devsite-suggest-all-results';
await page.waitForSelector(allResultsSelector);
await page.click(allResultsSelector);
// Wait for the results page to load and display the results.
const resultsSelector = '.gsc-results .gsc-thumbnail-inside a.gs-title';
await page.waitForSelector(resultsSelector);
// Extract the results from the page.
const links = await page.evaluate(resultsSelector => {
const anchors = Array.from(document.querySelectorAll(resultsSelector));
return anchors.map(anchor => {
const title = anchor.textContent.split('|')[0].trim();
return `${title} - ${anchor.href}`;
});
}, resultsSelector);
console.log(links.join('\n'));
await browser.close();
})();
Most used
puppteer.launch()
发生了什么:
- 根据参数拼接命令
包含三种类型设置:
LaunchOptions
,ChromeArgOptions
,BrowserOptions
。 根据配置选项决定chrome执行命令,通过可执行路径寻找chrome应用 - 启动子进程,绑定流 使用node的childProcess.spawn()启动一个子进程,并绑定默认IO流: stdin, stdout, stderr
- 获取地址,建立连接
- 默认情况。获取node与chrome的ws连接地址后建立连接,当检测到子进程的stderr流中有满足
/^DevTools listening on (ws:\/\/.*)$/
规则的输出时表示与chrome连接成功. - 自定义pipe。使用PipeTransport对象连通自定义的可读与可写流,之后会在它们之间传递Buffer数据。
- 监听链路上的消息与事件 使用由EventEmitter扩展的Connection类扩展连接的通信链路,在外层进一步封装链路中的消息格式等。返回Connection和CDPSession对象(作为client收发遵循CDP的消息)。
- 创建Browser实例,并新建初始空白页
ensureInitialPage()
,最终返回Promise<Browser>
browser.newPage()
newPage为browser中浏览器上下文的方法,在进行页面的操作时需要传递浏览器contextId
使用CDP中Target域的Target.createTarget
创建页面:
async _createPageInContext(contextId) {
const {targetId} = await this._connection.send('Target.createTarget', {url: 'about:blank', browserContextId: contextId || undefined});
const target = await this._targets.get(targetId);
assert(await target._initializedPromise, 'Failed to create target for page');
const page = await target.page();
return page;
}
page.goto()
实际上是执行FrameManager对象的navigate()方法:
// Page -> FrameManager(_frameManager)
async function navigate(client, url, referrer, frameId) {
try {
const response = await client.send('Page.navigate', {url, referrer, frameId});
ensureNewDocumentNavigation = !!response.loaderId;
return response.errorText ? new Error(`${response.errorText} at ${url}`) : null;
} catch (error) {
return error;
}
}
Page对象中的manager
page对象主要使用三种manager来管理常见操作:
- _frameManager - 管理页面相关行为。页面跳转(goto),等待加载(waitFor), 元素选择与处理(evaluate)…
- _networkManager - 管理网络相关行为。请求拦截(setRequestInterception),离线模式(setOfflineMode)…
- _emulationManager - 管理模拟行为。设置移动设备与视口尺寸(setViewport)
page.click()
同样属于_frameManager控制的操作行为,并且是属于在domWorld中操作的行为。
FrameManager中的DOMWorld实例
FrameManager中使用两个DomWorld对象实例管理对于元素的不同操作:
- _mainWorld - 负责使用选择器的元素选择与注入函数等操作
- _secondaryWorld - 负责操作行为,
// Page -> FrameManager(_frameManager) -> DOMWorld(_secondaryWorld)
async click(selector, options) {
const handle = await this.$(selector);
assert(handle, 'No node found for selector: ' + selector);
await handle.click(options);
await handle.dispose();
}
page.$(), page.evaluate(), page.$eval()
属于_frameManager
中_mainWorld
的操作,executionContext中执行元素选择与evaluate
等操作。
// Page -> FrameManager(_frameManager) -> DOMWorld(_mainWorld)
async $(selector) {
const document = await this._document();
const value = await document.$(selector);
return value;
}
...
async evaluate(pageFunction, ...args) {
const context = await this.executionContext();
return context.evaluate(pageFunction, ...args);
}
...
async $eval(selector, pageFunction, ...args) {
const document = await this._document();
return document.$eval(selector, pageFunction, ...args);
}
...
除此之外,还有用来选择多个元素和Handle的page.$$()
, page.$$eval()
方法。
Page.waitForSelector()
// Page -> FrameManager(_frameManager)
async waitForSelector(selector, options) {
const handle = await this._secondaryWorld.waitForSelector(selector, options);
if (!handle)
return null;
// executionContext -> [CDP]Runtime Domain -> ExecutionContextDescription
const mainExecutionContext = await this._mainWorld.executionContext();
// _adoptElementHandle -> [CDP]DOM Domain -> describeNode + resolveNode
const result = await mainExecutionContext._adoptElementHandle(handle);
await handle.dispose();
return result;
}
// Page -> FrameManager(_frameManager) -> DOMWorld(_secondaryWorld)
async _waitForSelectorOrXPath(selectorOrXPath, isXPath, options = {}) {
...
const polling = waitForVisible || waitForHidden ? 'raf' : 'mutation';
const title = `${isXPath ? 'XPath' : 'selector'} "${selectorOrXPath}"${waitForHidden ? ' to be hidden' : ''}`;
const waitTask = new WaitTask(this, predicate, title, polling, timeout, selectorOrXPath, isXPath, waitForVisible, waitForHidden);
const handle = await waitTask.promise;
if (!handle.asElement()) {
await handle.dispose();
return null;
}
return handle.asElement();
...
}
Page.screenshot() & Page.pdf()
- 处理传入配置:格式,路径,质量,剪裁等参数
- 加入到截图任务队列中,screenshotTaskQueue
Other
puppeteer的其他相关项目
juggler
- 实现了符合puppeteer API规范的FireFox自动化协议。
- 该项目在gecko的基础上,将juggler远程调试协议作为测试工具添加了进来。
- 测试时需要在本地手动构建添加了juggler的firefox。
puppeteer-firefox
- 实现了使用puppeteer的语法来操纵firefox
- 使用时需要有绑定了juggler的firefox
- 开发者目前跑测试中
例子:
const pptrFirefox = require('puppeteer-firefox');
(async () => {
const browser = await pptrFirefox.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({path: 'example.png'});
await browser.close();
})();
语法与puppeteer保持一致。
- 原文作者:yrq110
- 原文链接:http://yrq110.me/post/front-end/swim-in-shallow-puppeteer-api/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。