从零开发一款自动提取网页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 />