對於前端同窗來講其實作的更多的事情就是把數據整合好,按照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
同城的效果是同樣的了,通過了幾天的調研也算是有了初步的成果,也是有一些成就感的。
總結
總的來講在這個調研的過程當中仍是學到了不少的東西,好比阿里圖標庫是如何實現的,字體包裏面都有什麼等等等。。。雖然在這個過程當中用了不少第三方的依賴,可是結果是好的。
文章比較潦草,感謝各位花費這麼長時間閱讀,文章中若是有什麼錯誤,請在評論處指出,我會盡快作出改正。