如何获取 Puppeteer 访问的页面的所有 DOM 元素上的所有事件 - 基本上是 getEventListeners
Posted
技术标签:
【中文标题】如何获取 Puppeteer 访问的页面的所有 DOM 元素上的所有事件 - 基本上是 getEventListeners【英文标题】:How to get all events on all DOM elements of a page visited by Puppeteer - basically getEventListeners 【发布时间】:2021-04-08 21:40:40 【问题描述】:我正在处理一些 Puppeteer 支持的网站分析,并且确实需要列出页面上的所有事件。
使用“普通”javascript 很容易,所以我想我可以在 Puppeteer 中评估它并执行其他任务。
嗯 - 这并不容易,例如“getEventListeners”不起作用。所以下面的这段代码不起作用(但如果我把被评估的代码复制到浏览器的控制台并运行——它运行良好);
exports.getDomEventsOnElements = function (page)
return new Promise(async (resolve, reject) =>
try
let events = await page.evaluate(() =>
let eventsInDom = [];
const elems = document.querySelectorAll('*');
for(i = 0; i < elems.length; i++)
const element = elems[i];
const allEventsPerEl = getEventListeners(element);
if(allEventsPerEl)
const filteredEvents = Object.keys(allEventsPerEl).map(function(k)
return event: k, listeners: allEventsPerEl[k] ;
)
if(filteredEvents.length > 0)
eventsInDom.push(
el: element,
ev: filteredEvents
)
return eventsInDom;
)
resolve(events);
catch (e)
reject(e);
)
我进行了进一步调查,看起来这在 Puppeteer 中不起作用,甚至尝试过使用良好的旧 JQuery const events = $._data( element[0], 'events' );
,但它也不起作用。
然后我偶然发现了 Chrome DevTools 协议 (CDP),应该可以通过预先定义单个元素来获得它;
const cdp = await page.target().createCDPSession();
const INCLUDE_FN = true;
const result: objectId = await cdp.send('Runtime.evaluate',
expression: 'foo',
objectGroup: INCLUDE_FN ?
'provided' : // will include fn
'' // wont include fn
);
const listeners = await cdp.send('DOMDebugger.getEventListeners', objectId );
console.dir(listeners, depth: null );
(src:https://github.com/puppeteer/puppeteer/issues/3349)
但是当我想检查每个 DOM 元素的事件并将它们添加到数组时,这看起来太复杂了。我怀疑有比循环页面元素并为每个元素运行 CDP 更好的方法。或者说得更好——我希望 :)
有什么想法吗?
我只想拥有一个包含 (JS) 事件的所有元素的数组,例如:
let allEventsOnThePage : [
el: "blutton", events : ["click"],
el: "input", events : ["click", "blur", "focus"],
/* you get the picture */
];
【问题讨论】:
也许github.com/puppeteer/puppeteer/issues/3618 可以提供帮助。 【参考方案1】:我很好奇,所以我研究了扩展您找到的 CDP 示例,并想出了这个:
async function describe (session, selector = '*')
// Unique value to allow easy resource cleanup
const objectGroup = 'dc24d2b3-f5ec-4273-a5c8-1459b5c78ca0';
// Evaluate query selector in the browser
const result: objectId = await session.send('Runtime.evaluate',
expression: `document.querySelectorAll("$selector")`,
objectGroup
);
// Using the returned remote object ID, actually get the list of descriptors
const result = await session.send('Runtime.getProperties', objectId );
// Filter out functions and anything that isn't a node
const descriptors = result
.filter(x => x.value !== undefined)
.filter(x => x.value.objectId !== undefined)
.filter(x => x.value.className !== 'Function');
const elements = [];
for (const descriptor of descriptors)
const objectId = descriptor.value.objectId;
// Add the event listeners, and description of the node (for attributes)
Object.assign(descriptor, await session.send('DOMDebugger.getEventListeners', objectId ));
Object.assign(descriptor, await session.send('DOM.describeNode', objectId ));
elements.push(descriptor);
// Clean up after ourselves
await session.send('Runtime.releaseObjectGroup', objectGroup );
return elements;
它将返回一个对象数组,每个对象(至少)具有node
和listeners
属性,可以按如下方式使用:
/** Helper function to turn a flat array of key/value pairs into an object */
function parseAttributes (array)
const result = [];
for (let i = 0; i < array.length; i += 2)
result.push(array.slice(i, i + 2));
return Object.fromEntries(result);
(async () =>
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://chromedevtools.github.io/devtools-protocol', waitUntil: 'networkidle0' );
const session = await page.target().createCDPSession();
const result = await describe(session);
for (const node: localName, attributes , listeners of result)
if (listeners.length === 0) continue;
const id, class: _class = parseAttributes(attributes);
let descriptor = localName;
if (id !== undefined) descriptor += `#$id`;
if (_class !== undefined) descriptor += `.$_class`;
console.log(`$descriptor:`);
for (const type, handler: description of listeners)
console.log(` $type: $description`);
await browser.close();
)();
这将返回类似:
button.aside-close-button:
click: function W()I.classList.contains("shown")&&(I.classList.remove("shown"),P.focus())
main:
click: function W()I.classList.contains("shown")&&(I.classList.remove("shown"),P.focus())
button.menu-link:
click: e=>e.stopPropagation(),I.addEventListener("transitionend",()=>O.focus(),once:!0),I.classList.add("shown")
【讨论】:
看起来很有前途,会做一些测试看看它的性能如何(这将在数百或数千个 URL 上运行) 谢谢,这很完美,我很惊讶它的速度有多快。需要更多测试,但也许你可以将它公关给 Puppeteer :) CDP 有多快令人惊讶。也将使用复杂页面进行测试以上是关于如何获取 Puppeteer 访问的页面的所有 DOM 元素上的所有事件 - 基本上是 getEventListeners的主要内容,如果未能解决你的问题,请参考以下文章
想用 Puppeteer 刮桌子。如何获取所有行,遍历行,然后为每一行获取“td”?
Javascript/HTML/Puppeteer - 如何访问属性数据绑定中的值(单击按钮)?