本文转载于 SegmentFault 社区

作者:Aaron

对于前端同学来说其实做的更多的事情是把数据整合好,按照UI同学的设计通过后端同学给的数据展示在网页中,这也导致了很多人认为前端很简单,没有做什么工作也没有什么后端复杂的业务逻辑。

其实不然前端要做的工作有很多,比如要说的,如何做到数据的反爬,笔者近也接到了相同的任务,公司的数据频繁被爬虫爬走,出现这个情况之后,然后开始调研如何才能实现前端业务数据的反爬工作。刚刚开始接到这个需求的时候,不知道该如何处理这件事,只能硬着头皮接下了这个任务。

接到任务之后开始了各种Google,笔者觉得如果想要做到反爬要先知道什么是爬虫以及爬虫是如何作业。这好比我们要足够的了解对手才能知道如何去防御。

爬虫是通过发送http请求获取获取到响应后的内容,通过按照一定的规则,自动爬去网页信息,之后保存数据的过程。

笔者在刚刚开始的时候也写了一个小小的爬虫实践了一下,大概是发送一个get请求,然后通过类似于DOM操作的东西,找到网页中所需要的数据,然后进行存储。

分析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脚本动态添加入到页面中。

谈一谈使用字体库加密数据-仿58同城插图

大概知道58的骚操作之后开始研究下一步的东西,如何实现文字所见和实际不同的。其实对于每个汉字和字符来都对应了一个Unicode编码,从张图片中不难看出查看源码时&x9476这些是Unicode编码,浏览器通过Unicode在字体库中找到对应的文字。这个和平时使用的字体图标库道理差不多吧(个人觉得。。。哈哈哈)。

打开百度字体编辑网站打开一个以.ttf格式的字体文件。

谈一谈使用字体库加密数据-仿58同城插图1

清楚的可以看到字体包里面的每一个文字,以$开头的则是unicode的缩写了,做了一些处理,否则浏览器会直接解析成对应的字符。

接下来再回头分析58同城网页时如何操作的,看见的是5则查看源码时看到的则是其他的unicode编码,这个unicode对应的时另外的生僻的汉字。

先放下这一段,为了更好的理解,把矛头指向阿里图标库使用过阿里图标库的同学应该不是很陌生,阿里图标库会把svg文件转换成字体,通过下载引入到我们的项目中,完成图标的展示。针对不同的字体会生成新的unicode编码,然而这些新的unicode则不会与现有字体库中的unicode编码冲突。

此可以想出.ttf文件中的每个一字体都是一个svg文件,我们只需要通过技术手段把字体包转换成svg获取到svg中每个字体的绘制参数,替换掉原有文字的unicode替换成生僻字的编码不可以了吗?说干干。

程序设计

由于字体文件太多,所以需要在程序运行前要对这些字体文件进行处理操作,转换成svg这个过程需要很长的时间,根本不可能每次接到请求的时候做这件事情,所以为了能够在请求过程中快速处理,必须在程序运行前要把字体包转换成svg。

但是遇到一个问题,并不是所有文字全部都需要加密的,而是某些特定的字符需要加密,所以,为了保证这个操作,在数据库中写入当前需要加密的字符,在转换成svg之后去读取svg文件中的标签,把标签中的绘制路径的属性和一些必要参数,根据对应字符存储到数据库当中。

谈一谈使用字体库加密数据-仿58同城插图2

每次接收到数据请求时直接去读取数据库中的数据,然后生成svg文件,再把生成好的svg文件进行处理成base64发送给前端,完成展示。

解析字体文件

遇到的个问题是如何解析字体文件,由于笔者对于其他语言不太数据,所以只能使用node,通过对npm仓库的搜索找到了一个相关库ttf2svg。

首先安装这个库:

npminstall–save-dev ttf2svg

这里使用的基础字体库是微软雅黑这个文件在电脑中可以找到。MicrosoftYaHei.ttf如果找不到的小伙伴可以自行百度一下。

读取字体包转svg代码如下:

importpathfrom“path”;importfsfrom“fs”;importutilfrom“util”;importttf2svgfrom“ttf2svg”;// 每次启动前需要删除原有svg文件,以防改变了所需要加密的字体包,参数没有及时发生变化import{removeDir}from“../util/fs”;// 读取文件constreadFile = util.promisify(fs.readFile);// 写入文件constwriteFile = util.promisify(fs.writeFile);// 创建文件夹constmkdir = util.promisify(fs.mkdir);constttfToSvg =async() => {// 运行根目录constrootPath = process.cwd();// .ttf文件所在目录constttfUrl= path.join(rootPath,“static/ttf/MicrosoftYaHei.ttf”);// 导出文件文件夹名称constsaveSvgMkdirName =“ttfSvg”;// svg存储路径constsaveSvgUrl = path.resolve(rootPath,`${saveSvgMkdirName}/MicrosoftYaHei.svg`);// svg文件夹路径constsvgDirUrl = path.resolve(rootPath,saveSvgMkdirName);// 读取ttf文件生成bufferconstttfBuffer =awaitreadFile(ttfUrl);// 通过 ttf2svg 将buffer转换成svgconstsvgContent = ttf2svg(ttfBuffer);// 删除原有svg文件removeDir(svgDirUrl);// 创建存放svg文件夹awaitmkdir(svgDirUrl);// 写入svgawaitwriteFile(saveSvgUrl,svgContent);// 返回存放svg的路径地址returnsaveSvgUrl;};util/fs.jsimportfsfrom“fs”;exportconstremoveDir =(path) =>{letfiles = [];if( fs.existsSync(path) ) {files = fs.readdirSync(path);files.forEach((file,index) =>{letcurPath = path +“/”+ file;fs.unlinkSync(curPath);});fs.rmdirSync(path);}}

生成好所需要的svg文件,看看生成好的svg是不是我们所需要的呢?

谈一谈使用字体库加密数据-仿58同城插图3

看样子一切都在朝着好的方向发展,这个东西正是我们所需要的。接下来是开始读取svg文件(这里不同步数据库了,小伙伴们可以根据自己的需求进行同步处理),想要读取svg开始的时候还是蛮头疼的,不知道该如何去读取里面的内容。想了想之后觉得svg和 xml是差不多的,于是尝试着使用读取xml文件的形式去读取svg文件,结果真的成了。

这里使用xmldom来读取的svg文件:

npminstall–save-dev xmldom

有关xmldom的一些文档大家可以自行百度一下,也没有太复杂。具体应用代码如下:

importfsfrom“fs”;importutilfrom“util”;import{DOMParser}from“xmldom”;constreadFile = util.promisify(fs.readFile);constreadSvg =async(svgPath) => {// 读取svg文件constsvgContent =awaitreadFile(svgPath);// 读取内容转换成utf8形式constsvgHtml = Buffer.from(svgContent).toString(“utf8”);// 生成伪xmlconstdoc = (newDOMParser()).parseFromString(svgHtml,application/xml);// 获取到个font标签constoFont = doc.getElementsByTagName(“font”)[0];// 获取到font下面的所有glyph标签,并转换成数组// 读取出来的是个伪数组需要转换constoGlyphs =Array.from(oFont.getElementsByTagName(“glyph”));// 测试临时使用数组constarr = [];// 遍历oGlyphs所有标签oGlyphs.map((fontEle,index) =>{// svg对应的unicodeconstunicode = fontEle.getAttribute(“unicode”);// svg绘制参数constd = fontEle.getAttribute(“d”);// svg横向位置consthorizAdvX = fontEle.getAttribute(“horiz-adv-x”);// svg竖向位置constvertAdvY = fontEle.getAttribute(“vert-adv-y”);// 这里只是个方便测试做的判断if(index ===20|| index ===21|| index ===22) {arr.push({unicode,d,horizAdvX,vertAdvY});}})console.log(…arr);};

执行完上述代码完成,完全可以读取到里面的所有属性。这个时候忽然感觉已经看到的胜利的曙光有没有,哈哈哈。接下来是关键的一步了,如何把读取到的内容转换成是转换成base64编码。经过一番搜索之后,找到了svg2ttf这个仓库,简直没有太香啊。

安装相关依赖:

npminstall–save-dev svg2ttf

具体实现如下:

importfsfrom“fs”;importutilfrom“util”;import{DOMParser}from“xmldom”;constreadFile = util.promisify(fs.readFile);constreadSvg =async(svgPath) => {// 读取svg文件constsvgContent =awaitreadFile(svgPath);// 读取内容转换成utf8形式constsvgHtml = Buffer.from(svgContent).toString(“utf8”);// 生成伪xmlconstdoc = (newDOMParser()).parseFromString(svgHtml,application/xml);// 获取到个font标签constoFont = doc.getElementsByTagName(“font”)[0];// 获取到font下面的所有glyph标签,并转换成数组// 读取出来的是个伪数组需要转换constoGlyphs =Array.from(oFont.getElementsByTagName(“glyph”));// 测试临时使用数组constarr = [];// 遍历oGlyphs所有标签oGlyphs.map((fontEle,index) =>{// svg对应的unicodeconstunicode = fontEle.getAttribute(“unicode”);// svg绘制参数constd = fontEle.getAttribute(“d”);// svg横向位置consthorizAdvX = fontEle.getAttribute(“horiz-adv-x”);// svg竖向位置constvertAdvY = fontEle.getAttribute(“vert-adv-y”);// 这里只是个方便测试做的判断if(index ===20|| index ===21|| index ===22) {arr.push({unicode,d,horizAdvX,vertAdvY});}})// 获取svg内容letsvgStr = getSvgStr(arr);console.log(svgStr)// 把svg转换成ttfconstttf = svg2ttf(svgStr,{});// 把ttf转换成base64constbase64 = Buffer.from(ttf.buffer).toString(base64);console.log(base64);};constgetSvgStr =(arr) =>{// 用与拼接的svgletstr =“”;// 临时替换文件,暂时性的,以后需要替换成所以unicodelet_a = [“&x5539”,“&x5535”,“&x555C”];// 生成svg内容arr.map((el,index) =>{str +=`unicode=”${_a[index]};”d=”${el.d}horiz-adv-x=”${el.horizAdvX}vert-adv-y=”${el.vertAdvY}“/>`;})// 返回svg形式的字符串return` font-weight=”400″font-stretch=”normal”units-per-em=”2048″ascent=”2167″descent=”-536″/>${str}`;}

这里出了一些小问题,注意我们要把我们所生成的字体svg文件的font标签部分复制过来,作为参数如果不这样做的话生成的字体会出现位置偏移的现象。

所有工作准备绪了,执行程序可以得到应该给前端的base64编码了,这我也进行了测试。

<htmllang=“en”><head><metacharset=“UTF-8”><metaname=“viewport”content=“width=device-width, initial-scale=1.0”><metahttp-equiv=“X-UA-Compatible”content=“ie=edge”><title>Document

作者 nasiapp

在线客服
官方客服
我们将24小时内回复。
12:01
您好,有任何疑问请与我们联系!

选择聊天工具: