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

相关推荐
HEX9CF13 分钟前
【CTF Web】Pikachu xss之href输出 Writeup(GET请求+反射型XSS+javascript:伪协议绕过)
开发语言·前端·javascript·安全·网络安全·ecmascript·xss
凌云行者25 分钟前
使用rust写一个Web服务器——单线程版本
服务器·前端·rust
华农第一蒟蒻41 分钟前
Java中JWT(JSON Web Token)的运用
java·前端·spring boot·json·token
积水成江42 分钟前
关于Generator,async 和 await的介绍
前端·javascript·vue.js
Z3r4y43 分钟前
【Web】portswigger 服务端原型污染 labs 全解
javascript·web安全·nodejs·原型链污染·wp·portswigger
___Dream44 分钟前
【黑马软件测试三】web功能测试、抓包
前端·功能测试
金灰44 分钟前
CSS3练习--电商web
前端·css·css3
人生の三重奏1 小时前
前端——js补充
开发语言·前端·javascript
Tandy12356_1 小时前
js逆向——webpack实战案例(一)
前端·javascript·安全·webpack
TonyH20021 小时前
webpack 4 的 30 个步骤构建 react 开发环境
前端·css·react.js·webpack·postcss·打包