无法在page.evaluate()中使用外部函数

问题描述 投票:0回答:1

我正在用 puppeteer 抓取动态网站。我的目标是能够创建尽可能多的通用抓取逻辑,这也将删除大量样板代码。因此,出于这个原因,我创建了外部函数,在给定某些参数的情况下抓取数据。问题当我尝试在 page.evaluate() puppeteer 方法中使用该函数时,我遇到了一个 ReferenceError ,即该函数未定义。

做了一些研究, page.exposeFunction() 和 page.addScriptTag() 作为可能的解决方案出现。然而,当我尝试在我的 scraper 中使用它们时,addScriptTag() 不起作用,并且 hideFunction() 没有让我能够访问公开函数中的 DOM 元素。我知道 hideFunction() 是在 Node.js 内部执行的,而 addScriptTag() 在浏览器中执行的,但我不知道如何进一步处理这些信息,也不知道它对我的情况是否有价值。

这是我的刮刀:

import { Browser } from "puppeteer";

import { dataMapper } from "../../utils/api/functions/data-mapper.js";

export const mainCategoryScraper = async (browser: Browser) => {
  const [page] = await browser.pages();

  await page.setUserAgent(
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
  );

  await page.setRequestInterception(true);

  page.on("request", (req) => {
    if (
      req.resourceType() === "stylesheet" ||
      req.resourceType() === "font" ||
      req.resourceType() === "image"
    ) {
      req.abort();
    } else {
      req.continue();
    }
  });

  await page.goto("https://www.ozone.bg/pazeli-2d-3d/nastolni-igri", {
    waitUntil: "domcontentloaded",
  });

  /**
   * Function will execute in Node.js
   */
  // await page.exposeFunction('dataMapper', dataMapper);

  /**
   * The way of passing DOM elements to the function, because like that the function executes in the browser
   */
  // await page.addScriptTag({ content: `${dataMapper}` });

  const data = await page.evaluate(async () => {
    const contentContainer = document.querySelector(".col-main") as HTMLDivElement;

    const carousels = Array.from(
      contentContainer.querySelectorAll(".owl-item") as NodeListOf<HTMLDivElement>
    );

    const carouselsData = await dataMapper<HTMLDivElement>(carousels, ".title", "img", "a");

    return {
      carouselsData,
    };
  });
  await browser.close();

  return data;
};

这是 dataMapper 函数:

import { PossibleTags } from "../typescript/types.js";

export const dataMapper = function <T extends HTMLDivElement>(items: Array<T>, ...selectors: string[]) {
  let hasTitle = false;

  for (const selector of selectors) {
    if (selector === ".title" || selector === "h3") {
      hasTitle = true;
      break;
    }
  }
  
  return items.map((item) => {
    const data: PossibleTags = {};

    return selectors.map((selector) => {
        
      const dataProp = item.querySelector(selector);

      switch (selector) {
        case ".title": {
          data["title"] = (dataProp as HTMLSpanElement)?.innerText;
          break;
        }
        case "h3": {
          data["title"] = (dataProp as HTMLHeadingElement)?.innerText;
          break;
        }
        case "h6": {
          data["subTitle"] = (dataProp as HTMLHeadingElement)?.innerText;
          break;
        }
        case "img": {
          if (!hasTitle) {
            data["img"] = (dataProp as HTMLImageElement)?.getAttribute("src") ?? undefined;
            break;
          }

          data["title"] = (dataProp as HTMLImageElement)?.getAttribute("alt") ?? undefined;
          break;
        }
        case "a": {
          data["url"] = (dataProp as HTMLAnchorElement)?.getAttribute("href") ?? undefined;
        }
        default: {
          throw new Error("Such selector is not yet added to the possible selectors");
        }
      }
    });
  });
};

当我使用

page.exposeFunction('dataMapper', dataMapper);
时,它告诉我 item.querySelector 不是一个函数(在 dataMapper 内部)。对于
await page.addScriptTag({ content: `${dataMapper}` });
,它只是稍后在页面内抛出错误。评估,dataMapper 不是一个函数。

更新:在 addScriptTag 内指定路径时,它仍然给我:

Error [ReferenceError]: dataMapper is not defined
* 需要提一下的是 mainCategoryScraper * 稍后会在 scrapersHandler 函数中使用,该函数根据 URL 端点决定要执行的 scraper。

javascript node.js typescript web-scraping puppeteer
1个回答
0
投票

正如我在评论中讨论的那样,这里的方法似乎相当复杂。我警告不要过早抽象。 只需抓取数据即可:

const puppeteer = require("puppeteer"); // ^22.7.1 const url = "<Your URL>"; let browser; (async () => { browser = await puppeteer.launch(); const [page] = await browser.pages(); const ua = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"; await page.setUserAgent(ua); await page.setRequestInterception(true); const blockedResources = [ "stylesheet", "image", "other", "fetch", "xhr", "ping", ]; page.on("request", req => { if ( !req.url().startsWith("https://www.ozone.bg") || blockedResources.includes(req.resourceType()) ) { req.abort(); } else { req.continue(); } }); await page.goto(url, {waitUntil: "domcontentloaded"}); await page.waitForSelector(".owl-item"); const data = await page.$$eval(".owl-item", els => els.map(el => ({ title: el.querySelector(".title")?.textContent, img: el.querySelector("img").src, url: el.querySelector("a").href, oldPrice: el .querySelector(".old-price .price") ?.textContent.trim(), specialPrice: el .querySelector(".special-price .price") ?.textContent.trim(), })) ); console.log(data); })() .catch(err => console.error(err)) .finally(() => browser?.close());

如果你想使其通用,可能有比 
addScriptTag

更好的方法(

exposeFunction
通常对于提取数据没有用处,因为你无法从中访问 DOM)。首先,共享一个页面,该页面激发了 h6 和您需要处理的其他变体的存在,然后考虑提取适当的抽象,并可能在单个
$$eval
调用中完成所有工作。有一些技术可以做到这一点,但它们取决于用例。
    

© www.soinside.com 2019 - 2024. All rights reserved.