如何做到只使用 CSS 進行用戶追蹤?
回復1,加入高級 Node 進階交流群
譯者:黃梵高 https://juejin.cn/post/6887478219662950414
在瀏覽器里進行用戶追蹤會引發(fā)關于隱私和數據保護一次又一次的討論。類似 Google 分析之類的工具幾乎可以抓到所有需要的內容,包括來源,語言,設備,停留時間等等。
但是,想獲取一些感興趣的信息,你可能不需要任何外部追蹤器,甚至不需要 JavaScript。本文將向你展示,即便用戶禁用了 JavaScript,依然可以跟蹤用戶的行為。
追蹤器通常如何工作
通常,這類追蹤器分析工具要使用到 JavaScript。因此,大多數等信息可以十分輕松的讀取,并且可以立刻發(fā)送到服務端。
這就是為什么出現越來越多的方式來阻止瀏覽器中跟蹤器的原因。類似 Brave Browser 的瀏覽器或者某些 chrome 擴展程序會阻止跟蹤器的加載,例如 Google 分析。其中一個訣竅是,例如 Google 分析總是從外部集成的,一段來自 Google CDN 的 JavaScript 代碼。嵌入的 URL 總是相同的,因此可以輕松的將它阻止掉。
因此追蹤器總是會用 JavaScript 做些什么。甚至如果你通過阻止 URL 限制了追蹤器,網站擁有者可能會通過將 JavaScript 代碼嵌入頁面的方式繼續(xù)使用。最強有力的保護措施就是禁用 JavaScript,雖然這可能會付出非常大的代價。
最后,我們仍然可以不使用 JavaScript 追蹤一些內容,而是使用一些 CSS 技巧。當然 CSS 并不是為追蹤使用的,讓我們開始實踐吧。
找到設備類型信息
媒體查詢應該是每一個 web 開發(fā)者都知道的。有了這個,我們可以讓 CSS 代碼只在某些確定的屏幕條件下執(zhí)行。所以我們可以為智能手機或平板電腦等,編寫自己的查詢條件。
我們所有 CSS 追蹤器背后的魔法就是它們的屬性,比如我們可以將一段 URL 作為屬性值。有一個比較好的例子是 background-image 的屬性,它允許我們?yōu)橐粋€元素設置一張背景圖片。這張圖片從一段 URL 獲取,并且在執(zhí)行過程中,它是優(yōu)先請求的,因此會向這個 URL 地址: background-image: url('/dog.png'); 發(fā)送一個 GET 請求。
但是最后,沒人強制我們必須確保這段 URL 鏈接確實能訪問到圖片。服務器甚至不需要對請求進行應答,但我們仍然可以響應 GET 請求,向數據庫輸入數據。
const express = require("express");
const app = express();
app.get("/", (req, res) => {
res.sendFile(__dirname + "/index.html");
});
app.get("/mobile", (req, res) => {
console.log("is mobile")
res.end()
)}
app.listen(8080)
復制代碼
至于后端,我使用 Express.js 作為服務器。它提供了一個簡單的 HTML 網站;如果訪問設備是智能手機,則會調用 mobile 路由。并且我們的后端是唯一使用 JavaScript 的地方。
@media only screen and (max-width: 768px) {
body {
background-image: url("http://localhost:8080/mobile");
}
}
復制代碼
在我們的 index.html 文件中,我們有了上面的 CSS 代碼。只有在用戶設備與媒體查詢匹配的時候,才請求背景圖片。
如果現在一部智能手機訪問這個頁面,媒體查詢會執(zhí)行,并發(fā)送請求背景圖片的請求,同時服務端會輸出它是智能手機。這些操作是完全沒有使用 JavaScript。
并且由于我們不會發(fā)送一張圖片作為回應,這個網站內容將不會有任何改變。
找到操作系統(tǒng)信息
現在變得更加瘋狂,我們能大致找到用戶操作系統(tǒng)通過它支持的字體。在 CSS 中,我們可以使用多種后備方案,換句話說,可以指定多種字體。如果第一個在系統(tǒng)上不起作用,瀏覽器將會嘗試第二個。
font-family: BlinkMacSystemFont, "Arial"; 當我在我們的網站嵌入這句代碼時,我的 MacBook 使用第一種蘋果標準字體,這字體只可以在 Mac OS 上使用。當在我的 Windows PC 上,Arial 正常使用。
當使用字體時,我們可以定義自定義字體以及從什么地方加載它。Google 字體的工作方式相同,如果我們要從某處使用自定義的字體,必須先從服務器加載它。并且我們可以多次使用字體。
@font-face {
font-family: Font2;
src: url("http://localhost:8080/notmac");
}
body {
font-family: BlinkMacSystemFont, Font2, "Arial";
}
復制代碼
這里我們?yōu)榱巳康?body 部分設置了字體。從邏輯上講,你只能使用一種字體。以至于在 MacBook 上,使用的是第一種字體,即系統(tǒng)自己的字體。在類似 Windows 的其他系統(tǒng)上,系統(tǒng)檢查字體是否存在。當然,肯定不存在,因此嘗試使用下一種我們自己定義的字體。它仍然不得不從服務端加載,因此我們的 CSS 代碼會再次觸發(fā) GET 請求。
畢竟 Font2 不是一個真正的字體,因此我們繼續(xù)嘗試,最終將使用 Arial 字體。盡管如此,我們仍然可以在用戶無感知的情況下,使用一個合理的字體。
追蹤元素信息
到目前為止,我們所做的事情就是當用戶抵達網站,立即對信息進行分析。當然,我們也可以利用 CSS 對單獨的事件做出應對。
如下所示,我們可以使用下面的例子,來分析鼠標懸?;蚧顒邮录?。
<head>
<style>
#one:hover {
background-image: url("http://localhost:8080/one-hovered/");
}
</style>
</head>
<body>
<button id="one">Hover me</button>
</body>
復制代碼
當鼠標每次懸停在按鈕上,它會一次又一次的設置背景圖片,一個 GET 請求也隨之發(fā)出。
我們可以在按鈕被點擊時,做相同的事情。在 CSS 中,這就是活動事件。
<head>
<style>
#one:active {
background-image: url("http://localhost:8080/one-clicked/");
}
</style>
</head>
<body>
<button id="one">Click me</button>
</body>
復制代碼
還有一系列其他事件。例如,懸停事件幾乎適用在每一個元素上。因此從理論上來講,我們可以追蹤用戶的每一個行為。
猶豫計時器
使用更多的代碼,我們可以組合這些事件并且了解更多信息,而不僅僅是發(fā)生了那些事件。
對于許多網站主來說,更感興趣的是,用戶在看到或懸停在元素上猶豫了多久才點擊某個元素。通過下面的代碼,我們可以測量用戶懸停后點擊所花費的時間。
let counter;
app.get("/one-hovered", (req, res) => {
counter = Date.now();
});
app.get("/one-active", (req, res) => {
console.log("Clicked after", (Date.now() - counter) / 1000, "seconds");
});
復制代碼
用戶一旦懸停,計時器就會啟動。最后,我們可以算出直到點擊過了幾秒。
你可能會認為由于它嵌入在 CSS 代碼中,統(tǒng)計的可能并不準確,但事實并非如此。由于請求的體積十分小,并且立即作用在服務器上。我試了幾次并測量了時間,最終測量的結果非常精確。
很驚人,不是嗎?
讓整個功能更美觀
為了不被發(fā)現,使用不顯眼的 URL 是十分有意義的。最后,每個人都可以看到完整的前端代碼。
你也可以使用自己想到的關鍵詞,代替?zhèn)€別特別顯眼的路由單詞。最后,前端和后端的 URL 必須匹配。
對于上面的示例,我始終將我自己的路由用作 GET 請求。這樣十分清晰明白。一種更優(yōu)雅的方式是使用 URL 的查詢,這在 CSS 當中也適用。
@font-face {
font-family: Font2;
src: url("http://192.168.2.110:8080/os/mac");
/* or: */
src: url("http://192.168.2.110:8080/?os=mac");
}


“分享、點贊、在看” 支持一波 
