使用 puppeteer 提高网页抓取速度

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

我正在尝试创建一个 Node.js API,用于抓取网站(我一开始只使用 Goodreads 作为要抓取的网站,并且当 我第一次优化该方法时会进一步扩展 并将抓取的数据提供给最终用户,将使用我的 API。

我最初的方法是规划API结构,决定使用puppeteer,然后开始创建。成功创建第一个端点时,我注意到一些事情 - 在 Postman 中大约需要 2-3 秒才能完成请求,这非常慢。

这是我的代码:

scraper-handler.ts

import { NextFunction, Request, Response } from "express";
import { MOST_POPULAR_LISTS } from "../utils/api/urls-endpoints.js";
import { listScraper } from "./spec-scrapers/list-scraper.js";
import { lists } from "../utils/api/full-urls.js";
import puppeteer from "puppeteer";
import { GOODREADS_POPULAR_LISTS_URL } from "../utils/goodreads/urls.js";

export const scraperHandler = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  const browser = await puppeteer.launch({
    // headless: false,
    // defaultViewport: null,
  });

  const pages = await browser.pages();

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

  switch (req.url) {
    case `/${MOST_POPULAR_LISTS}`: {
      const result = await listScraper(
        browser,
        pages[0],
        GOODREADS_POPULAR_LISTS_URL,
        1,
        ".cell",
        ".listTitle",
        ".listTitle"
      );

      res.status(200).json({
        status: "success",
        data: result,
      });
      break;
    }
    default: {
      next();
      break;
    }
  }
};

这是

case /${MOST_POPULAR_LISTS}:

list-scraper.ts

import puppeteer, { Page } from "puppeteer";
import { Browser } from "puppeteer";

export const listScraper = async (
  browser: Browser,
  page: Page,
  url: string,
  pageI = 1,
  main: string,
  title = "",
  ref = ""
)  => {
  // const page = await browser.newPage();

  await page.goto(url, {
    waitUntil: "domcontentloaded",
  });
  
  const books = await page.evaluate(
    (mainSelector, titleSelector, refSelector) => {
      // const nextLink = document.querySelector('a[rel="next"]');

      // console.log(nextLink);
      const elements = document.querySelectorAll(mainSelector);
      
      return Array.from(elements)
        .slice(0, 3)
        .map((element) => {
          const title =
            titleSelector.length > 0 &&
            (element.querySelector(titleSelector) as HTMLElement | null)
              ?.innerText;
          const ref =
            refSelector.length > 0 &&
            (element.querySelector(refSelector) as HTMLAnchorElement | null)
              ?.href;

          return { title, ref };
        });
    },
    main,
    title,
    ref
  );
  // await page.click(".pagination > a");

  await browser.close();

  return books;
};

导入的变量值并不那么重要。

所以我的问题是如何优化我的方法以及可以使用哪些技术,以使抓取速度更快,从而大幅提高 API 的性能?

我搜索了各种帖子,许多帖子建议对 CPU 进行某种操作,但我不明白如何在我的案例中使用它。 Node.js 中的子进程也被建议了好几次。

提前谢谢您!

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

可以考虑一些事情来提高速度。

  1. 我看到的第一个主要痛点是在每个请求上创建一个新的
    puppeteer
    实例。我们可以按照单例模式创建一个模块。
// browser_instance.ts
import puppeteer from 'puppeteer';

let browserInstance = null;

export async function getBrowser() {
    if (!browserInstance) {
        browserInstance = await puppeteer.launch({ ... });
    }
    return browserInstance;
}

然后在您的代码中,尝试获取实例

import { getBrowser } from './browser-instance.js';
....
export const scraperHandler = async (req: Request, res: Response, next: NextFunction) => {
    const browser = await getBrowser();
    // ... use the browser instance for scraping
};
  1. 考虑使用
    Promise.all
    等待所有请求以并发方式完成。
const results = await Promise.all(listUrls.map(async (url) => {
    const page = await browser.newPage();
    // ... scraping logic for each page ...
}));
  1. 如果数据变化不大,第二种方法是缓存您已经访问过的现有网站。

您还可以进行更多优化。考虑研究如何找到应用程序中的慢点,并研究如何优化流程。

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