用网络刮擦释放数据金矿
#node #webscraping #puppetter

数据是新的石油,为了从数字沙漠中提取油,我们可以使用废料,这意味着从网页中提取数据,并且有多种方法可以实现这一目标。有许多受欢迎的套餐,例如硒,砂纸,美丽的汤等。

但是在这一集中,我们将使用 puppeteer 在节点Eco系统中取消数据,它为我们提供了与用户友好的API,可以与网页交互。

另一个因刮擦而闻名的软件包是Cheerio,但我不会使用它,因为它具有一定的局限性,并且对所有开发人员都不那么用户友好,因为它使用jQuery语法来过滤元素,也不会在CSR(客户端渲染)网站上工作。

制作提取器的书房

让我们从准备我们的编码操场开始。创建一个带有有趣名称的文件夹,例如“ Scrape-Book”。接下来,让我们启动我们的项目:只需键入npm init -y。现在,要召唤Puppeteer,请输入NPM I Puppeteer。轻松!

现在,让我们通过创建一个名为“ Scrape.js”的文件来准备代码。 voilã!

mkdir scrape-book
cd scrape-book
npm init -y
npm i puppeteer
touch scrape.js

在这一集中,我将刮擦https://books.toscrape.com的书籍

现在让我们导入木偶

现在我将制作一个函数scrapeBooks,该函数将使用一个参数链接。

在此功能中,让我们初始化浏览器并打开带有给定链接的新页面。

const puppeteer = require("puppeteer");
const scrapeBooks = async (link) => {
    const browser = await puppeteer.launch({headless: false}); //pass "new" for headless
    const page = await browser.newPage();
    await page.setViewport({ width: 1366, height: 768, deviceScaleFactor: 1 });
    await page.goto(link);

    await browser.close();
};

快速提示:可以在puppeteer.launch中传递一个“无头”选项,这意味着不会打开浏览器的GUI版本。当我们在本地开发时,这很好,以便我们可以看到正在发生的事情。您可以将其设置为false或使用“新”选项。

和超时挑战! Puppeteer给我们30秒以打开页面。如果花费太长时间,我们可以通过跳过图像和不必要的内容来加快速度。如果页面上的脚本很喜欢,让我们休息一下。

//abort the request for image, css and javascript file
page.on('request', (request) => {
        if (request.resourceType() === 'image' || request.resourceType() === 'stylesheet' || request.resourceType() === 'script') {
            request.abort();
        } else {
            request.continue();
        }
    });

好吧,让我们继续。

现在,我们可以在页面中执行一些查询,因为我将在该页面上执行另一个称为script的函数,该函数将在该页面上执行,该函数刚刚打开,因此将其视为前端环境,并且不要尝试包含任何nodejs模块在此功能中。

它不起作用,因为它的环境不同,这就是为什么我创建了一个单独的功能以将其分开。

现在抓住页面上的所有产品元素并运行一个循环以提取我们需要的所有信息。

我要将所有这些都存储在数组中,然后将其返回。

const script = () => {
    let allProductsNode = document.querySelectorAll(".product_pod");

    let allProducts = [];

    allProductsNode.forEach((p) => {
        let title = p.querySelector("h3 a").innerHTML;
        let price = p.querySelector(".price_color").innerHTML;
        let availability = p
            .querySelector(".instock.availability")
            .textContent.trim();
        let imageUrl = p.querySelector("img").src;
        let bookLink =
            `https://books.toscrape.com/catalogue/` +
            p.querySelector(".image_container a").getAttribute("href");
        allProducts.push({
            title,
            price,
            availability,
            imageUrl,
            bookLink,
        });
    });
    return allProducts;
};

我们还必须更新我们的Scrapebooks功能,我们将等到页面上的产品类可用,然后使用page.evaluate运行该脚本。

    //wait until products class is available
    await page.waitForSelector(".product_pod")

    //excecute script to grab all the data
    let allBooks = await page.evaluate(script);

在网站上有1000本书,每页20本书,因此我们可以循环浏览所有页面,我们将获得1000本书的所有详细信息。

让我们编写另一个scrape函数,该功能将从每个页面中提取数据。

我只需从1到50运行一个循环并调用Scrapebooks函数,而且我还需要一个全局数组来存储所有这些。

抓住所有数据后,我们可以将其存储在文件中,以便我们可以使用FS模块轻松完成此操作。

因此,只需导入FS模块并创建一个新文件books.json

 await fs.writeFile("./books.json", JSON.stringify(data), "utf-8");

我们的书对象看起来像这样 -

Book Object Json

而不是将其写入文件中,您也可以将这些数据直接存储在数据库中。

注意: - 有时将所有数据存储在单个数组中不是一个好主意,该数组将占据很多内存,因此您也可以在每次迭代中写入文件,然后您可以将该文件修改为单个数组。

现在整个代码看起来像这样: -

const puppeteer = require("puppeteer");
const fs = require("fs/promises");
let data = [];
const script = () => {
    let allProductsNode = document.querySelectorAll(".product_pod");

    let allProducts = [];

    allProductsNode.forEach((p) => {
        let title = p.querySelector("h3 a").innerHTML;
        let price = p.querySelector(".price_color").innerHTML;
        let availability = p
            .querySelector(".instock.availability")
            .textContent.trim();
        let imageUrl = p.querySelector("img").src;
        let bookLink =
            `https://books.toscrape.com/catalogue/` +
            p.querySelector(".image_container a").getAttribute("href");
        allProducts.push({
            title,
            price,
            availability,
            imageUrl,
            bookLink,
        });
    });
    return allProducts;
};

const scrapeBooks = async (link) => {
    const browser = await puppeteer.launch({ headless: false }); //pass "new" for headless
    const page = await browser.newPage();
    await page.setViewport({ width: 1600, height: 1000, deviceScaleFactor: 1 });
    await page.goto(link);

    //wait until products class is available
    await page.waitForSelector(".product_pod");

    //excecute script to grab all the data
    let allBooks = await page.evaluate(script);

    data = [...data, ...allBooks];

    await browser.close();
};

async function scrap() {
    try {
        for (let i = 1; i <= 50; i++) {
            await scrapeBooks(`https://books.toscrape.com/catalogue/page-${i}.html`);
        }

        await fs.writeFile("./books.json", JSON.stringify(data), "utf-8");
    } catch (error) {
        console.log(error)
    }
}

scrap();

如果您有任何建议或问题,请在评论部分中告诉我。