import puppeteer from "puppeteer";
export const getNigeriaPortsLineups = async () => {
let browser;
try {
browser = await puppeteer.launch({
ignoreHTTPSErrors: true,
headless: false,
timeout: 60000,
});
const width = 1920,
height = 1080;
const page = await browser.newPage();
await page.setViewport({ width, height });
await page.setUserAgent(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36"
);
await page.goto("https://shippos.nigerianports.gov.ng/", {
timeout: 60000,
});
await page.evaluate(async () => {
let nextButton = document.querySelector("#Apapa2_next") as HTMLElement;
const tableRows = document.querySelectorAll("#Apapa2 tbody tr");
const sugarRows = Array.from(tableRows).map((row) => {
return Array.from(row.querySelectorAll("td")).map((cell) =>
cell.textContent.trim()
);
});
let count = 0;
while (count <= 4) {
console.info(count);
await new Promise((resolve) => setTimeout(resolve, 2000));
nextButton.click()
count++;
}
// while (nextButton && !nextButton.classList.contains("disabled")) {
// nextButton.click();
// await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait for a second before clicking again
// }
});
} catch (error) {
console.error(error);
} finally {
// if (browser) await browser.close();
}
};
Я столкнулся с проблемой в Puppeteer при попытке перейти по нескольким страницам таблицы, нажав кнопку «Далее». Несмотря на наличие цикла для многократного нажатия кнопки, событие щелчка происходит только один раз. Несмотря на регистрацию переменной count, которая отслеживает количество раз, которое следует нажать на кнопку, кажется, что цикл выполняет правильное количество раз, но событие щелчка соответствующим образом не повторяется.






Похоже, что кнопка «Далее» удаляется и заменяется после каждого щелчка, в результате чего старый результат запроса становится устаревшим (удаляется из документа). Попробуйте повторно запрашивать его при каждом клике.
Вот пример, который также немного подчищает логику и очищает все данные:
const puppeteer = require("puppeteer"); // ^22.2.0
const url = "<Your URL>";
let browser;
(async () => {
browser = await puppeteer.launch();
const [page] = await browser.pages();
await page.goto(url, {
waitUntil: "domcontentloaded",
timeout: 120_000,
});
await page.waitForSelector("#Apapa2_next");
const data = await page.evaluate(() => {
const data = [];
for (let i = 0; i < 1000; i++) {
data.push(
...[...document.querySelectorAll("#Apapa2 tbody tr")].map(row =>
[...row.querySelectorAll("td")].map(cell =>
cell.textContent.trim()
)
)
);
const nextBtn = document.querySelector("#Apapa2_next");
if (nextBtn && !nextBtn.classList.contains("disabled")) {
nextBtn.click();
}
else {
break;
}
}
return data;
});
console.table(data);
})()
.catch(err => console.error(err))
.finally(() => browser?.close());
Теперь данные, похоже, доступны в статическом HTML, поэтому я бы использовал для этого fetch и Cheerio, а не Puppeteer:
const cheerio = require("cheerio"); // ^1.0.0-rc.12
const url = "<Your URL>";
fetch(url)
.then(res => {
if (!res.ok) {
throw Error(res.statusText);
}
return res.text();
})
.then(html => {
const $ = cheerio.load(html);
const data = [...$("#Apapa2 tbody tr")].map(e =>
[...$(e).find("td")].map(e => $(e).text().trim())
);
console.table(data);
})
.catch(err => console.error(err));
Вот быстрый тест на моей машине:
Кукольник:
real 0m20.965s
user 0m2.222s
sys 0m0.802s
Приветствую:
real 0m5.061s
user 0m0.547s
sys 0m0.042s
Fetch + Cheerio здесь уничтожает Puppeteer по всем параметрам (проще код, быстрее). Один из способов вернуть Puppeteer некоторую респектабельность — заблокировать JS и запросы и просто сосредоточиться на получении данных из необработанного HTML-ответа:
const puppeteer = require("puppeteer");
const url = "<Your URL>";
let browser;
(async () => {
browser = await puppeteer.launch();
const [page] = await browser.pages();
await page.setJavaScriptEnabled(false);
await page.setRequestInterception(true);
page.on("request", req =>
req.url() === url ? req.continue() : req.abort()
);
await page.goto(url, {waitUntil: "domcontentloaded"});
const data = await page.$$eval("#Apapa2 tbody tr", els =>
els.map(row =>
[...row.querySelectorAll("td")].map(cell =>
cell.textContent.trim()
)
)
);
console.table(data);
})()
.catch(err => console.error(err))
.finally(() => browser?.close());
Время:
real 0m5.047s
user 0m0.839s
sys 0m0.230s
Намного лучше. Быстрее и не нужно беспокоиться о нажатии кнопок. Это может быть удобно, если ваши запросы на выборку заблокированы.
Если вам интересно узнать больше о методах, используемых здесь для ускорения и упрощения скрипта (не спать, использовать domcontentloaded, блокировать JS и ресурсы, избегать манипуляций с DOM, использовать Fetch и Cheerio вместо Puppeteer), они подробно описаны в моем пост в блоге об антипаттернах Puppeteer.