談一談使用字體庫加密數據-仿58同城

對於前端同窗來講其實作的更多的事情就是把數據整合好,按照UI同窗的設計經過後端同窗給的數據展現在網頁中,這也就致使了不少人認爲前端很簡單,沒有作什麼工做也沒有什麼後端複雜的業務邏輯。html

其實否則前端要作的工做有不少,就好比今天要說的,如何作到數據的反爬,筆者最近也接到了相同的任務,公司的數據頻繁被爬蟲爬走,出現這個狀況以後,而後就開始調研如何才能實現前端業務數據的反爬工做。剛剛開始接到這個需求的時候,不知道該如何處理這件事,只能硬着頭皮接下了這個任務。前端

接到任務以後就開始了各類Google,筆者以爲若是想要作到反爬就要先知道什麼是爬蟲以及爬蟲是如何做業。這就比如咱們要足夠的瞭解對手才能知道如何去防護。node

爬蟲是經過發送http請求獲取獲取到響應後的內容,經過按照必定的規則,自動爬去網頁信息,以後保存數據的過程。數據庫

筆者在剛剛開始的時候也寫了一個小小的爬蟲實踐了一下,大概就是發送一個get請求,而後經過相似於DOM操做的東西,找到網頁中所須要的數據,而後進行存儲。npm

分析58案例

在調研過程當中發現不少博客都是針對爬取58同城的網頁數據進行了分析,因而就點進去看了一下。58是使用的字體對數據進行加密的。所見的和所實際展現的內容是不一致的。看上去非常高端的樣子。後端

image

這是什麼狀況?因而查看了一下當前的元素的CSS樣式,能夠注意到這樣的元素使用了奇怪的font-family:fangchan-secret(房產-加密)字體樣式,若是咱們關閉這個strongbox樣式,停用這個字體,頁面上就會如實的顯示亂碼了。數組

其實能夠看的出來58同城是使用了font-family字體進行一次加密處理,筆者在Network中找了好久也沒有找到這個有關這段的字體文件。。。啊嘞?那麼字體是哪來的。。。因而筆者就去查看了一下font-family@font-face這個CSS的相關文獻。瀏覽器

原來@font-face不僅僅能夠接收一個文件的地址,還可使用base64做爲src中的參數。因而在58同城的頁面中查看源碼,果然和我想的同樣,58同城沒有經過拉取字體庫資源處理,而是在頁面最開始建立時經過JavaScript腳本動態添加入到頁面中。app

image

大概知道58的騷操做以後就開始研究下一步的東西,如何實現文字所見和實際不一樣的。其實對於每一個漢字和字符來都對應了一個Unicode編碼,從第一張圖片中不難看出查看源碼時&#x9476這些就是Unicode編碼,瀏覽器經過Unicode在字體庫中找到對應的文字。這個就和平時使用的字體圖標庫道理差很少吧(我的以爲。。。哈哈哈)。dom

打開百度字體編輯網站打開一個以.ttf格式的字體文件。

image

清楚的能夠看到字體包裏面的每個文字,以$開頭的則是unicode的縮寫了,作了一些處理,不然瀏覽器會直接解析成對應的字符。

接下來再回頭分析58同城網頁時如何操做的,看見的是5則查看源碼時看到的則是其餘的unicode編碼,這個unicode對應的時另外的生僻的漢字。

先放下這一段,爲了更好的理解,把矛頭指向阿里圖標庫使用過阿里圖標庫的同窗應該不是很陌生,阿里圖標庫會把svg文件轉換成字體,經過下載引入到咱們的項目中,完成圖標的展現。針對不一樣的字體會生成新的unicode編碼,然而這些新的unicode則不會與現有字體庫中的unicode編碼衝突。

就此能夠想出.ttf文件中的每一個一字體都是一個svg文件,咱們只須要經過技術手段把字體包轉換成svg獲取到svg中每一個字體的繪製參數,替換掉原有文字的unicode替換成生僻字的編碼不就能夠了嗎?說幹就幹。

程序設計

因爲字體文件太多,因此須要在程序運行前要對這些字體文件進行處理操做,轉換成svg這個過程須要很長的時間,根本就不可能每次接到請求的時候就作這件事情,因此爲了可以在請求過程當中快速處理,必須在程序運行前就要把字體包轉換成svg

可是遇到一個問題,並非全部文字所有都須要加密的,而是某些特定的字符須要加密,因此,爲了保證這個操做,在數據庫中寫入當前須要加密的字符,在轉換成svg以後去讀取svg文件中的標籤,把標籤中的繪製路徑的屬性和一些必要參數,根據對應字符存儲到數據庫當中。

image

每次接收到數據請求時直接去讀取數據庫中的數據,而後生成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是否是咱們所須要的呢?

image

看樣子一切都在朝着好的方向發展,這個東西正是咱們所須要的。接下來就是開始讀取svg文件(這裏就不一樣步數據庫了,小夥伴們能夠根據本身的需求進行同步處理),想要讀取svg開始的時候仍是蠻頭疼的,不知道該如何去讀取裏面的內容。想了想以後以爲svgxml是差很少的,因而就嘗試着使用讀取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 = ["&#x5539","&#x5535","&#x555C"];
    //  生成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;">&#x5539;&#x5535;&#x555C;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>

以上就是個人測試代碼,展現效果以下:

image

這樣下來就和58同城的效果是同樣的了,通過了幾天的調研也算是有了初步的成果,也是有一些成就感的。

總結

總的來講在這個調研的過程當中仍是學到了不少的東西,好比阿里圖標庫是如何實現的,字體包裏面都有什麼等等等。。。雖然在這個過程當中用了不少第三方的依賴,可是結果是好的。

文章比較潦草,感謝各位花費這麼長時間閱讀,文章中若是有什麼錯誤,請在評論處指出,我會盡快作出改正。

相關文章
相關標籤/搜索