Choosing Between page.evaluate and page.evaluateHandle for Large DOM Extraction
0 reputation · 14 Mar 2021, 13:50 UTC
0 reputation · 14 Mar 2021, 13:50 UTC
The task is to retrieve all rows of a 10,000‑row HTML table from a remote page using Puppeteer. The result can be large enough that copying it into Node via page.evaluate may cause high memory usage, while page.evaluateHandle avoids copying but requires explicit handle.dispose() to prevent leaks.
Uncertainty remains about which strategy yields the best trade‑off between memory safety and overall execution time when the dataset must be processed in a single run, and whether chunking the extraction with multiple page.evaluate calls is preferable to a single page.evaluateHandle followed by selective property access.
page.evaluateHandle and dispose the handle after extracting only the needed fields, or is it safer to split the table into smaller chunks and use page.evaluate for each chunk?35275 reputation · 14 Mar 2021, 23:34 UTC
For a single run where you ultimately need all rows in Node, use chunked page.evaluate with in-page serialization and process/release each chunk. Do not use a single page.evaluateHandle for the whole table unless you only need a small subset of fields and can keep the handle alive in the page.
page.evaluate copies only serializable return values into Node. Map table cells to plain objects or arrays inside the page and never return DOM elements.page.evaluateHandle returns a JSHandle that lives in the browser page context. It avoids copying the whole object until properties are accessed, but the handle must be disposed, preferably in a finally block, to release the browser-side reference.page.evaluate bounds Node transfer and avoids handle lifecycle. It only reduces peak RSS if each chunk is processed and released, not accumulated into one final in-memory array.page.evaluateHandle is preferable only when you need a small subset of fields from a large live object. Repeated property access crosses the context boundary and can be slower than one serialization.const rows = await page.evaluate(({start, end}) => {
const trs = Array.from(document.querySelectorAll('table tbody tr'));
return trs.slice(start, end).map(tr => ({
cells: Array.from(tr.querySelectorAll('td')).map(td => td.innerText)
}));
}, {start, end});
page.evaluateHandle, always dispose:
let handle;
try {
handle = await page.evaluateHandle(() => document.querySelector('table'));
// access only needed fields
} finally {
await handle?.dispose();
}
One missing diagnostic that changes the recommendation: can you stream/write each chunk and release it, or must you hold all 10,000 rows in Node memory at once? If you must accumulate the full set, peak memory will be similar regardless of chunking, and the decision shifts to transfer overhead vs handle safety. Knowing average serialized bytes per row also lets you set a data-size-based chunk target instead of a row-count guess.
page.evaluate, chunked page.evaluate at 500 and 1,000 rows with release per chunk, and evaluateHandle with selective access plus dispose.Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.