Puppeteer CDPSession Send Expression Multiple Values - puppeteer

I'm using this bit of code that is working for its purpose of setting one localStorage value, but am unsure how to apply multiple values using this method. Searching for documentation has lead me many places without clear answers.
const browser = await puppeteer.launch();
browser.on('targetchanged', async (target) => {
const targetPage = await target.page();
const client = await targetPage.target().createCDPSession();
await client.send('Runtime.evaluate', {
expression: `localStorage.setItem('hello', 'world')`,
});
});
Specifically, how can I set hello2, hello3, etc... within the expression?
expression: `localStorage.setItem('hello', 'world')`,

Related

YouTube's get_video_info endpoint no longer working

I was using the YouTube's get_video_info unofficial endpoint to get the resolution of a video. Sometime very recently, this has stopped working and gives me:
We're sorry...... but your computer or network may be sending automated queries. To protect our users, we can't process your request right now.
Is there another public endpoint via which I can get the size/aspect ratio of the video? I need the endpoint to be accessible without having to use some login or developer key.
I spent a couple of hours debugging youtube-dl, and I found out that for all videos, you can simply get the content of the web page, i.e.
(async () => {
// From the back-end
const urlVideo = "https://www.youtube.com/watch?v=aiSla-5xq3w";
const html = await (await fetch(urlVideo)).text();
console.log(html);
})();
And then you match the content against the following RegExp:
/ytInitialPlayerResponse\s*=\s*({.+?})\s*;\s*(?:var\s+meta|<\/script|\n)/
Sample HTML:
(async () => {
const html = await (await fetch("https://gist.githubusercontent.com/avi12/0255ab161b560c3cb7e341bfb5933c2f/raw/3203f6e4c56fff693e929880f6b63f74929cfb04/YouTube-example.html")).text();
const matches = html.match(/ytInitialPlayerResponse\s*=\s*({.+?})\s*;\s*(?:var\s+meta|<\/script|\n)/);
const json = JSON.parse(matches[1]);
const [format] = json.streamingData.adaptiveFormats;
console.log(`${format.width}x${format.height}`);
})();

how to execute a script in every window that gets loaded in puppeteer?

I need to execute a script in every Window object created in Chrome – that is:
tabs opened through puppeteer
links opened by click()ing links in puppeteer
all the popups (e.g. window.open or "_blank")
all the iframes contained in the above
it must be executed without me evaluating it explicitly for that particular Window object...
I checked Chrome's documentation and what I should be using is Page.addScriptToEvaluateOnNewDocument.
However, it doesn't look to be possible to use through puppeteer.
Any idea? Thanks.
This searches for a target in all browser contexts.
An example of finding a target for a page opened
via window.open() or popups:
await page.evaluate(() => window.open('https://www.example.com/'))
const newWindowTarget = await browser.waitForTarget(async target => {
await page.evaluate(() => {
runTheScriptYouLike()
console.log('Hello StackOverflow!')
})
})
via browser.pages() or tabs
This script run evaluation of a script in the second tab:
const pageTab2 = (await browser.pages())[1]
const runScriptOnTab2 = await pageTab2.evaluate(() => {
runTheScriptYouLike()
console.log('Hello StackOverflow!')
})
via page.frames() or iframes
An example of getting eval from an iframe element:
const frame = page.frames().find(frame => frame.name() === 'myframe')
const result = await frame.evaluate(() => {
return Promise.resolve(8 * 7);
});
console.log(result); // prints "56"
Hope this may help you

Finding/Returning Text

I need to open very simple websites and scan for a json object i.e.
myJSONObject:["el1","el2"]. There is only one HTML <pre> tag on the site that contains 100s of lines of text. Nothing else.
I was planning on scanning the page for myJSONObject: and then return ["el1", "el2"].
I used the following, which returns true, as it finds "myJSONObject:", but I have no way to return any text.
const found = await page.evaluate(() => window.find("myJSONObject:"));
Is there a way to use a regexp or something to find the needed text and return it? Is this at all possible?
I am new to puppeteer, so I am unsure of its capabilities. I appreciate any feedback.
You already find the right function (puppeteer.evaluate) to do the job. With it you can return strings, objects, numbers or booleans (in fact any serializable/stringifyable value) from browser/page context to the node context.
Don't know if you already grasp this: browser/page context and node context are different. The only way you can transfer data between them is by stringifying the data and then transfer them.
Said that, to solve your problem you have to come up with a regex and return the string matched. Full working example:
Suppose the <pre> text is something like this: <pre>[...] myJSONObject:["el1","el2"] [...]</pre>
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
// setup test page
await page.evaluate(() => {
const pre = document.createElement('pre');
pre.innerText = '<pre>[...] myJSONObject:["el1","el2"] [...]</pre>';
document.body.append(pre);
});
// important part (this is the answer to your question)
const myJson = await page.evaluate(() => {
var re = /myJSONObject:(\[.*?])/; // regex to match "json text"
const pre = document.querySelector('pre').innerText;
const matchedJsonText = pre.match(re)[1];
const json = JSON.parse(matchedJsonText);
return json;
});
// show results
console.log('myJSONObject:', myJson);
await browser.close();
})();
Please note that this regex only work with the json you provided as example. You'll have to come up with a better regex to match the jsons that you need.

get post title after Infinite scroll finished

I manage to show all the post on a site where it has load_more button to go to the next page, but something is missing,
I got error of
e Error: Node is either not visible or not an HTMLElement
at ElementHandle._clickablePoint (/Users/minghann/Documents/productnation_scraper/node_modules/puppeteer/lib/ExecutionContext.js:331:13)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
Which doesn't happen if I don't load all the post. It's hard to debug because I don't know which post is missing what. Full code as below:
const browser = await puppeteer.launch({
devtools: true
});
const page = await browser.newPage();
await page.goto("https://example.net");
await page.waitForSelector(".load_more_btn");
const load_more_exist = !!(await page.$(".load_more_btn"));
while (load_more_exist > 0) {
await page.click(".load_more_btn");
}
const posts = await page.$$(".post");
let result = [];
for (const post of posts) {
result = [
...result,
{
title: await post.$eval(".post_title a", e => e.innerText)
}
];
}
console.log(result);
browser.close();
There are multiple ways and best way is to combine the following two different ways.
Look for Ajax
Wait for request instead. Whenever you click on Load More, it will do a simple ajax request to ?ajax-request=jnews. We can use .waitForRequest or .waitForResponse for this use case. Here is a working example,
await Promise.all([
page.waitForRequest(response => response.url().includes('?ajax-request=jnews') && response.status() === 200),
page.click(".load_more_btn")
])
Clean DOM and wait for new Element
Refer to these answers here and here.
Basically you can remove the dom elements that you collected, so next time you collect more data, there won't be any duplicates.
So, once you remove all current elements like document.querySelectorAll('.jeg_post'), you can simply do another page.waitFor('.jeg_post') later if you need.

pageFunction in Puppeteer returns empty object

I'm using page.$eval in Puppeteer and I dont know why a pageFunction would return an empty object when the object isn't empty. Here's a code sample:
const puppeteer = require('puppeteer');
(async() => {
const browser = await puppeteer.launch({
headless: false,
slowMo: 1000
});
const page = await browser.newPage();
await page.goto('https://www.google.com/search?q=news');
const result1 = await page.$eval('#resultStats', elem => elem.textContent)
console.log('result1', result1); // returns 'About 2,890,000,000 results (0.45 seconds)'. This is expected behavior straight from puppeeteer docs
const result2 = await page.$eval('#resultStats', elem => elem)
console.log('result2', result2); // returns and empty object. Why? I would have expected to see a DOM Node Object here
await browser.close();
})();
How do I get the whole element back in result2?
I didn't understand that the pageFunction function is running within Chromium itself, so in the second example where it is returning elem => elem, it's actually returning a live NodeList collection to Puppeteer.
But returning a live NodeList collection from Chromium back to puppeteer isn't possible because the way Puppeteer passes data to and from Chromium has to be serializable via JSON.stringify / JSON.parse. When Puppeteer runs JSON.stringify on a live NodeList, I believe it returns an empty object.
Well, as you said above.You can get the dom node in the evaluate function.But when you return the dom node from evaluate function, puppeteer will handle the data you returned.So you can't get the adjective data.