分类
工具

使用 site-validator 验证网站链接

网站死链的问题比较常见,坏处就不多说了,大家应该都明白。想解决这个问题并不容易,主要难点在发现死链。如果是纯静态网站就比较简单,直接查服务器的 404 日志即可;如果是动态的,比如 SPA,就比较难搞。

所以我做了这个项目:site-validator。其实非常简单,用 puppeteer 访问网站,找出所有链接,一级一级点下去,直到把一个网站的链接都点一遍为止。记录下所有的死链,输出,然后排查。

代码简单,但是确实很实用,帮我厂找到不少死链,很好的提升了搜索表现。将来考虑再加一些新功能进去,比如集成 lighthouse 审计之类的。有兴趣的同学可以下载使用,也欢迎提意见和 issue 哦。

分类
工具

给 Hexo 增加替换大图并生成缩略图的功能

我厂的官方博客使用 Hexo 搭建,静态页比较符合我厂的技术风格。

我厂博客可能会用到一些很大的火焰图 SVG,厂长担心打开速度会受拖累,所以让我想办法把大图替换成缩略图,单击再打开原图。

经过一些研究,我大概得到以下线索:

  1. Hexo 会加载 `/scripts` 下面的脚本
  2. Hexo 是用钩子机制,跟 WordPress 非常像
  3. Hexo 的钩子函数可以使用 Promise

那么,大概方案就出来了:

  1. 添加脚本,等待 `after_post_render` 钩子。这个钩子触发的时候,博文已经从 markdown 渲染成 html。
  2. 使用 cheerio,找到所有图片,检查图片大小,略过不太大的图片(我的标准是 500K)
  3. 对大图生成缩略图,替换 `src`。

因为目标是 SVG => PNG/JPG,在 npm 里搜了一圈,发现大家不是 phantom.js 就是 puppeteer,那我就不需要用库了,自己直接写好了。

经过反复的尝试,最终完成的代码如下:

hexo.extend.filter.register('after_post_render', async function (data) {
  /** 摘录、详情、内容 */
  const dispose = ['excerpt', 'more', 'content'];
  for (const key of dispose) {
    const $ = cheerio.load(data[key], {
      ignoreWhitespace: false,
      xmlMode: false,
      lowerCaseTags: false,
      decodeEntities: false
    });

    // 获取所有需要调整大小的图片路径
    let images = $('img').map(async function () {
      let src = $(this).attr('src');
      if (!src) {
        if (config.log) {
          console.info("no src attr, skipped...");
          console.info($(this));
        }
        return;
      }

      // 顺便加上 `loading="lazy"`
      $(this).attr('loading', 'lazy');

      // take snapshot fo big SVGs
      const originalSrc = src;
      if (/\.svg$/.test(src)) {
        const img = resolve(__dirname, '../source', originalSrc);
        const {size} = await stat(img);
        if (size <= config.limit) {
          return;
        }
        $(this)
          .attr('data-src', src)
          .attr('src', `${src}.png`);
        return img;
      }
    }).get();
    images = await Promise.all(images);
    images = images.filter(image => !!image);
    // 只启动一次 pupppeteer,希望减少系统消耗
    if (images.length > 0) {
      const browser = await puppeteer.launch({
        defaultViewport: config,
      });
      const page = await browser.newPage();
      for (const image of images) {
        await page.goto(`file://${image}`);
        const buffer = await page.screenshot();
        const name = basename(image);
        console.log('screenshot for svg: ' + name);
        // 这一步很重要,因为 `after_post_render` 的时候图片还没有复制,所以要利用 hexo 自身的复制功能复制图片
        route.set(`images/${name}.png`, buffer);
      }
      await browser.close();
    }
    data[key] = $.html();
  }
  return data;
});

这段代码里,搞清楚应该用 route.set() 稍微花了些时间,Hexo 的文档写的真是差强人意。这个方法可以向指定位置写入内容,写入的可以是文本,也可以是二进制 buffer。如果写入的是绝对路径,那么就是简单的写入;如果是相对路径,就是向博文环境内写入,接下来的复制过程也会复制到最终代码里。

分类
js

Puppeteer 笔记

记录使用 Puppeteer 的一些经验。

安装使用

puppeteer 是一个“库”,没有自带的命令行功能。所以要使用的话必须写一个文件,然后实现对应的功能。

npm i puppeteer

在 WSL 下使用

因为 WSL 的环境比较特殊,直接使用会报错:

No usable sandbox! Update your kernel or see https://chromium.googlesource.com/chromium/src/+/master/docs/linux_suid_sandbox_development.md for more information on developing with the SUID sandbox. If you want to live dangerously and need an immediate workaround, you can try using –no-sandbox.

所以需要禁用 sandbox:

const browser = await puppeteer.launch({
  args: ['--no-sandbox', '--disable-setuid-sandbox'],
});

试用后抓图成功,汉字变成方框,可能跟我 WSL 里的字体配置有关,先不管了。

关于 sandbox,可以看这个文档,我其实也不太了解……

我的测试仓库和工具

参见 GitHub puppeteer-tool