内容简介:从CDP与源码的角度简单分析下pptr中的常用API开放了用程序控制页面行为的接口。允许工具在chromium、chrome和其他基于Blink的浏览器上插桩、监测、调试。其中插桩(instrument)操作根据特点被分成了多种域(DOM, 调试器,网络等等)。每个域中都定义了它所支持的命令及生成的事件,命令与事件都被序列化成了固定结构的JSON对象。
从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
-
获取地址,建立连接
/^DevTools listening on (ws:\/\/.*)$/
- 监听链路上的消息与事件 使用由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保持一致。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。