让chatgpt将html中的图片转为base64方法示例

目录
  • 前言
  • 思路
  • 实现过程
    • 递归读取指定目录下的所有 html 文件路径 htmlPaths
    • 获取每个 html 里面的图片 src
      • 获取 html 字符串的所有图片 src
      • 忽略注释的代码
      • 本身是 base64,则忽略
      • 忽略 Thymeleaf 语法
      • 结合起来,封装成一个函数
  • src 转 base64
  • 最后将新的 html 替换旧的 html
  • 性能优化
  • 总的代码
  • 总结

前言

故事要从我们公司的新官网说起,新官网是叫外包做的,前后端没有分离,对,你没听错,都到了 2023 年的今天,新项目依然是前后端混在一起,堆成一座屎山,然后通过模板渲染展示网页。

当然,对于非研发的,技术栈咋样都不重要,又不是不能用~

各位看官,听到上面的情况,是不是隐隐约约感觉到会有啥惊喜(惊吓)

哈,你来翻译翻译,什么叫惊喜?好,那我来翻译翻译,惊喜(bushi)就是自从新官网上线后,PV(页面访问量)下降了 50%。是的,你没看错,原因就是打开官网巨卡,一般需要 7~8s。

就单单请求个 html,就需要耗费 7s+的时间。

运营那边被老板亲切问候后,就跑过来找我们研发帮忙看问题,把情况说的特严重,唉,最终还不是得我们帮忙处理烂摊子

那没办法,我们就开始分析一通,啪的一下,很快呀,就找到了加载贼慢的原因:

  • 刚才所说的服务端响应慢,那这个交给后端去搞就好(这个是重中之重)
  • 官网的图片请求是在是太多了,而我们知道 http 请求是有次数限制的(http1.1),太多的话其他只能处于阻塞状态

那第二点自然是需要前端去搞了,图片太多,导致 http 请求太多,那好办,把小图片转 base64 不就好。 嗯,思路很简单,如果是前后端分离的项目,我们一般无脑配置 webpack url-loader 的体积限制就好,或者配置 webpack5 的 asset,即在导出一个 data URI 和发送一个单独的文件之间自动选择:

rules: [
    {
      test: /\.(png|jpe?g|svg|gif)$/,
      //webpack4 start
      use: [
        {
          loader: 'url-loader',
          options: { limit: 10 * 1024 },
        },
      ],
      //webpack4 end
      // webapack5 start
      type:'asset/inline',
      parser: {
        dataUrlCondition: {
          maxSize: 10 * 1024, // 小于10k则转为base64
        },
      },
      // webapack5 end
    },
  ],

很简单对吧,但当你想快速 cv 以上配置的时候,发现,前端代码都是混在后端代码里面,一堆 html 文件,html 里面又混杂着一堆的 Thymeleaf 语法(Thymeleaf 是一个跟 Velocity、FreeMarker 类似的模板引擎,它可以完全替代 JSP,JSP??都啥年代了)

越看越不对劲,正所谓理想很丰满,现实很骨感,唉,只能长叹一声。

但没办法,领导可不管用啥方式,只要有个方法把 html 里面图片小于某个指定值,如 10k,那就转 base64,让这些小图片不走 http 请求。

思路

那么,正常的路走不通(当然也有可能有其他更快捷的方式,只是比较赶,暂时想不到更好、更简单的方式实现),那就另辟蹊径。

全体流程如下:

1. mvn clean // marven 清空输出目录

2. mvn compile // marven编译

3. 将所有 html 的的小图片都转 base64

4. mvn package // marven 打包

以上主要重点关注第3点,既然要将所有 html 的的小图片都转 base64,那么自然而然可以通过写个 node 脚本来实现,大概可以分为以下几个步骤:

  • 递归读取指定目录下的所有 html 文件路径 htmlPaths,这里我们假设所有 html 都放在 backend/templates 里面
  • 遍历每个路径,读取对应的 html,然后通过正则匹配到每个 html 里面的所有 img 对应的 src,存放到 imgSrcs 里面
  • 遍历每个 img 的 src,读取 imageBuffer,并转为 base64
  • 再将得到的 base64 替换原先的 src
  • 最后将新的 html 替换旧的 html

上面的步骤应该比较清晰的,不过,等一下,最近 chatgpt 不是很火吗,让它来写不就好~

实现过程

递归读取指定目录下的所有 html 文件路径 htmlPaths

首先判断入口目录的下的内容,是文件的话就判断是否以.html 结尾,是的话则放入 htmlFilePaths,是目录的话则递归遍历,那我们来问神奇的 chatgpt:

嗯,很不错,第一次几乎完美,顺利拿到所有的 html 路径

获取每个 html 里面的图片 src

自然而然想到用正则匹配来做,所以我马上问:

一看结果就不是我想要的,当然也是我描述的不清晰,导致 chatgpt 以为要获取 html 文档的图片 src

获取 html 字符串的所有图片 src

应该问要获取 html 字符串的,所以我接着问:

// 定义匹配图片标签的正则表达式
var imgRegex = /<img\s+[^>]*src=["']([^"']*)["'][^>]*>/gi;
// 要解析的HTML字符串
var htmlString = '<img src="image1.jpg"> <img src="image2.jpg">';
// 遍历所有匹配到的图片标签并获取其src属性
var matches;
while ((matches = imgRegex.exec(htmlString)) !== null) {
  var src = matches[1];
  console.log(src);
}

运行后,得到的结果:

不错不错,再接再厉。

忽略注释的代码

但 html 代码可能 img 被注释了,如 <!-- <img src='xxx.jpg'> -->,那么我们实际上没必要去转换,故我们让个让其忽略注释的:

那我们来试试,将其中注释代码中插入 img,看看是否会解析:

// 定义匹配图片标签的正则表达式
var imgRegex = /<img\s+[^>]*src=["']([^"']*)["'][^>]*>/gi;
// 定义匹配注释的正则表达式
var commentRegex = /<!--[\s\S]*?-->/g;
// 要解析的HTML字符串
var htmlString = '<!-- <img src="image3.jpg"> --> <img src="image1.jpg"> <img src="image2.jpg">';
// 删除所有注释
htmlString = htmlString.replace(commentRegex, '');
// 遍历所有匹配到的图片标签并获取其src属性
var matches;
while ((matches = imgRegex.exec(htmlString)) !== null) {
  var src = matches[1];
  console.log(src);
}

可以看到被注释掉的 image3 不会被匹配到

本身是 base64,则忽略

继续继续,但图片 src 可能一开始就是 base64 了,就没必要转了,而 base64 是 data:image 开头的,所以我们再让它加下条件:

// 定义匹配图片标签的正则表达式
var imgRegex = /<img\s+[^>]*src=["']((?!data:image)[^"']*)["'][^>]*>/gi;
// 要解析的HTML字符串
var htmlString = '<img src="image1.jpg"> <img src="data:image/png;base64,iVBORw0KG..."> <img src="image2.jpg"><!-- <img src="image3.jpg"> -->';
// 遍历所有匹配到的不以 data:image 开头的图片标签并获取其src属性
var matches;
while ((matches = imgRegex.exec(htmlString)) !== null) {
  var src = matches[1];
  console.log(src);
}

我们运行看下结果

成功跳过了 base64 的,但是,好像没有了忽略注释代码的条件,啊这。。

所以我就让他忽略注释和 base64 的,但好像一直丢三落四,按下葫芦浮起瓢,大家可以看看

上面的注释的被匹配到了

注释又又又被匹配到了,算了,我直接问如果忽略 base64,然后再组合忽略注释的就好,组合后的代码如下:

const imgRegex = /<img\s+[^>]*src=["']((?!data:image)[^"']*)["'][^>]*>/gi;
// 定义匹配注释的正则表达式
const commentRegex = /<!--[\s\S]*?-->/g
// 删除所有注释
var htmlString = '<img src="image1.jpg"> <img src="data:image/png;base64,iVBORw0KG..."> <img src="image2.jpg"><!-- <img src="image3.jpg"> -->';
htmlString = htmlString.replace(commentRegex, '')
// 遍历所有匹配到的图片标签并获取其src属性
const imgSrcs = []
let matches;
while ((matches = imgRegex.exec(htmlString)) !== null) {
const src = matches[1];
imgSrcs.push(src)
}
console.log(imgSrcs)

忽略 Thymeleaf 语法

还有个问题,img 的 src 可能是通过服务端渲染导入的,那么我们要忽略掉,大致语法为 <img th:src="${t.imgUrl1}" />,也就是以 th:开头的

// 定义匹配图片标签的正则表达式
var imgRegex = /<img\s+[^>]*src=["']((?!data:image|@{)(?!\s*th:src)[^"']*)["'][^>]*>/gi;
// 要解析的HTML字符串
var htmlString = '<img src="image1.jpg"> <img src="@{some/url}"> <img src="data:image/png;base64,iVBORw0KG..."> <img src="image2.jpg" th:src="@{some/other/url}">';
// 遍历所有匹配到的不以 data:image、@{ 和包含 th:src 的图片标签并获取其src属性
var matches;
while ((matches = imgRegex.exec(htmlString)) !== null) {
  var src = matches[1];
  console.log(src);
}

上面的@{可以忽略,实际上也是属于 Thymeleaf 语法,屏蔽 th:src 即可

结合起来,封装成一个函数

function getImgSrcInHtml(htmlString) {
  /** 定义匹配图片标签的正则表达式
   * 1.src中不以data:image开头,即不以base64开头,没必要再转化了
   * 2.不是th:src
   * 3.忽略注释的代码
   * */
  const imgRegex = /<img\s+[^>th:]*src=["']((?!data:image)[^"']*)["'][^>]*>/gi;
  // 定义匹配注释的正则表达式
  const commentRegex = /<!--[\s\S]*?-->/g
  // 删除所有注释
  htmlString = htmlString.replace(commentRegex, '')
  // 遍历所有匹配到的图片标签并获取其src属性
  const imgSrcs = []
  let matches;
  while ((matches = imgRegex.exec(htmlString)) !== null) {
    const src = matches[1];
    imgSrcs.push(src)
  }
  return imgSrcs.filter(Boolean)
}

运行下,看下结果:

src 转 base64

自此,我们拿到所有 html 里面的 src 了,那么判断下是否小于指定 size,是的话转 base64

获取文件大小可通过 fs 的 statSync 来拿到对应文件的 size,如下

/** 获取文件大小 */
function getFileSize(filePath) {
  const stat = fs.statSync(filePath)
  return stat.size
}

如果满足条件则将图片转 base64,而图片本身是 Buffer,所以要转一下:

/** 图片转成base64 */
function imageToBase64(filePath) {
  // 读取图片文件
  const imageBuffer = fs.readFileSync(filePath)
  const extname = getExtname(filePath)
  // 将图片文件转换为 base64 编码字符串
  const base64String = Buffer.from(imageBuffer).toString('base64')
  return `data:image/${extname.slice(1)};base64,${base64String}`
}

通过加上前缀data:image/${extname.slice(1)};base64,,其中 extname 是文件后缀名,通过path.extname拿到:

/** 获取后缀名 */
function getExtname(filePath) {
  return path.extname(filePath)
}

每得到一个 base64,则替换原先的 src:

htmlString = htmlString.replace(src, imgBase64)

最后将新的 html 替换旧的 html

html 下满足条件的 src 全部替换好后,就可以将新的 html 替换老的了,实际上也就是重写回去:

writeFile(htmlPath, htmlString)

性能优化

但我们发现整个过程中有两处可以优化代码:

  • 如果 src 大于指定尺寸,那么下次遇到直接跳过,不再获取尺寸大小
  • 一个图片可能被多处引用到,那么转 base64 后,下次遇到就没必要再转了,直接复用即可

针对第一点,我们可以通过声明一个 Set,存放大于指定尺寸的 src:

for (const src of imgSrcs) {
      /** 之前已经大于了,这次遇到就直接跳过即可 */
      if (imgOverSizeSet.has(src)) continue
      let absoluteSrc = src
      // 如果不是相对路径,那么转换为绝对路径
      if (!src.startsWith('.')) absoluteSrc = path.join(STATIC_PATH, src)
      // 不存在或者超出限制,则不替换
      if (getFileSize(absoluteSrc) &gt;= FILE_LIMIE_SIZE) {
        imgNotExistOrOverSizeSet.add(src)
        continue
      }
}

针对第二点,我们可以通过声明一个 Map,key 为 src,value 为 base64:

/** 存imgSrc -&gt; 图片base64 */
const imgSrc2Base64Map = new Map()

每次判断到对应的 src 有值,则直接拿之前的 base64,不再转化:

let imgBase64 = imgSrc2Base64Map.get(src)
if (!imgBase64) {
  imgBase64 = imageToBase64(absoluteSrc)
  imgSrc2Base64Map.set(src, imgBase64)
}

总的代码

const fs = require('fs')
const path = require('path')
function resolve(relativePath) {
  return path.resolve(__dirname, relativePath)
}
/** 静态资源路径 */
const STATIC_PATH = resolve('xxx')
/** html模板路径 */
const TEMPLATE_PATH = resolve('yyy')
/** 文件大小限制 10K */
const FILE_LIMIE_SIZE = 1024 * 10
/** 图片转成base64 */
function imageToBase64(filePath) {
  // 读取图片文件
  const imageBuffer = fs.readFileSync(filePath)
  const extname = getExtname(filePath)
  // 将图片文件转换为 base64 编码字符串
  const base64String = Buffer.from(imageBuffer).toString('base64')
  return `data:image/${extname.slice(1)};base64,${base64String}`
}
/** 获取文件大小 */
function getFileSize(filePath) {
  const stat = fs.statSync(filePath)
  return stat.size
}
/** 获取后缀名 */
function getExtname(filePath) {
  return path.extname(filePath)
}
/** 获取所有html路径 */
function getHtmlPaths(dir, filePaths = []) {
  const files = fs.readdirSync(dir);
  for (const file of files) {
    const filePath = path.join(dir, file);
    const fileStat = fs.statSync(filePath);
    if (fileStat.isDirectory()) {
      getHtmlPaths(filePath, filePaths);
    } else if (fileStat.isFile() && getExtname(filePath) === '.html') {
      filePaths.push(filePath);
    }
  }
  return filePaths;
}
function readFile(filePath) {
  return fs.readFileSync(filePath, 'utf-8')
}
function writeFile(filePath, source) {
  return fs.writeFileSync(filePath, source)
}
/** 获取html中满足规则的img src */
function getImgSrcInHtml(htmlString) {
  /** 定义匹配图片标签的正则表达式
   * 1.src中不以data:image开头,即不以base64开头,没必要再转化了
   * 2.不是th:src
   * 3.忽略注释的代码
   * */
  const imgRegex = /<img\s+[^>th:]*src=["']((?!data:image)[^"']*)["'][^>]*>/gi;
  // 定义匹配注释的正则表达式
  const commentRegex = /<!--[\s\S]*?-->/g
  // 删除所有注释
  htmlString = htmlString.replace(commentRegex, '')
  // 遍历所有匹配到的图片标签并获取其src属性
  const imgSrcs = []
  let matches;
  while ((matches = imgRegex.exec(htmlString)) !== null) {
    const src = matches[1];
    imgSrcs.push(src)
  }
  return imgSrcs.filter(Boolean)
}
/** 主程序 */
function main() {
  const htmlPaths = getHtmlPaths(TEMPLATE_PATH)
  /** 存imgSrc -> 图片base64 */
  const imgSrc2Base64Map = new Map()
  /** 存不存在,或者超过指定大小的img */
  const imgNotExistOrOverSizeSet = new Set()
  htmlPaths.forEach(htmlPath => {
    let htmlString = readFile(htmlPath)
    const imgSrcs = getImgSrcInHtml(htmlString)
    if (!imgSrcs.length) return
    for (const src of imgSrcs) {
      if (imgNotExistOrOverSizeSet.has(src)) continue
      let absoluteSrc = src
      // console.log(imgSrcs)
      // 如果不是相对路径,那么转换为绝对路径
      if (!src.startsWith('.')) absoluteSrc = path.join(STATIC_PATH, src)
      const isExist = fs.existsSync(absoluteSrc)
      if (!isExist) console.log('not isExist', src)
      // 不存在或者超出限制,则不替换
      if (!isExist || getFileSize(absoluteSrc) >= FILE_LIMIE_SIZE) {
        imgNotExistOrOverSizeSet.add(src)
        continue
      }
      let imgBase64 = imgSrc2Base64Map.get(src)
      if (!imgBase64) {
        imgBase64 = imageToBase64(absoluteSrc)
        imgSrc2Base64Map.set(src, imgBase64)
      }
      htmlString = htmlString.replace(src, imgBase64)
    }
    // 替换好后,写回
    writeFile(htmlPath, htmlString)
  })
}
main()

总结

因为太多的小图片导致 http 请求阻塞,所以要把满足条件的小图片转为 base64, 而前端的 html 混在在后端代码里面,且里面混杂着 Thymeleaf 模板语法,想通过 webpack 打包的方式看起来好像不行(至少目前不知道咋办),所以退而求其之自己写个脚本来处理。

大致思路就是:

  • 读取所有 html,匹配到里面的所有 img 的 src
  • 匹配的过程中我们要忽略注释的代码、已经是 base64 的图片、Thymeleaf 模板语法的,涉及到挺多的正则语法,这部分我通过调戏 chatgpt 来实现,虽然过程中不是十分完美
  • src 可能被重复用到,所以这里又可以优化为:
    • 如果 src 大于指定尺寸,那么下次遇到直接跳过,不再获取尺寸大小
    • 一个图片可能被多处引用到,那么转 base64 后,下次遇到就没必要再转了,直接复用即可

以上就是让chatgpt将html中的图片转为base64方法示例的详细内容,更多关于chatgpt html图片转base64的资料请关注我们其它相关文章!

(0)

相关推荐

  • 让ChatGPT解读Vue3源码过程解析

    目录 前言 实战 setup 小结 总结 前言 ChatGPT 最近十分火爆,今天我也来让 ChatGPT 帮我阅读一下 Vue3 的源代码. 都知道 Vue3 组件有一个 setup函数.那么它内部做了什么呢,今天跟随 ChatGPT 来一探究竟. 实战 setup setup 函数在什么位置呢,我们不知道他的实现函数名称,于是问一下 ChatGPT: ChatGPT 告诉我,setup 函数在packages/runtime-core/src/component.ts 文件中.众所周知,ru

  • ChatGPT-4 震撼发布将释放更大生产力

    ChatGPT-4 发布 3月15日消息,美国当地时间周二,人工智能研究公司OpenAI发布了其下一代大型语言模型GPT-4,这是其支持ChatGPT和新必应等应用程序的最新AI大型语言模型.该公司表示,该模型在许多专业测试中的表现超出了“人类水平”.GPT-4, 相较于ChatGPT(GPT-3.5)有了质的飞跃,这迭代速度太快了!总体来说,逻辑推理能力更强,语言能力更强,各种考试已经基本超过90%的人类! OpenAI声称,ChatGPT-4比迭代前的GPT-3.5更大,这意味着它接受了更多

  • ChatGPT在前端领域的初步探索

    目录 什么是ChatGPT ChatGPT优势 体验流程 辅助开发 ChatGPT的现状与未来 总结 什么是ChatGPT 官方解释:ChatGPT是一个智能聊天机器人,来自于OpenAI,它能够使用人工智能技术进行对话,并回答用户提出的问题和请求.它由GPT(Generative Pre-trained Transformer)算法支持,可以模拟人类对话和回答各种问题,包括日常生活.科技.娱乐.健康.财经等领域.ChatGPT可以通过各种渠道进行访问,如网站.社交媒体或移动应用程序. Chat

  • Django 实现将图片转为Base64,然后使用json传输

    最近使用Django来进行图像的传输,由于要求需要使用Json格式进行请求,所以我们尝试了二进制编码放在json里,发现bytes格式不能打入json,于是转为了base64 将图片转为json 图片转为json有2中方法,一个是二进制后再转,一个是转为矩阵以后再转. import base64 import cv2 #通过opencv转base64 img_im= cv2.imread("D://32.png") aa=base64.b64encode(cv2.imencode('.

  • webpack图片转为base64的实现示例

    下载url-loader yarn add -D url-loader module: { rules: [ { test: /\.(jpeg|jpg|png|svg|gif)$/, use: { loader: 'url-loader', // 默认使用的是es6模块 options: { // 配置 esModule: false, // 使用common.js规范 outputPath: 'images', // 输出的文件目录 name: 'images/[contenthash:4].

  • C#操作数据库中存取图片文件的方法

    本文实例讲述了C#操作数据库中存取图片文件的方法.分享给大家供大家参考.具体如下: private string sqlconnstr = "Data Source=.;Database=db_test;User id=sa;PWD=123456"; /*功能:把一种图片插入到数据库中 *返回值:无 */ void InsertImageToDB() { //将需要存储的图片读取为数据流 FileStream fs = new FileStream(@"D:/Bear.jpg

  • Python读取excel中的图片完美解决方法

    excel中有图片是很常见的,但是通过python读取excel中的图片没有很好的解决办法. 网上找了一种很聪明的方法,原理是这样的: 1.将待读取的excel文件后缀名改成zip,变成压缩文件. 2.再解压这个文件. 3.在解压后的文件夹中,就有excel中的图片. 4.这样读excel中的图片,就变成了读文件夹中的图片了,和普通文件一样,可以做各种处理. 解压后的压缩包如下: python脚本如下: ''' File Name: readexcelimg Author: tim Date:

  • java将图片转为base64返回给前端

    本文实例为大家分享了java将图片转为base64返回给前端的具体代码,供大家参考,具体内容如下 一.controller端代码 @RequestMapping(value = "/captcha") public void imagecode(HttpServletRequest request, HttpServletResponse response) throws Exception { JSONObject object = new JSONObject(); Captcha

  • oracle中decode函数的使用方法示例

    decode的几种用法 1:使用decode判断字符串是否一样 DECODE(value,if1,then1,if2,then2,if3,then3,...,else) 含义为 IF 条件=值1 THEN RETURN(value 1) ELSIF 条件=值2 THEN RETURN(value 2) ...... ELSIF 条件=值n THEN RETURN(value 3) ELSE RETURN(default) END IF sql测试 select empno,decode(empn

  • Java中生成唯一ID的方法示例

    有时我们不依赖于数据库中自动递增的字段产生唯一ID,比如多表同一字段需要统一一个唯一ID,这时就需要用程序来生成一个唯一的全局ID. UUID 从Java 5开始, UUID 类提供了一种生成唯一ID的简单方法.UUID是通用唯一识别码 (Universally Unique Identifier)的缩写,UUID来源于OSF(Open Software Foundation,开源软件基金会)的DCE(Distributed Computing Environment,分布式计算环境)规范.UU

  • C语言中可变参数的使用方法示例

    前言 在C语言程序编写中我们使用最多的函数一定包括printf以及很多类似的变形体.这个函数包含在C库函数中,定义为 int printf( const char* format, ...); 除了一个格式化字符串之外还可以输入多个可变参量,如: printf("%d",i); printf("%s",s); printf("the number is %d ,string is:%s", i, s); 格式化字符串的判断本章暂且不论,下面分析一

  • 在SpringBoot项目中的使用Swagger的方法示例

    一. 首先Swagger是什么? Swagger 是一个规范和完整的框架,用于生成.描述.调用和可视化 RESTful 风格的 Web 服务.总体目标是使客户端和文件系统作为服务器以同样的速度来更新.文件的方法,参数和模型紧密集成到服务器端的代码,允许API来始终保持同步.Swagger官方API文档:https://swagger.io/ 作用:   1. 接口的文档在线自动生成.   2. 功能测试. Swagger的主见介绍:    Swagger Codegen: 通过Codegen 可

  • MyBatis-Plus中如何使用ResultMap的方法示例

    目录 问题说明 解决方法 自定义@AutoResultMap注解 MyBatis-Plus (简称MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发.提高效率而生. MyBatis-Plus对MyBatis基本零侵入,完全可以与MyBatis混合使用,这点很赞. 在涉及到关系型数据库增删查改的业务时,我比较喜欢用MyBatis-Plus,开发效率极高.具体的使用可以参考官网,或者自己上手摸索感受一下. 下面简单总结一下在MyBatis-Plus中如何使用R

随机推荐