Навигация по странице с помощью puppeteer

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, которая отслеживает количество раз, которое следует нажать на кнопку, кажется, что цикл выполняет правильное количество раз, но событие щелчка соответствующим образом не повторяется.

Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
1
0
61
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Похоже, что кнопка «Далее» удаляется и заменяется после каждого щелчка, в результате чего старый результат запроса становится устаревшим (удаляется из документа). Попробуйте повторно запрашивать его при каждом клике.

Вот пример, который также немного подчищает логику и очищает все данные:

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.

Другие вопросы по теме