使用🍄Puppeteer🍄爬取AI图片

最近发现了一个AI图片的网站,然后自己就下载了一些。碰巧这几天学习Puppeteer的知识,准备利用Puppeteer进行图片的爬取。

代码是根据浏览器插件-Headless Recorder生成的Puppeteer脚步进行修改的。

基础知识可参考

浏览器插件-Headless Recorder

Headless Recorder允许您记录与网站的交互并自动生成Puppeteer或Playwright脚本。它会捕捉您在浏览器中的操作,并将其转换为可用于自动化测试、网页抓取或其他任务的代码。

插件地址:Headless Recorder

使用Headless Recorder的步骤如下:

  1. 从Chrome网络商店安装Headless Recorder Chrome扩展程序。
  2. 在Chrome中打开您想要与之交互的网站。
  3. 单击Chrome工具栏中的Headless Recorder扩展程序图标以打开录制器。
  4. 在网站上执行您的交互操作,比如点击按钮、填写表单、在页面之间导航等。
  5. 单击Headless Recorder扩展程序中的"停止"按钮以停止录制。
  6. 录制的交互操作将被转换为Puppeteer或Playwright代码,您可以将其复制粘贴到您自己的脚本中。

Headless Recorder是一个非常有用的工具,可以快速创建自动化脚本,而无需手动编写所有代码。它可以在自动化重复任务或测试Web应用程序时节省您的时间和精力。

模拟图片下载

图片网站:天极图片

选择任意一张图片点进去,然后点击图片,点击下载图片按钮。

利用浏览器插件生成脚步:

js 复制代码
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch()
const page = await browser.newPage()
const navigationPromise = page.waitForNavigation()

await page.goto('https://pic.yesky.com/')

await page.setViewport({  width: 1366, height: 607})

await page.waitForSelector('.picList1 > .layout > li:nth-child(3) > .picBox > img')
await page.click('.picList1 > .layout > li:nth-child(3) > .picBox > img')

await navigationPromise

await page.waitForSelector('#atlas > .layout > .atlasSwiper > .bigPic > img')
await page.click('#atlas > .layout > .atlasSwiper > .bigPic > img')

await page.waitForSelector('body > .bigPic_fc > .swiper > .btns > .download')
await page.click('body > .bigPic_fc > .swiper > .btns > .download')

await browser.close()

这里我们先看如何下载图片里的一系列图片。

从上述代码,我们可以看到,下载图片的流程为:

  • 点击图片预览
  • 预览界面有下载按钮,点击下载按钮,图片即可下载。
  • 点击下一个,显示下一张图片,再次点击按钮即可。
  • 重复第三步,直到下载全部即可。

从上述流程中,我们需要解决一个问题:一共需求点击多少次下一页按钮,即这一页面总共包含多少张图片

查看页面,我们可以看到:

这样我们只需获取这个元素对应的值即可:

js 复制代码
  let total = await page1.$eval(
    ".atlas_information ul li span i",
    (el) => {
      let text = el.innerHTML;
      return text.split("张")[0];
    }
  );

这样我们就可以获取该页面总的图片数量。

那最后代码如下:

js 复制代码
const puppeteer = require("puppeteer");
// 谷歌浏览器地址
const CHROME_EXECUTALBE_PATH =
  "C:/Program Files/Google/Chrome/Application/chrome.exe";

const downloadImage = async (page, total) => {
  for (let i = 0; i < total; i++) {
    // 点击下载按钮
    await page.waitForSelector(
      "body > .bigPic_fc > .swiper > .btns > .download"
    );
    await page.click("body > .bigPic_fc > .swiper > .btns > .download");

    // 点击下一页按钮
    await page.waitForSelector(
      "body > .bigPic_fc > .swiper > .swiper-button-next"
    );
    await page.click("body > .bigPic_fc > .swiper > .swiper-button-next");
  }
};

(async () => {
  const browser = await puppeteer.launch({
    headless: false,
    defaultViewport: {
      width: 1366,
      height: 607,
    },
    args: ["--start-maximized"],
    executablePath: CHROME_EXECUTALBE_PATH, // 这个路径写上就可以执行了
  });

  const page = await browser.newPage();
  await page.goto("https://pic.yesky.com/307/2147478807.shtml");
  // 获取页面图片数量
  let total = await page.$eval(
    ".atlas_information > ul > li:nth-child(5) > span > i",
    (el) => {
      let text = el.innerHTML;
      return text.split("张")[0];
    }
  );

  // 点击图片预览
  await page.waitForSelector("#atlas > .layout > .atlasSwiper > .bigPic > img");
  await page.click("#atlas > .layout > .atlasSwiper > .bigPic > img");

  // 图片下载
  downloadImage(page, total);
  await browser.close();
})();

下载第一页的所有图片

上一章节,我们讲述了如何下载某一个图片的系列图片。这一章节主要讲述如何下载第一页的所有图片对应的图片系列。

这里我们可以获取第一页所有图片对应的页面链接:

js 复制代码
  const linkArr = await page.$$eval(
    ".list .classification_listContent .picName",
    (elements) => elements.map((element) => element.href)
  );

然后我们依次遍历即可:

js 复制代码
const puppeteer = require("puppeteer");
const CHROME_EXECUTALBE_PATH =
  "C:/Program Files/Google/Chrome/Application/chrome.exe";

const downloadImage = async (page, total) => {
  for (let i = 0; i < total; i++) {
    await page.waitForSelector(".bigPic_fc .swiper .btns .download");
    await page.click(".bigPic_fc .swiper .btns .download");

    await page.waitForSelector(".bigPic_fc .swiper .swiper-button-next");
    await page.click(".bigPic_fc .swiper .swiper-button-next");
    console.log("正在下载:", i + 1, "/", total);
     // 添加500ms的等待时间
    await new Promise((resolve) => setInterval(resolve, 500));
  }
};

(async () => {
  const browser = await puppeteer.launch({
    headless: false,
    defaultViewport: {
      width: 1366,
      height: 607,
    },
    args: ["--start-maximized"],
    executablePath: CHROME_EXECUTALBE_PATH, // 这个路径写上就可以执行了
  });

  const page = await browser.newPage();

  await page.goto("https://pic.yesky.com/c/6_25152.shtml");

  const linkArr = await page.$$eval(
    ".list .classification_listContent .picName",
    (elements) => elements.map((element) => element.href)
  );
  // 避免打开多个浏览器窗口
  let page1 = await browser.newPage();

  for (let link of linkArr) {
    await page1.goto(link);

    // 获取页面图片数量
    let total = await page1.$eval(
      ".atlas_information > ul > li:nth-child(5) > span > i",
      (el) => {
        let text = el.innerHTML;
        return text.split("张")[0];
      }
    );

    await page1.waitForSelector("#atlas .layout .atlasSwiper .bigPic img");
    await page1.click("#atlas .layout .atlasSwiper .bigPic img");
    await downloadImage(page1, total);
  }
  await browser.close();
})();

执行上述代码,即可下载第一页的所有图片。

下载某一系列的所有图片

上一章节我们讲述了如何下载第一页的所有图片,这一章节我们讲述如何下载某一系列的所有图片。

由于图片总页码不多,点击对应系列即可查询到该系列的总页数,然后我们依次遍历即可。

js 复制代码
const puppeteer = require("puppeteer");
const fs = require("fs");
const request = require("request");
const path = require("path");

const CHROME_EXECUTALBE_PATH =
  "C:/Program Files/Google/Chrome/Application/chrome.exe";

const downloadImage = async (page, total) => {
  for (let i = 0; i < total; i++) {
    // 点击下载按钮
    await page.waitForSelector(
      "body > .bigPic_fc > .swiper > .btns > .download"
    );
    await page.click("body > .bigPic_fc > .swiper > .btns > .download");

    // 点击下一页按钮
    await page.waitForSelector(
      "body > .bigPic_fc > .swiper > .swiper-button-next"
    );
    await page.click("body > .bigPic_fc > .swiper > .swiper-button-next");

    console.log("正在下载:", i + 1, "/", total);
    // 添加500ms的等待时间
    await new Promise((resolve) => setInterval(resolve, 500));
  }
};

(async () => {
  const browser = await puppeteer.launch({
    headless: false,
    defaultViewport: {
      width: 1366,
      height: 607,
    },
    args: ["--start-maximized"],
    executablePath: CHROME_EXECUTALBE_PATH, // 这个路径写上就可以执行了
  });

  const page = await browser.newPage();
  for (let pageIndex = 1; pageIndex < 11; pageIndex++) {
    await page.goto(`https://pic.yesky.com/c/6_25152_${pageIndex}.shtml`);

    const linkArr = await page.$$eval(
      ".list .classification_listContent .picName",
      (elements) => elements.map((element) => element.href)
    );
    let page1 = await browser.newPage();

    for (let link of linkArr) {
      await page1.goto(link);

      let total = await page1.$eval(".atlas_information ul li span i", (el) => {
        let text = el.innerHTML;
        return text.split("张")[0];
      });

      await page1.waitForSelector("#atlas .layout .atlasSwiper .bigPic img");
      await page1.click("#atlas .layout .atlasSwiper .bigPic img");

      await downloadImage(page1, total);
    }
  }

  await browser.close();
})();

其他系列也是同样的道理。

自定义文件名和存储路径

上面的代码是可以爬取所有的图片的,但是存在几个问题:

  • 点击下载按钮,文件名是英文的拼接,并且同一系列是没有任何关联的。
  • 文件存储位置是默认浏览器下载位置,无法自定义存储位置,这就导致所有的图片均下载在同一路径,一个文件夹包含近千张的图片。

而我们是想将同一系列的图片下载在同一个文件夹,文件夹命名为该系列的名称,并且图片的名称是系列名称+对应序号。

前面的代码我们是使用的是网站自带的下载功能。如果要实现上述的功能,但是如果使用网站自带的下载功能,是无法自定义文件名称的。

这里我们可以获取图片的对应地址,然后利用网络请求实现即可:

js 复制代码
const download = (url, filename, customPath) => {
  return new Promise((resolve, reject) => {
    const filePath = customPath ? `${customPath}/${filename}` : filename;
    request.head(url, function (err, res, body) {
      request(url)
        .pipe(fs.createWriteStream(filePath))
        .on("close", () => resolve(filePath))
        .on("error", reject);
    });
  });
};

然后我们要获取图片系列的名称,用作文件夹名称和文件名称:

js 复制代码
let title = await page1.$eval(
    "body > #atlas > .layout > h1",
    (elements) => elements.innerText
  );

但是在下载的时候,出现了一个问题:文件夹名称里面包含不允许的特殊符号,会导致图片下载中断。

这里我们就要做一下文件夹名称的特殊字段的过滤:

js 复制代码
// 文件夹名称的特殊字段过滤
const sanitizeFilename = (filename) => {
  return filename.replace(/[!@#$%^&*()+=\-[\]\\';,./{}|":<>?~_]/g, "_");
};

let rawTitle = await page1.$eval(
  "body > #atlas > .layout > h1",
  (elements) => elements.innerText
);
title = sanitizeFilename(rawTitle);

然后就剩最后一个功能,就是文件夹的建立:

js 复制代码
// 使用路径拼接创建目录
const directoryPath = path.join(__dirname, title);
fs.mkdir(directoryPath, () => {});

最后的完整代码如下:

js 复制代码
const puppeteer = require("puppeteer");
const fs = require("fs");
const request = require("request");
const path = require("path");

const CHROME_EXECUTALBE_PATH =
  "C:/Program Files/Google/Chrome/Application/chrome.exe";

const download = (url, filename, customPath) => {
  return new Promise((resolve, reject) => {
    const filePath = customPath ? `${customPath}/${filename}` : filename;
    request.head(url, function (err, res, body) {
      request(url)
        .pipe(fs.createWriteStream(filePath))
        .on("close", () => resolve(filePath))
        .on("error", reject);
    });
  });
};

const sanitizeFilename = (filename) => {
  return filename.replace(/[!@#$%^&*()+=\-[\]\\';,./{}|":<>?~_]/g, "_");
};

(async () => {
  const browser = await puppeteer.launch({
    headless: false,
    defaultViewport: {
      width: 1960,
      height: 1280,
    },
    args: ["--start-maximized"],
    executablePath: CHROME_EXECUTALBE_PATH, // 这个路径写上就可以执行了
  });

  const page = await browser.newPage();
  for (let pageIndex = 1; pageIndex < 11; pageIndex++) {
    await page.goto(`https://pic.yesky.com/c/6_25152_${pageIndex}.shtml`);

    const linkArr = await page.$$eval(
      ".list .classification_listContent .picName",
      (elements) => elements.map((element) => element.href)
    );
    let page1 = await browser.newPage();

    for (let link of linkArr) {
      await page1.goto(link);

      let total = await page1.$eval(".atlas_information ul li span i", (el) => {
        let text = el.innerHTML;
        return text.split("张")[0];
      });

      let rawTitle = await page1.$eval(
        "body > #atlas > .layout > h1",
        (elements) => elements.innerText
      );
      title = sanitizeFilename(rawTitle);
      console.log(title);

      await page1.waitForSelector("#atlas .layout .atlasSwiper .bigPic img");
      await page1.click("#atlas .layout .atlasSwiper .bigPic img");

      const imageArr = await page1.$$eval(
        "body > .bigPic_fc > .swiper > ul li img",
        (elements) =>
          elements.map((element) => {
            return element.src || element?.getAttribute("data-src");
          })
      );
      // 使用路径拼接创建目录
      const directoryPath = path.join(__dirname, title);
      fs.mkdir(directoryPath, () => {});

      let index = 1;

      for (let url of imageArr) {
        download(url, `${title}-${index}.png`, title);
        index += 1;
      }
      // await downloadImage(page1, total);
      await new Promise((resolve) =>
        setTimeout(resolve, Math.min(2000, 1000 * total))
      );
    }
  }

  await browser.close();
})();

结语

上述代码实现的时候,可以将headless: false,字段注释掉,就不用打开浏览器了。

其实puppeteer还有其他的很多功能,只是暂时没用到具体需求,或者有些需求是不方便公开的。这篇文章大家做做参考即可。

相关推荐
咖啡の猫1 小时前
Shell脚本-for循环应用案例
前端·chrome
百万蹄蹄向前冲3 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5814 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路4 小时前
GeoTools 读取影像元数据
前端
ssshooter5 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友5 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
Jerry5 小时前
Jetpack Compose 中的状态
前端
dae bal6 小时前
关于RSA和AES加密
前端·vue.js
柳杉6 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化
lynn8570_blog6 小时前
低端设备加载webp ANR
前端·算法