下载Markdown文件网络图片到本地

850 阅读3分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

背景:有时候网络上下载的md文件,图片的地址往往是网路图片,并且还无法正常显示。且无法适用于无网络的情况。为此通过脚本工具,批量爬取md文件中的网络地址图片,并同步修改md文件中的图片索引地址。同时为了通用性,将node脚本打包成exe文件,其他人也能使用。

步骤:

1.解析出md文件中的网络图片地址。

2.下载文件。

3.替换文件中的网络地址

4.脚本打包成exe。

5.优化,添加部分交互和统计。

详细:

1.解析出md文件中的网络图片地址

let urlList = str.match(/(?<=(![image.png]))(.*)/g); // 这里只匹配image.png格式
 
    if (Array.isArray(urlList) && urlList.length > 0) {
      urlList = urlList.filter(
        (url) => url.indexOf("https") > -1 || url.indexOf("http") > -1
      );
    } else {
      return;
    }

2.下载文件

/**
 * @description 文件下载和保存
 * @param {*} url 地址
 * @param {*} name 文件名
 */
function load(url, name) {
  https.get(url, function (res) {
    var imgData = "";
    res.setEncoding("binary"); //一定要设置response的编码为binary否则会下载下来的图片打不开
    res.on("data", function (chunk) {
      imgData += chunk;
    });
    res.on("end", function () {
      if (imgData) {
        console.log("下载成功!", url);
        var path = `../file/images/${name}`;
        console.log("文件路径", path);
        fs.writeFileSync(path, imgData, "binary");
        success += 1;
      } else {
        console.log("下载失败!,图片路径不存在!");
        fail += 1;
      }
      if (images === success + fail) {
        exit();
      }
    });
  });

3.替换文件中的网络地址

urlList.forEach((url) => {
  if (url.indexOf("https") > -1 || url.indexOf("http") > -1) {
    const name = url.slice(url.lastIndexOf("/") + 1, url.indexOf("#"));
    str = str.replace(url, `(images/${name})`);
  }
});

上步下载将图片放在了images文件路径下,这里改为相对路径

4.脚本打包成exe

使用的是pkg包,这个包可以把node项目打包成exe执行文件,优点是打包的文件体积较小,但是只支持js文件,初始下载cache会比较慢或者容易出错。

// 全局安装pkg
npm install -g pkg

// 进入你的项目目录
cd project

// 执行打包exe文件,其中index.js就是你的入口文件
pkg -t win index.js

// 如果你需要支持win/linux/mac多个平台的话,就执行这一句
pkg index.js

5.优化,添加部分交互和统计

使用的是node交互工具inquirer,作为cmd交互不失为一种比较好的方式。

 console.log(`图片总数:${images},成功:${success},失败:${fail}`);
const promptList = [
    {
      type: "confirm",
      message: "执行完毕,是否退出?",
      name: "exit",
    },
  ];

  await inquirer.prompt(promptList).then((answers) => {
    console.log('退出成功'); // 返回的结果
  });

其他:因为这里图片的下载、文件的读写都是异步的,所以这里要处理好异步的问题。

全部代码参考:

const fs = require("fs");
const path = require("path");
var https = require("https");
const inquirer = require("inquirer");

let success = 0,
  fail = 0,
  images = 0;

  /**
   * @description 文md文件读取和url解析
   * @returns 
   */
const start = () => {
  try {
    // 读取文件内容
    const buff = fs.readFileSync("../file/index.md");
    let str = buff.toString();
    let urlList = str.match(/(?<=(![image.png]))(.*)/g);

    if (Array.isArray(urlList) && urlList.length > 0) {
      urlList = urlList.filter(
        (url) => url.indexOf("https") > -1 || url.indexOf("http") > -1
      );
    } else {
      return;
    }
    images = urlList.length;
    // 批量下载
    urlList.forEach((url) => {
      const urlGet = url.slice(1, url.indexOf("#"));
      const name = urlGet.slice(urlGet.lastIndexOf("/") + 1);
      load(urlGet, name);
    });
    // 替换图片路径
    urlList.forEach((url) => {
      if (url.indexOf("https") > -1 || url.indexOf("http") > -1) {
        const name = url.slice(url.lastIndexOf("/") + 1, url.indexOf("#"));
        str = str.replace(url, `(images/${name})`);
      }
    });
    fs.writeFileSync("../file/index.new.md", str);
  } catch (e) {
    console.log(e);
    exit();
  }
};

/**
 * @description 文件下载和保存
 * @param {*} url 地址
 * @param {*} name 文件名
 */
function load(url, name) {
  https.get(url, function (res) {
    var imgData = "";
    res.setEncoding("binary"); // 设置response的编码为binary否则会下载下来的图片打不开
    res.on("data", function (chunk) {
      imgData += chunk;
    });
    res.on("end", function () {
      if (imgData) {
        console.log("下载成功!", url);
        var path = `../file/images/${name}`;
        console.log("文件路径", path);
        fs.writeFileSync(path, imgData, "binary");
        success += 1;
      } else {
        console.log("下载失败!,图片路径不存在!");
        fail += 1;
      }
      // 判断是否已经全部下载
      if (images === success + fail) {
        exit();
      }
    });
  });
}

setTimeout(() => {
  start();
}, 1000);

/**
 * @description 退出,这里阻塞直接退出,便于用户直接看到下载成果而不是闪退
 */
async function exit() {
  console.log(`图片总数:${images},成功:${success},失败:${fail}`);
  const promptList = [
    {
      type: "confirm",
      message: "执行完毕,是否退出?",
      name: "exit",
    },
  ];

  await inquirer.prompt(promptList).then((answers) => {
    console.log('退出成功'); // 返回的结果
  });
}

总结:

1.这里的文件爬取不算是难点,比较难的点是pkg打包和交互优化。pkg打包一开始是初始化文件下载失败,因为外网环境不稳定,其次是打包成的exe执行完就闪退,无论正确与错误。这给排查造成了困难。

2.交互的方式,目前研究可以有两种,一种是使用inquirer 的cmd交互方式,一种是把脚本写成后端服务,再写一个前端的页面请求服务即可。再不济可以通过文件配置的方式,读取配置来执行脚本。