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


“分享、點(diǎn)贊、在看” 支持一波 
