【AutoTinyPng】从程序员的角度来压缩图片

简介: 【AutoTinyPng】从程序员的角度来压缩图片

前言:


说来很奇怪,现在的不少技术交流群里面存在这一些“伪程序员”,就比如说下图的这段对话,用在线的图片压缩网站要对自己的大量图片进行压缩,居然嫌麻烦都跑群里面问要怎么办?


从程序员的角度来解决这个问题:
  1. 上班摸鱼法: 一张一张来,干一张算一张。
  2. 土豪氪金法: 通过网站开放的API进行简单编程进行批量处理,当然你处理的越多就需要支付一些费用。
  3. 展示技术法: 适合在合理的数量内,难得的机会中复习一下你的编程知识,还能把活干好。
  4. 其他: 。。。

1.png


打码前的准备:

  1. 我们选择展示技术法来做今天的Demo,我也觉得这是一个程序员的选择(丢给美工的事我。。。);
  2. 一款产品的质量也是需要逐渐进行打磨优化,tinypng在程序员中间还是流传的较为好用的一款产品,我们依然选择tinypng,用别人专业的工具做自己的事,漂亮!。


思路介绍:

  1. 递归获取本地文件夹里的文件
  2. 过滤文件,格式必须是.jpg .png,大小小于5MB.(文件夹递归)
  3. 每次只处理一个文件(可以绕过20个的数量限制)
  4. 处理返回数据拿到远程优化图片地址
  5. 取回图片更新本地图片
  6. 纯node实现不依赖任何其他代码片段


打码实现:

仅适用Node提供的模块:
const fs = require("fs");
const { Console } = require("console");
const path = require("path");
const https = require("https");
const URL = require("url").URL;
复制代码

通用浏览器标识,防止同一标识被服务器拦截:
const USER_AGENT = [
  "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
  "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv,2.0.1) Gecko/20100101 Firefox/4.0.1",
  "Mozilla/5.0 (Windows NT 6.1; rv,2.0.1) Gecko/20100101 Firefox/4.0.1",
  "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11",
  "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11",
  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
  "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)",
  "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; maxthon 2.0)",
  "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SE 2.X MetaSr 1.0; SE 2.X MetaSr 1.0; .NET CLR 2.0.50727; SE 2.X MetaSr 1.0)",
];
复制代码

定义log,支持输出日志到文件:
// 定义log,支持输出日志到文件
class Log {
  options = {
    flags: "a", // append模式
    encoding: "utf8", // utf8编码
  };
  logger = {};
  /**
   * 初始化打印配置
   */
  constructor() {
    this.logger = new Console({
      stdout: fs.createWriteStream("./log.tinypng.stdout.log", this.options),
      stderr: fs.createWriteStream("./log.tinypng.stderr.log", this.options),
    });
  }
  /**
   * log级别
   * @param {*} message 输出信息
   */
  log(message) {
    if (message) {
      this.logger.log(message);
      console.log(message);
    }
  }
  /**
   * error级别
   * @param {*} message 输出err信息
   */
  error(message) {
    if (message) {
      this.logger.error(message);
      console.error(message);
    }
  }
}
// 实例化Log对象
const Tlog = new Log();
复制代码

定义TinyPng对象:
class TinyPng {
  // 配置信息: 后缀格式和最大文件大小受接收限制不允许调整
  config = {
    files: [],
    entryFolder: "./",
    deepLoop: false,
    extension: [".jpg", ".png"],
    max: 5200000, // 5MB == 5242848.754299136
    min: 100000, // 100KB
  };
  // 成功处理计数
  successCount = 0;
  // 失败处理计数
  failCount = 0;
  /**
   * TinyPng 构造器
   * @param {*} entry 入口文件
   * @param {*} deep 是否递归
   */
  constructor(entry, deep) {
    console.log(USER_AGENT[Math.floor(Math.random() * 10)]);
    if (entry != undefined) {
      this.config.entryFolder = entry;
    }
    if (deep != undefined) {
      this.config.deepLoop = deep;
    }
    // 过滤传入入口目录中符合调整的待处理文件
    this.fileFilter(this.config.entryFolder);
    Tlog.log(`本次执行脚本的配置:`);
    Object.keys(this.config).forEach((key) => {
      if (key !== "files") {
        Tlog.log(`配置${key}:${this.config[key]}`);
      }
    });
    Tlog.log(`等待处理文件的数量:${this.config.files.length}`);
  }
  /**
   * 执行压缩
   */
  compress() {
    Tlog.log("启动图像压缩,请稍等...");
    let asyncAll = [];
    if (this.config.files.length > 0) {
      this.config.files.forEach((img) => {
        asyncAll.push(this.fileUpload(img));
      });
      Promise.all(asyncAll)
        .then(() => {
          Tlog.log(
            `处理完毕: 成功: ${this.successCount}张, 成功率${
              this.successCount / this.config.files.length
            }`
          );
        })
        .catch((error) => {
          Tlog.error(error);
        });
    }
  }
  /**
   * 过滤待处理文件夹,得到待处理文件列表
   * @param {*} folder 待处理文件夹
   * @param {*} files 待处理文件列表
   */
  fileFilter(folder) {
    // 读取文件夹
    fs.readdirSync(folder).forEach((file) => {
      let fullFilePath = path.join(folder, file);
      // 读取文件信息
      let fileStat = fs.statSync(fullFilePath);
      // 过滤文件安全性/大小限制/后缀名
      if (
        fileStat.size <= this.config.max &&
        fileStat.size >= this.config.min &&
        fileStat.isFile() &&
        this.config.extension.includes(path.extname(file))
      ) {
        this.config.files.push(fullFilePath);
      }
      // 是都要深度递归处理文件夹
      else if (this.config.deepLoop && fileStat.isDirectory()) {
        this.fileFilter(fullFilePath);
      }
    });
  }
  /**
   * TinyPng 远程压缩 HTTPS 请求的配置生成方法
   */
  getAjaxOptions() {
    return {
      method: "POST",
      hostname: "tinypng.com",
      path: "/web/shrink",
      headers: {
        rejectUnauthorized: false,
        "X-Forwarded-For": Array(4)
          .fill(1)
          .map(() => parseInt(Math.random() * 254 + 1))
          .join("."),
        "Postman-Token": Date.now(),
        "Cache-Control": "no-cache",
        "Content-Type": "application/x-www-form-urlencoded",
        "User-Agent": USER_AGENT[Math.floor(Math.random() * 10)],
      },
    };
  }
  /**
   * TinyPng 远程压缩 HTTPS 请求
   * @param {string} img 待处理的文件
   * @success {
   *              "input": { "size": 887, "type": "image/png" },
   *              "output": { "size": 785, "type": "image/png", "width": 81, "height": 81, "ratio": 0.885, "url": "https://tinypng.com/web/output/7aztz90nq5p9545zch8gjzqg5ubdatd6" }
   *           }
   * @error  {"error": "Bad request", "message" : "Request is invalid"}
   */
  fileUpload(imgPath) {
    return new Promise((resolve) => {
      let req = https.request(this.getAjaxOptions(), (res) => {
        res.on("data", async (buf) => {
          let obj = JSON.parse(buf.toString());
          if (obj.error) {
            Tlog.log(`压缩失败!\n 当前文件:${imgPath} \n ${obj.message}`);
          } else {
            resolve(await this.fileUpdate(imgPath, obj));
          }
        });
      });
      req.write(fs.readFileSync(imgPath), "binary");
      req.on("error", (e) => {
        Tlog.log(`请求错误! \n 当前文件:${imgPath} \n, ${e}`);
      });
      req.end();
    }).catch((error) => {
      Tlog.log(error);
    });
  }
  // 该方法被循环调用,请求图片数据
  fileUpdate(entryImgPath, obj) {
    return new Promise((resolve) => {
      let options = new URL(obj.output.url);
      let req = https.request(options, (res) => {
        let body = "";
        res.setEncoding("binary");
        res.on("data", (data) => (body += data));
        res.on("end", () => {
          fs.writeFile(entryImgPath, body, "binary", (err) => {
            if (err) {
              Tlog.log(err);
            } else {
              this.successCount++;
              let message = `压缩成功 : 优化比例: ${(
                (1 - obj.output.ratio) *
                100
              ).toFixed(2)}% ,原始大小: ${(obj.input.size / 1024).toFixed(
                2
              )}KB ,压缩大小: ${(obj.output.size / 1024).toFixed(
                2
              )}KB ,文件:${entryImgPath}`;
              Tlog.log(message);
              resolve(message);
            }
          });
        });
      });
      req.on("error", (e) => {
        Tlog.log(e);
      });
      req.end();
    }).catch((error) => {
      Tlog.log(error);
    });
  }
}
module.exports = TinyPng;
复制代码

入口脚本:
/**
 * 因网络原因和第三方接口防刷等技术限制导致部分图像处理失败
 */
const TinyPng = require("./tinypng.compress.img");
function getEntryPath() {
  let i = process.argv.findIndex((i) => i === "-p");
  if (process.argv[i + 1]) {
    return process.argv[i + 1];
  }
}
new TinyPng(getEntryPath(), true).compress();
复制代码

执行演示:

2.png


日志记录:

3.png


结语:

程序员还是要将重复的工作简单化,几年前就靠这份脚本将150多M的前端项目压到了20~30兆,你会想着说怎么还能有这样的项目,你们项目很大么?说实话就是不规范导致的,多年积累的文件你要一张张去处理你觉得靠谱么,你刚压缩完其他同事又提交了一堆大图片怎么办,那么最好将脚本改一下再加入到编译时的插件中,完美!



相关文章
|
1天前
|
弹性计算 运维 搜索推荐
三翼鸟携手阿里云ECS g9i:智慧家庭场景的效能革命与未来生活新范式
三翼鸟是海尔智家旗下全球首个智慧家庭场景品牌,致力于提供覆盖衣、食、住、娱的一站式全场景解决方案。截至2025年,服务近1亿家庭,连接设备超5000万台。面对高并发、低延迟与稳定性挑战,全面升级为阿里云ECS g9i实例,实现连接能力提升40%、故障率下降90%、响应速度提升至120ms以内,成本降低20%,推动智慧家庭体验全面跃迁。
|
2天前
|
数据采集 人工智能 自然语言处理
3分钟采集134篇AI文章!深度解析如何通过云无影AgentBay实现25倍并发 + LlamaIndex智能推荐
结合阿里云无影 AgentBay 云端并发采集与 LlamaIndex 智能分析,3分钟高效抓取134篇 AI Agent 文章,实现 AI 推荐、智能问答与知识沉淀,打造从数据获取到价值提炼的完整闭环。
339 90
|
9天前
|
人工智能 自然语言处理 前端开发
Qoder全栈开发实战指南:开启AI驱动的下一代编程范式
Qoder是阿里巴巴于2025年发布的AI编程平台,首创“智能代理式编程”,支持自然语言驱动的全栈开发。通过仓库级理解、多智能体协同与云端沙箱执行,实现从需求到上线的端到端自动化,大幅提升研发效率,重塑程序员角色,引领AI原生开发新范式。
806 156
|
2天前
|
数据采集 缓存 数据可视化
Android 无侵入式数据采集:从手动埋点到字节码插桩的演进之路
本文深入探讨Android无侵入式埋点技术,通过AOP与字节码插桩(如ASM)实现数据采集自动化,彻底解耦业务代码与埋点逻辑。涵盖页面浏览、点击事件自动追踪及注解驱动的半自动化方案,提升数据质量与研发效率,助力团队迈向高效、稳定的智能化埋点体系。(238字)
243 156
|
3天前
|
域名解析 人工智能
【实操攻略】手把手教学,免费领取.CN域名
即日起至2025年12月31日,购买万小智AI建站或云·企业官网,每单可免费领1个.CN域名首年!跟我了解领取攻略吧~
|
10天前
|
机器人 API 调度
基于 DMS Dify+Notebook+Airflow 实现 Agent 的一站式开发
本文提出“DMS Dify + Notebook + Airflow”三位一体架构,解决 Dify 在代码执行与定时调度上的局限。通过 Notebook 扩展 Python 环境,Airflow实现任务调度,构建可扩展、可运维的企业级智能 Agent 系统,提升大模型应用的工程化能力。
|
人工智能 前端开发 API
前端接入通义千问(Qwen)API:5 分钟实现你的 AI 问答助手
本文介绍如何在5分钟内通过前端接入通义千问(Qwen)API,快速打造一个AI问答助手。涵盖API配置、界面设计、流式响应、历史管理、错误重试等核心功能,并提供安全与性能优化建议,助你轻松集成智能对话能力到前端应用中。
787 154