使用🍄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还有其他的很多功能,只是暂时没用到具体需求,或者有些需求是不方便公开的。这篇文章大家做做参考即可。

相关推荐
racerun2 分钟前
vue VueResource & axios
前端·javascript·vue.js
J总裁的小芒果19 分钟前
THREE.js 入门(六) 纹理、uv坐标
开发语言·javascript·uv
m0_5485147719 分钟前
前端Pako.js 压缩解压库 与 Java 的 zlib 压缩与解压 的互通实现
java·前端·javascript
AndrewPerfect20 分钟前
xss csrf怎么预防?
前端·xss·csrf
Calm55023 分钟前
Vue3:uv-upload图片上传
前端·vue.js
浮游本尊27 分钟前
Nginx配置:如何在一个域名下运行两个网站
前端·javascript
m0_7482398328 分钟前
前端bug调试
前端·bug
m0_7482329230 分钟前
[项目][boost搜索引擎#4] cpp-httplib使用 log.hpp 前端 测试及总结
前端·搜索引擎
新中地GIS开发老师35 分钟前
《Vue进阶教程》(12)ref的实现详细教程
前端·javascript·vue.js·arcgis·前端框架·地理信息科学·地信
m0_7482495438 分钟前
前端:base64的作用
前端