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

打开百度字体编辑网站打开一个以.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.jsimport 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 += `                      unicode="${_a[index]};"                      d="${el.d}"                      horiz-adv-x="${el.horizAdvX}"                      vert-adv-y="${el.vertAdvY}"/>`;    })    //  返回svg形式的字符串    return `<?xml version="1.0" standalone="no"?>                                                                  font-weight="400"                      font-stretch="normal"                      units-per-em="2048"                      ascent="2167"                      descent="-536"/>                        ${str}                          `;}

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

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

<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>Documenttitle>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;">唹唵啜567div>  <div>div>  <div class="fiveBox">0123456789div>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同城的效果是一样的了,经过了几天的调研也算是有了初步的成果,也是有一些成就感的。

总结

总的来说在这个调研的过程中还是学到了很多的东西,比如阿里图标库是如何实现的,字体包里面都有什么等等等。。。虽然在这个过程中用了很多第三方的依赖,但是结果是好的。

文章比较潦草,感谢各位花费这么长时间阅读,文章中如果有什么错误,请在评论处指出,我会尽快做出改正。

- END -

readfile读取串口数据_谈一谈使用字体库加密数据仿58同城相关推荐

  1. readfile读取串口数据_西门子PLC-1200-串口Modbus RTU通讯实例

    西门子PLC-1200的基于232串口的Modbus RTU通讯实例 这里先讲一下Modbus和232,232和485串口是一种串口接法,这种接法实际能实现十几种的协议通讯,有基于串口的Modbus ...

  2. 读取串口数据_自定义串口通信的相关问题整理

    串口通信是常见的通信方式,串口接口是大部分工控器件标配的通信接口.在项目开发的过程中,也经常遇到进行串口通信的处理.这里就串口通信的部分问题分享给大家. 1.TTL.RS232.RS422.RS458 ...

  3. 使用Python读取串口数据

    Python读取串口数据 使用Python读取串口数据 1.编程流程 2.应用serial库 3.编码设计 4.编程实现 使用Python读取串口数据 1.编程流程 串口数据流程为, 1.实例化串口连 ...

  4. windows下C++读取串口数据

    windows下C++读取串口数据 这里提供一个类: SerialPort.h #pragma once #ifndef SERIALPORT_H_ #define SERIALPORT_H_ #in ...

  5. 如何在QT中读取串口数据

    总是能在别人的博客中学到太多太多,谢谢各位对知识的无私共享,谢谢大家 前言 去年我使用Qt编写串口通信程序时,将自己的学习过程写成了教程(Qt编写串口通信程序全程图文讲解),但是由于时间等原因,我只实 ...

  6. python串口通信_python 读取串口数据的示例

    python3 读取串口数据 demo 最近在写一个demo,zigbee串口连接树莓派,树莓派使用串口通信接受zigbee穿过来得值.其中我是用的树莓派是3代B+,zigbee每隔三秒钟从串口输出数 ...

  7. web前端读取串口数据

    来自扫码枪应用适配的经验及扩展. 查了好多资料,发现实现思路不外乎以下几种: 1.使用服务端开发语言开发一个读取串口数据的本地服务模块,并实现websocket长连接支持,前端开启长连接到该服务,根据 ...

  8. Java读取串口数据

    本文主要实现读取员工刷卡信息 将淘宝买的刷卡IC卡读取器插入Windows笔记本的USB接口,安装驱动,设备管理器会自动添加一个模拟的COM3串口 本次使用的Java环境为 java version ...

  9. java 读取串口数据

    串口资料 链接:https://pan.baidu.com/s/1jVO47WF7pCO6kDLezsBZ0g 提取码:hgwn 这段时间遇到了读取串口数据的需求,吭哧吭哧搞了两三天才搞出来,惭愧,所 ...

最新文章

  1. 路由器和交换机的综合实验
  2. percona mysql安装_mysql 安装 (percona)
  3. 马斯克要复活恐龙?乌龙还差不多
  4. 【Android学习笔记】设置App启动页
  5. 透视变换--图像拼接
  6. 庖丁解牛!深入剖析React Native下一代架构重构
  7. C++基础——有关引用的问题
  8. 【Redis笔记】数据结构和对象:字典
  9. PyTorch1.4安装-进入官网安装时最靠谱的
  10. MyCAT实现MySQL的读写分离
  11. 频谱仪的更改ip_通过局域网(LAN)读取频谱分析仪图像的方法
  12. 【Python 小白到精通 | 课程笔记】第三章:数据处理就像侦探游戏(函数和包)
  13. python判断图片相似度_图像检索系列——利用 Python 检测图像相似度!
  14. visio中图形任意位置添加连接点
  15. KGB知识图谱成功落地金融行业
  16. python实现触摸精灵功能_触摸精灵实现找图功能
  17. 百度地图API js详细介绍
  18. 《HelloGitHub》第 74 期
  19. Super Hide IP 3.4.7.8允许您以匿名方式进行网上冲浪、 保持隐藏您的 IP 地址
  20. 花旗杯决赛第二 + 最佳移动应用

热门文章

  1. 软件工程导论团队介绍——信科吴彦组
  2. The type 'System.Object' is defined in an assembly that is not referenced
  3. 【Linux】【Commands】文本查看类
  4. 【普通の随笔】3.26
  5. 学会查找问题的源头:《全屏游戏中自动切出到桌面的问题解决(二)》
  6. tcp的粘包和拆包示例以及使用LengthFieldFrameDecoder来解决的方法
  7. 线程(Thread)和异常
  8. 华为2021数字化转型报告:从战略到执行.pdf(附103页pdf下载链接)
  9. 【免费下载】2021年11月热门报告盘点(附热门报告列表及下载链接)
  10. 专访头条搜索:从推荐到搜索,如何构建搜索技术的另一种可能?