对于前端同窗来讲其实作的更多的事情就是把数据整合好,按照UI
同窗的设计经过后端同窗给的数据展现在网页中,这也就致使了不少人认为前端很简单,没有作什么工做也没有什么后端复杂的业务逻辑。html
其实否则前端要作的工做有不少,就好比今天要说的,如何作到数据的反爬,笔者最近也接到了相同的任务,公司的数据频繁被爬虫爬走,出现这个状况以后,而后就开始调研如何才能实现前端业务数据的反爬工做。刚刚开始接到这个需求的时候,不知道该如何处理这件事,只能硬着头皮接下了这个任务。前端
接到任务以后就开始了各类Google
,笔者以为若是想要作到反爬就要先知道什么是爬虫以及爬虫是如何做业。这就比如咱们要足够的了解对手才能知道如何去防护。node
爬虫是经过发送http
请求获取获取到响应后的内容,经过按照必定的规则,自动爬去网页信息,以后保存数据的过程。数据库
笔者在刚刚开始的时候也写了一个小小的爬虫实践了一下,大概就是发送一个get
请求,而后经过相似于DOM
操做的东西,找到网页中所须要的数据,而后进行存储。npm
分析58案例
在调研过程当中发现不少博客都是针对爬取58
同城的网页数据进行了分析,因而就点进去看了一下。58
是使用的字体对数据进行加密的。所见的和所实际展现的内容是不一致的。看上去非常高端的样子。后端
这是什么状况?因而查看了一下当前的元素的CSS
样式,能够注意到这样的元素使用了奇怪的font-family:fangchan-secret
(房产-加密)字体样式,若是咱们关闭这个strongbox
样式,停用这个字体,页面上就会如实的显示乱码了。数组
其实能够看的出来58
同城是使用了font-family
字体进行一次加密处理,笔者在Network
中找了好久也没有找到这个有关这段的字体文件。。。啊嘞?那么字体是哪来的。。。因而笔者就去查看了一下font-family
和@font-face
这个CSS
的相关文献。浏览器
原来@font-face
不仅仅能够接收一个文件的地址,还可使用base64
做为src
中的参数。因而在58
同城的页面中查看源码,果然和我想的同样,58
同城没有经过拉取字体库资源处理,而是在页面最开始建立时经过JavaScript
脚本动态添加入到页面中。app
大概知道58
的骚操做以后就开始研究下一步的东西,如何实现文字所见和实际不一样的。其实对于每一个汉字和字符来都对应了一个Unicode
编码,从第一张图片中不难看出查看源码时鑶
这些就是Unicode
编码,浏览器经过Unicode
在字体库中找到对应的文字。这个就和平时使用的字体图标库道理差很少吧(我的以为。。。哈哈哈)。dom
打开百度字体编辑网站打开一个以.ttf
格式的字体文件。
清楚的能够看到字体包里面的每个文字,以$
开头的则是unicode
的缩写了,作了一些处理,不然浏览器会直接解析成对应的字符。
接下来再回头分析58
同城网页时如何操做的,看见的是5
则查看源码时看到的则是其余的unicode
编码,这个unicode
对应的时另外的生僻的汉字。
先放下这一段,为了更好的理解,把矛头指向阿里图标库使用过阿里图标库的同窗应该不是很陌生,阿里图标库会把svg
文件转换成字体,经过下载引入到咱们的项目中,完成图标的展现。针对不一样的字体会生成新的unicode
编码,然而这些新的unicode
则不会与现有字体库中的unicode
编码冲突。
就此能够想出.ttf
文件中的每一个一字体都是一个svg
文件,咱们只须要经过技术手段把字体包转换成svg
获取到svg
中每一个字体的绘制参数,替换掉原有文字的unicode
替换成生僻字的编码不就能够了吗?说干就干。
程序设计
因为字体文件太多,因此须要在程序运行前要对这些字体文件进行处理操做,转换成svg
这个过程须要很长的时间,根本就不可能每次接到请求的时候就作这件事情,因此为了可以在请求过程当中快速处理,必须在程序运行前就要把字体包转换成svg
。
可是遇到一个问题,并非全部文字所有都须要加密的,而是某些特定的字符须要加密,因此,为了保证这个操做,在数据库中写入当前须要加密的字符,在转换成svg
以后去读取svg
文件中的标签,把标签中的绘制路径的属性和一些必要参数,根据对应字符存储到数据库当中。
每次接收到数据请求时直接去读取数据库中的数据,而后生成svg
文件,再把生成好的svg
文件进行处理成base64
发送给前端,完成展现。
解析字体文件
遇到的第一个问题就是如何解析字体文件,因为笔者对于其余语言不太数据,因此只能使用node
,经过对npm
仓库的搜索找到了一个相关库ttf2svg
。
首先安装这个库:
npm install --save-dev ttf2svg
这里使用的基础字体库是微软雅黑
这个文件在电脑中就能够找到。MicrosoftYaHei.ttf
若是找不到的小伙伴能够自行百度一下。
读取字体包转svg
代码以下:
import path from "path"; import fs from "fs"; import util from "util"; import ttf2svg from "ttf2svg"; // 每次启动前须要删除原有svg文件,以防改变了所须要加密的字体包,参数没有及时发生变化 import {removeDir} from "../util/fs"; // 读取文件 const readFile = util.promisify(fs.readFile); // 写入文件 const writeFile = util.promisify(fs.writeFile); // 建立文件夹 const mkdir = util.promisify(fs.mkdir); const ttfToSvg = async () => { // 运行根目录 const rootPath = process.cwd(); // .ttf文件所在目录 const ttfUrl= path.join(rootPath,"static/ttf/MicrosoftYaHei.ttf"); // 导出文件文件夹名称 const saveSvgMkdirName = "ttfSvg"; // svg存储路径 const saveSvgUrl = path.resolve(rootPath,`${saveSvgMkdirName}/MicrosoftYaHei.svg`); // svg文件夹路径 const svgDirUrl = path.resolve(rootPath,saveSvgMkdirName); // 读取ttf文件生成buffer const ttfBuffer = await readFile(ttfUrl); // 经过 ttf2svg 将buffer转换成svg const svgContent = ttf2svg(ttfBuffer); // 删除原有svg文件 removeDir(svgDirUrl); // 建立存放svg文件夹 await mkdir(svgDirUrl); // 写入svg await writeFile(saveSvgUrl,svgContent); // 返回存放svg的路径地址 return saveSvgUrl; };
util/fs.js
import fs from "fs"; export const removeDir = (path) => { let files = []; if( fs.existsSync(path) ) { files = fs.readdirSync(path); files.forEach((file,index) => { let curPath = path + "/" + file; fs.unlinkSync(curPath); }); fs.rmdirSync(path); } }
生成好所须要的svg
文件,看看生成好的svg
是否是咱们所须要的呢?
看样子一切都在朝着好的方向发展,这个东西正是咱们所须要的。接下来就是开始读取svg
文件(这里就不一样步数据库了,小伙伴们能够根据本身的需求进行同步处理),想要读取svg
开始的时候仍是蛮头疼的,不知道该如何去读取里面的内容。想了想以后以为svg
和 xml
是差很少的,因而就尝试着使用读取xml
文件的形式去读取svg
文件,结果就真的成了。
这里使用xmldom
来读取的svg
文件:
npm install --save-dev xmldom
有关xmldom
的一些文档你们能够自行百度一下,也没有太复杂。具体应用代码以下:
import fs from "fs"; import util from "util"; import {DOMParser} from "xmldom"; const readFile = util.promisify(fs.readFile); const readSvg = async (svgPath) => { // 读取svg文件 const svgContent = await readFile(svgPath); // 读取内容转换成utf8形式 const svgHtml = Buffer.from(svgContent).toString("utf8"); // 生成伪xml const doc = (new DOMParser()).parseFromString(svgHtml, 'application/xml'); // 获取到第一个font标签 const oFont = doc.getElementsByTagName("font")[0]; // 获取到font下面的全部glyph标签,并转换成数组 // 读取出来的是个伪数组须要转换 const oGlyphs = Array.from(oFont.getElementsByTagName("glyph")); // 测试临时使用数组 const arr = []; // 遍历oGlyphs全部标签 oGlyphs.map((fontEle,index) => { // svg对应的unicode const unicode = fontEle.getAttribute("unicode"); // svg绘制参数 const d = fontEle.getAttribute("d"); // svg横向位置 const horizAdvX = fontEle.getAttribute("horiz-adv-x"); // svg竖向位置 const vertAdvY = fontEle.getAttribute("vert-adv-y"); // 这里只是个方便测试作的判断 if(index === 20 || index === 21 || index === 22) { arr.push({ unicode,d,horizAdvX,vertAdvY }); } }) console.log(...arr); };
执行完上述代码就完成,彻底能够读取到里面的全部属性。这个时候突然感受已经看到的胜利的曙光有没有,哈哈哈。接下来就是最关键的一步了,如何把读取到的内容转换成是转换成base64
编码。通过一番搜索以后,找到了svg2ttf
这个仓库,简直没有太香啊。
安装相关依赖:
npm install --save-dev svg2ttf
具体实现以下:
import fs from "fs"; import util from "util"; import {DOMParser} from "xmldom"; const readFile = util.promisify(fs.readFile); const readSvg = async (svgPath) => { // 读取svg文件 const svgContent = await readFile(svgPath); // 读取内容转换成utf8形式 const svgHtml = Buffer.from(svgContent).toString("utf8"); // 生成伪xml const doc = (new DOMParser()).parseFromString(svgHtml, 'application/xml'); // 获取到第一个font标签 const oFont = doc.getElementsByTagName("font")[0]; // 获取到font下面的全部glyph标签,并转换成数组 // 读取出来的是个伪数组须要转换 const oGlyphs = Array.from(oFont.getElementsByTagName("glyph")); // 测试临时使用数组 const arr = []; // 遍历oGlyphs全部标签 oGlyphs.map((fontEle,index) => { // svg对应的unicode const unicode = fontEle.getAttribute("unicode"); // svg绘制参数 const d = fontEle.getAttribute("d"); // svg横向位置 const horizAdvX = fontEle.getAttribute("horiz-adv-x"); // svg竖向位置 const vertAdvY = fontEle.getAttribute("vert-adv-y"); // 这里只是个方便测试作的判断 if(index === 20 || index === 21 || index === 22) { arr.push({ unicode,d,horizAdvX,vertAdvY }); } }) // 获取svg内容 let svgStr = getSvgStr(arr); console.log(svgStr) // 把svg转换成ttf const ttf = svg2ttf(svgStr,{}); // 把ttf转换成base64 const base64 = Buffer.from(ttf.buffer).toString('base64'); console.log(base64); }; const getSvgStr = (arr) => { // 用与拼接的svg let str = ""; // 临时替换文件,暂时性的,之后须要替换成因此unicode let _a = ["唹","唵","啜"]; // 生成svg内容 arr.map((el,index) => { str += `<glyph glyph-name="${+new Date()}" unicode="${_a[index]};" d="${el.d}" horiz-adv-x="${el.horizAdvX}" vert-adv-y="${el.vertAdvY}"/>`; }) // 返回svg形式的字符串 return `<?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" > <svg xmlns="http://www.w3.org/2000/svg"> <defs> <font id="svgtofont" horiz-adv-x="2688" vert-adv-y="2688"> <font-face font-family="Microsoft YaHei" font-weight="400" font-stretch="normal" units-per-em="2048" ascent="2167" descent="-536"/> <missing-glyph /> ${str} </font> </defs> </svg>`; }
这里出了一些小问题,注意咱们要把咱们所生成的字体svg
文件的font
标签部分复制过来,做为参数若是不这样作的话生成的字体会出现位置偏移的现象。
全部工做准备就绪了,执行程序就能够获得应该给前端的base64
编码了,这我也进行了测试。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <script> function addStyle (base64,fontName){ let oStyle = document.createElement("style"); oStyle.innerText = `@font-face { font-family: "${fontName}"; src: url(data:application/x-font-woff;charset=utf-8;base64,${base64}); }`; document.head.appendChild(oStyle); }; const b = "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"; const n = "abc"; addStyle(b,n); </script> <body> <div class="box"> <div></div> <div></div> <div style="font-family: abc; color:red; font-size:50px;">唹唵啜567</div> <div></div> <div class="fiveBox">0123456789</div> </div> <script> </script> <style> * { margin:0px; padding: 0px; } .box { width:100%; white-space: nowrap; } .box div { width:500px; height:50px; border:1px solid #ededed; /* float: left; */ font-size: 20px; color: pink; } .box::after { content: ""; display: block; clear: both; } </style> </body> </html>
以上就是个人测试代码,展现效果以下:
这样下来就和58
同城的效果是同样的了,通过了几天的调研也算是有了初步的成果,也是有一些成就感的。
总结
总的来讲在这个调研的过程当中仍是学到了不少的东西,好比阿里图标库是如何实现的,字体包里面都有什么等等等。。。虽然在这个过程当中用了不少第三方的依赖,可是结果是好的。
文章比较潦草,感谢各位花费这么长时间阅读,文章中若是有什么错误,请在评论处指出,我会尽快作出改正。