Я написал крошечные сценарии в узле, используя puppeteer для циклического выполнения кликов по ссылке разных сообщений с целевой страницы Веб-сайт.
Ссылка на сайт, используемая в моих сценариях, является заполнителем. К тому же они не динамичны. Так что кукловод может быть излишним. Тем не менее, Мое намерение состоит в том, чтобы изучить логику нажатия.
Когда я выполняю свой первый скрипт, он щелкает один раз и выдает следующую ошибку, когда выходит из источника.
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch({headless:false});
const [page] = await browser.pages();
await page.goto("https://stackoverflow.com/questions/tagged/web-scraping",{waitUntil:'networkidle2'});
await page.waitFor(".summary");
const sections = await page.$$(".summary");
for (const section of sections) {
await section.$eval(".question-hyperlink", el => el.click())
}
await browser.close();
})();
Ошибка, с которой сталкивается приведенный выше скрипт:
(node:9944) UnhandledPromiseRejectionWarning: Error: Execution context was destroyed, most likely because of a navigation.
Когда я выполняю следующее, скрипт делает вид, что щелкает один раз (на самом деле это не так) и сталкивается с той же ошибкой, что и раньше.
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch({headless:false});
const [page] = await browser.pages();
await page.goto("https://stackoverflow.com/questions/tagged/web-scraping");
await page.waitFor(".summary .question-hyperlink");
const sections = await page.$$(".summary .question-hyperlink");
for (let i=0, lngth = sections.length; i < lngth; i++) {
await sections[i].click();
}
await browser.close();
})();
Ошибка выше выдает:
(node:10128) UnhandledPromiseRejectionWarning: Error: Execution context was destroyed, most likely because of a navigation.
Как я могу позволить моему сценарию выполнять клики циклически?
Execution context was destroyed, most likely because of a navigation.
В ошибке говорится, что вы хотели щелкнуть какую-то ссылку или сделать что-то на какой-то странице, которой больше не существует, скорее всего, из-за того, что вы ушли.
Думайте о сценарии кукловода как о реальном человеке, просматривающем настоящую страницу.
Сначала мы загружаем URL-адрес (https://stackoverflow.com/questions/tagged/веб-скрейпинг).
Далее мы хотим просмотреть все вопросы, заданные на этой странице. Чтобы сделать это, что мы обычно делаем? Мы бы сделали одно из следующего,
Таким образом, оба они включают в себя уход от текущей страницы и возврат к ней.
Если вы не будете следовать этому потоку, вы получите сообщение об ошибке, как указано выше.
Есть по крайней мере 4 или более способов решить эту проблему. Я пойду с самыми простыми и сложными.
Сначала мы извлекаем все ссылки на текущей странице.
const links = await page.$$eval(".hyperlink", element => element.href);
Это дает нам список URL. Мы можем создать новую вкладку для каждой ссылки.
for(let link of links){
const newTab = await browser.newPage();
await newTab.goto(link);
// do the stuff
await newTab.close();
}
Это будет проходить по каждой ссылке по одной. Мы могли бы улучшить это, используя promise.map и различные библиотеки очередей, но вы поняли идею.
Нам нужно будет каким-то образом сохранить состояние, чтобы мы могли знать, какую ссылку мы посетили в прошлый раз. Если мы посетили третий вопрос и вернулись на страницу тегов, нам нужно посетить 4-й вопрос в следующий раз, и наоборот.
Проверьте следующий код.
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto(
`https://stackoverflow.com/questions/tagged/web-scraping?sort=newest&pagesize=15`
);
const visitLink = async (index = 0) => {
await page.waitFor("div.summary > h3 > a");
// extract the links to click, we need this every time
// because the context will be destryoed once we navigate
const links = await page.$$("div.summary > h3 > a");
// assuming there are 15 questions on one page,
// we will stop on 16th question, since that does not exist
if (links[index]) {
console.info("Clicking ", index);
await Promise.all([
// so, start with the first link
await page.evaluate(element => {
element.click();
}, links[index]),
// either make sure we are on the correct page due to navigation
await page.waitForNavigation(),
// or wait for the post data as well
await page.waitFor(".post-text")
]);
const currentPage = await page.title();
console.info(index, currentPage);
// go back and visit next link
await page.goBack({ waitUntil: "networkidle0" });
return visitLink(index + 1);
}
console.info("No links left to click");
};
await visitLink();
await browser.close();
})();
Обновлено: Есть несколько вопросов, похожих на этот. Я буду ссылаться на них, если вы захотите узнать больше.
Вместо того, чтобы циклически щелкать все ссылки, я считаю, что лучше проанализировать все ссылки, а затем перейти к каждой из них, используя один и тот же браузер. Дать ему шанс:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch({headless:false});
const [page] = await browser.pages();
const base = "https://stackoverflow.com"
await page.goto("https://stackoverflow.com/questions/tagged/web-scraping");
let links = [];
await page.waitFor(".summary .question-hyperlink");
const sections = await page.$$(".summary .question-hyperlink");
for (const section of sections) {
const clink = await page.evaluate(el=>el.getAttribute("href"), section);
links.push(`${base}${clink}`);
}
for (const link of links) {
await page.goto(link);
await page.waitFor('h1 > a');
}
await browser.close();
})();
Функция WaitFor() устарела, вы можете заменить ее функцией WaitForSelector().