从零开发一款自动提取网页html并一键转换为md文件的工具(vue源码版)

优采云 发布时间: 2022-05-08 09:42

  从零开发一款自动提取网页html并一键转换为md文件的工具(vue源码版)

  

  最近几年涌现出了很多技术博客和技术社区, 也有很多技术同仁开始打造自己的博客, 我们可以把自己的博客同步到不同的技术平台, 但是随着技术平台的增多, 我们文章同步所花费的时间会越来越多, 那么有没有一个工具能快速的将博客发布到不同的平台呢? 或者有没有一个工具, 可以把html直接转化为技术平台能够识别的“语言”直接发布呢?

  我们都知道程序员最爱的写博客的“语言”就是makedown, 并且目前大部分的技术社区都支持makedown语法, 所以说只要有makedown, 我们就能快速的同步到不同的技术平台.

  也许有人会说, 我们写博客直接用makedown语法写不就好了? 的确这样做可以满足需求, 但缺点就是我们本地必须要保存一份makedown文件, 如果博客内容涉及到图片, 我们还需要维护一个img目录, 这样每次在不同技术社区发布文章还是会很麻烦, 所以综上我们开发了一款自动爬取html内容并一键转换为makedown的工具, 这样我们就可以“肆无忌惮的”发布博客了.

  你将收获

  github地址笔者将在文末附上, 感兴趣的朋友可以一起共建, 学习和探索.

  效果演示

  

  客户端思路

  先理一下思路:

  

  为什么选择turndown

  客户端最重要的一步是html转md,这里我们使用的 turndown。

  为什么使用turndown呢,原因如下:

  具体实现

    // 引入第三方插件<br />  import { gfm, tables, strikethrough } from 'turndown-plugin-gfm'<br /><br />  const turndownService = new TurndownService({ codeBlockStyle: 'fenced' })<br />  // Use the gfm plugin<br />  turndownService.use(gfm)<br /><br />  // Use the table and strikethrough plugins only<br />  turndownService.use([tables, strikethrough])<br /><br />  /**<br />   * 自定义配置(rule名不能重复)<br />   * 这里我们指定 `pre` 标签为代码块,并在代码块的前后加个换行,防止显示异常<br />   */<br />  turndownService.addRule('pre2Code', {<br />    filter: ['pre'],<br />    replacement (content) {<br />      return '```\n' + content + '\n```'<br />    }<br />  })<br />

  额外功能

  支持自动获取链接文章标题,无需手动去原文复制。

  服务端

  这里我们使用的服务端是node.js,用前端的框架写服务端,体验杠杠的。

  思路

  先理一下思路:

  

  具体实现获取前端传递的链接地址

  这里直接使用node的自带语法,我们采用的是get形式传递,用query即可

  const qUrl = req.query.url<br />

  通过请求获取html串

  这里我们是用request进行请求

   request({<br />   url: qUrl<br /> }, (error, response, body) => {<br />   if (error) {<br />     res.status(404).send('Url Error')<br />     return<br />   }<br />   // 这里的 body 就是文章的 `html`<br />   console.log(body)<br /> })<br />

  根据不同平台域名获取不同的dom

  由于技术平台众多,每个平台的文章内容标签、样式名或 id 会有差异,需要针对兼容。

  首先先用js-dom去模拟操作dom,封装一个方法

   /**<br />  * 获取准确的文章内容<br />  * @param {string} html html串<br />  * @param {string} selector css选择器<br />  * @return {string} htmlContent<br />  */<br /> const getDom = (html, selector) => {<br />   const dom = new JSDOM(html)<br />   const htmlContent = dom.window.document.querySelector(selector)<br />   return htmlContent<br /> }<br />

  兼容不同的平台,应用不同的 css 选择器

   // 比如掘金,内容块的样式名为 .markdown-body,内容里会有 style 标签样式和一些多余的复制代码文字,通过原生 dom 操作删掉<br /> if (qUrl.includes('juejin.cn')) {<br />   const htmlContent = getBySelector('.markdown-body')<br />   const extraDom = htmlContent.querySelector('style')<br />   const extraDomArr = htmlContent.querySelectorAll('.copy-code-btn')<br />   extraDom && extraDom.remove()<br />   extraDomArr.length > 0 && extraDomArr.forEach((v) => { v.remove() })<br />   return htmlContent<br /> }<br /><br /> // 再比如 oschina,内容块的样式名为 .article-detail,内容里会有多余的 .ad-wrap 内容,照样删掉<br /> if (qUrl.includes('oschina.net')) {<br />   const htmlContent = getBySelector('.article-detail')<br />   const extraDom = htmlContent.querySelector('.ad-wrap')<br />   extraDom && extraDom.remove()<br />   return htmlContent<br /> }<br /><br /> // 最后匹配通用标签。优先适配 article 标签,没有再用 body 标签<br /> const htmlArticle = getBySelector('article')<br /> if (htmlArticle) { return htmlArticle }<br /><br /> const htmlBody = getBySelector('body')<br /> if (htmlBody) { return htmlBody }<br />

  转换图片和链接的相对路径为绝对路径,方便以后查找源路径

   // 通过原生api - URL 获取链接的源域名<br /> const qOrigin = new URL(qUrl).origin || ''<br /><br /> // 获取图片、链接的绝对路径。通过 URL 将 `路径+源域名` 转换为绝对路径,不熟悉的同学请自行了解<br /> const getAbsoluteUrl = p => new URL(p, qOrigin).href<br /><br /> // 转换图片、链接的相对路径,不同平台的图片懒加载属性名不一样,需要做特定兼容<br /> const changeRelativeUrl = (dom) => {<br />   if (!dom) { return '内容出错~' }<br />   const copyDom = dom<br />   // 获取所有图片<br />   const imgs = copyDom.querySelectorAll('img')<br />   // 获取所有链接<br />   const links = copyDom.querySelectorAll('a')<br />   // 替换完所有路径返回新 dom<br />   imgs.length > 0 && imgs.forEach((v) => {<br />     /**<br />     * 处理懒加载路径<br />     * 简书:data-original-src<br />     * 掘金:data-src<br />     * segmentfault:data-src<br />     */<br />     const src = v.src || v.getAttribute('data-src') || v.getAttribute('data-original-src') || ''<br />     v.src = getAbsoluteUrl(src)<br />   })<br />   links.length > 0 && links.forEach((v) => {<br />     const href = v.href || qUrl<br />     v.href = getAbsoluteUrl(href)<br />   })<br />   return copyDom<br /> }<br /><br /> // 在获取不同平台的文章内容 getBody 方法里,应用 changeRelativeUrl 方法<br /> const getBody = (content) => {<br />   ...<br />   ...<br />   return changeRelativeUrl(htmlContent)<br /> }<br />

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线