找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3264

积分

0

好友

436

主题
发表于 2 小时前 | 查看: 2| 回复: 0

先说一件事。

2025年底,UCSD 和 Mozilla 发了篇论文。他们爬了两万个网站,发现——12.7%的热门站点都在用 Canvas 指纹追踪你。

更离谱的是,广告拦截器只挡住了 5%。剩下 95%,你装了 Adblock 也没用。

因为指纹脚本早就不从第三方域名加载了。它们把自己打包进网站自己的 JS bundle 里,从 URL 上看跟正常代码一模一样。广告拦截器哪敢拦?拦了网站就崩了。

还有个更讽刺的事:你装反指纹扩展,反而更容易被识别。

用的人太少(可能不到 0.1%),你一旦用了,就成了那个"小众群体"里的显眼包——"哦,这哥们用了 Canvas 随机化扩展,标记一下"。这就是反指纹悖论。


Canvas 指纹到底是啥?

核心代码就三行:

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.fillText('Cwm fjordbank gly', 2, 15);
const fingerprint = canvas.toDataURL();

同一台机器,这段代码永远输出同一个 base64 字符串。

换一台机器——GPU 不同、系统不同、字体库不同——结果就不一样了。

就这,成了跨站追踪的核武器。你换浏览器、清缓存、开无痕都没用。只要还是那台物理设备,Canvas 画出来的东西就不变。

原理讲完了。直接写检测器。


写个检测器,看看谁在画你

思路很简单:篡改 Canvas 的 toDataURL 方法,劫持每一次调用。看谁调了、调了几次、画了多大。

装环境

pip install playwright
playwright install chromium --with-deps

完整代码

建个 canvas_fp_detector.py

"""
Canvas指纹检测器

功能:
  - 网络拦截(屏蔽图片/字体,加速页面加载)
  - 智能等待页面稳定
  - 详细的调用分析(来源、次数、画布尺寸、耗时)
"""

import asyncio
from playwright.async_api import async_playwright, TimeoutError as PlaywrightTimeout

async def detect_canvas_fingerprinting(url, timeout_ms=25000):
    async with async_playwright() as p:
        browser = await p.chromium.launch(
            headless=True,
            args=["--no-sandbox", "--disable-setuid-sandbox"]
        )

        context = await browser.new_context(
            viewport={"width": 1920, "height": 1080},
            user_agent=(
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                "AppleWebKit/537.36 (KHTML, like Gecko) "
                "Chrome/124.0.0.0 Safari/537.36"
            )
        )

        page = await context.new_page()

        # 加速:屏蔽图片/字体等非关键资源
        await page.route("**/*.{png,jpg,jpeg,gif,svg,ico,woff,woff2,ttf,eot}",
                         lambda route: route.abort())

        # 注入Canvas拦截器
        await page.add_init_script("""
        (() => {
            const original = HTMLCanvasElement.prototype.toDataURL;
            const calls = [];

            HTMLCanvasElement.prototype.toDataURL = function(...args) {
                const stack = new Error().stack || '';
                calls.push({
                    w: this.width, h: this.height,
                    t: args[0] || 'image/png',
                    s: stack.slice(0, 300),
                    ts: Date.now()
                });
                return original.apply(this, args);
            };

            window.__canvas_calls__ = calls;
        })();
        """)

        print(f"  访问: {url}")
        try:
            await page.goto(url, wait_until="networkidle", timeout=timeout_ms)
            try:
                await page.wait_for_function(
                    "() => document.readyState === 'complete'", timeout=5000
                )
                await page.wait_for_timeout(2000)
            except PlaywrightTimeout:
                await page.wait_for_timeout(1000)
        except PlaywrightTimeout:
            print(f"  ⚠️ 页面超时,用已有数据分析")
        except Exception as e:
            print(f"  ❌ 访问失败: {e}")
            await browser.close()
            return None

        data = await page.evaluate("""
            () => {
                const calls = window.__canvas_calls__ || [];
                const bySrc = {};
                for (const c of calls) {
                    const m = c.s.match(/https?:\/\/[^\/]+/);
                    const src = m ? m[0] : 'inline';
                    if (!bySrc[src]) bySrc[src] = [];
                    bySrc[src].push(c);
                }
                return {
                    total: calls.length,
                    sources: Object.entries(bySrc).map(([src, items]) => ({
                        source: src,
                        count: items.length,
                        sizes: [...new Set(items.map(i => `${i.w}x${i.h}`))],
                        interval_ms: items.length > 1 ?
                            items[items.length-1].ts - items[0].ts : 0
                    }))
                };
            }
        """)

        await browser.close()
        return url, data

def analyze(url, data):
    print(f"\n{'='*50}")
    print(f"🔍 目标: {url}")
    print(f"{'='*50}")
    if not data:
        print("❌ 检测失败")
        return
    total = data["total"]
    print(f"📊 Canvas调用次数: {total}")
    if total == 0:
        print("✅ 未检测到指纹行为")
        return
    for src in data["sources"]:
        flag = "🚨" if src["count"] >= 3 else ("⚠️" if src["count"] >= 2 else "ℹ️")
        print(f"  {flag} {src['source']}")
        print(f"     调用{src['count']}次, 画布: {', '.join(src['sizes'])}")
        if src["count"] >= 2:
            print(f"     耗时: {src['interval_ms']}ms")
    fp_sources = [s for s in data["sources"] if s["count"] >= 2]
    if total >= 3 and len(fp_sources) >= 1:
        print(f"\n🚨 判定: 疑似Canvas指纹采集 (可疑来源: {len(fp_sources)}个)")
    elif total >= 2:
        print(f"\n⚠️ 判定: 低度可疑,仅少量调用")
    else:
        print(f"\n✅ 判定: 正常")

async def main():
    sites = [
        "https://www.baidu.com",
        "https://www.zhihu.com",
        "https://www.douban.com",
        "https://www.jd.com",
    ]
    for site in sites:
        result = await detect_canvas_fingerprinting(site)
        if result:
            analyze(*result)

if __name__ == "__main__":
    asyncio.run(main())

实际跑一下

Canvas指纹检测日志截图,包含百度、知乎、豆瓣、京东访问结果

几个有意思的点:

百度调了2次,但不像是搞指纹的。调用间隔1.8秒,更像是页面渲染过程中顺带的行为。

知乎实锤。调了5次,两个来源。注意那个 cstaticdun.126.net——网易易盾,国内最大的风控SDK之一。你刷知乎的时候,它在后台偷偷画你的Canvas。一次不够,画三次。

豆瓣最干净。0次。豆瓣可能是大厂里唯一不搞Canvas指纹的。

京东最离谱。19次。持续3.6秒。三种尺寸的画布来回画。这是典型的批量指纹采集——不是画一次就够,是要从不同角度提取特征,构建精确的设备指纹。


怎么防?

这个问题得分两边聊。

如果你是普通用户:保护隐私

① 换Firefox,开抗指纹

Firefox是目前唯一默认提供持久化Canvas随机化的主流浏览器。每次会话内对相同绘图返回相同结果,一致性检测能过,但指纹已被干扰。

地址栏输入 about:config
搜索 privacy.resistFingerprinting → 设为 true
# 或者(新版Firefox)
搜索 privacy.fingerprintingProtection → 设为 true

② 别装那些"反指纹扩展"

反直觉,但这是真的。Canvas随机化扩展的用户群体太小(<0.1%),你一旦装了反而成了一个稀有特征——"用了Canvas随机化"本身就成了一个高熵指纹信号。

Firefox的做法是对的:从浏览器层面默认开启,让所有人都被随机化。当90%的用户都用抗指纹时,它就不是指纹信号了。

③ Tor Browser:最硬核的方案

Tor不仅随机化Canvas输出,还统一了所有浏览器的窗口尺寸(1000x900)、时区(UTC)、语言(en-US)。每个会话都是一个全新的指纹。代价是很多网站会把你当成爬虫直接拦了。

如果你是爬虫开发者:反反爬

这部分才是重点。既然指纹能识别出你不是真人,那怎么绕过?

① 戴好面具:改Playwright的默认特征

新手最容易暴露的问题:

# ❌ 新手写法——特征太明显
browser = await p.chromium.launch(headless=True)

# ✅ 正确做法——伪装成真实浏览器
context = await browser.new_context(
    viewport={"width": 1920, "height": 1080},
    user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
               "AppleWebKit/537.36 (KHTML, like Gecko) "
               "Chrome/124.0.0.0 Safari/537.36",
    locale="zh-CN",
    timezone_id="Asia/Shanghai",
    geolocation={"latitude": 39.9042, "longitude": 116.4074},
    permissions=["geolocation"],
)

少了locale、timezone这种细节,Akamai一查就知道你不是真人。

② 抹掉自动化痕迹

Playwright默认会在浏览器里留下明显的自动化标记:

await page.add_init_script("""
// 删除webdriver标志
Object.defineProperty(navigator, 'webdriver', {
    get: () => undefined
});

// 补上Chrome特有的属性
window.chrome = { runtime: {}, loadTimes: function() {}, csi: function() {}, app: {} };

// 补上plugins(无头浏览器默认是空的)
Object.defineProperty(navigator, 'plugins', {
    get: () => [1, 2, 3, 4, 5]
});

// 补上languages
Object.defineProperty(navigator, 'languages', {
    get: () => ['zh-CN', 'zh', 'en']
});
""")

你可以在自己的检测器上加一段:打开一个网站,检查 navigator.webdriver。如果是true,你的爬虫已经被认出来了。

③ 换指纹,别只换IP

很多开发者以为轮换IP就安全了。实际上指纹一查,100个IP来的都是同一台机器的Canvas输出——这比被封IP更惨,你的整个代理池都被标记了。

正确的做法是每次会话用不同的配置组合:

FINGERPRINTS = [
    {"os": "windows", "viewport": "1920x1080", "ua": "Windows NT 10.0"},
    {"os": "macos", "viewport": "1440x900", "ua": "Macintosh; Intel Mac OS X 10_15"},
    {"os": "linux", "viewport": "1366x768", "ua": "X11; Linux x86_64"},
]
import random
fp = random.choice(FINGERPRINTS)

context = await browser.new_context(
    viewport={"width": int(fp["viewport"].split("x")[0]), ...},
    user_agent=f"Mozilla/5.0 ({fp['ua']}) ..."
)

光换IP不换指纹,等于换了件外套没换脸。

④ 终极方案:用真实浏览器配置文件

Playwright支持加载真实的Chrome用户数据目录。先在普通Chrome上登录、装插件、产生浏览历史,然后把用户数据目录给Playwright用:

context = await browser.new_context(
    storage_state="real_chrome_profile.json"
)

这样你的爬虫从指纹到Cookie到LocalStorage,跟真人一模一样。当然代价是每个"指纹"需要一台物理设备。

京东画17次Canvas不是为了好玩。它是想确保:就算你换了IP、换了UA、清空了Cookie,它还是能认出你。

如果想系统地学习自动化工具和反检测技术,可以到云栈社区多逛逛,那里有更多实战经验分享。


所以

十年前,5.5%的网站用Canvas指纹追踪你。今天,12.7%。

翻了一倍多。而且拦截不住。

好消息是Firefox已经默认在做了。Safari也在收紧API。反指纹正在从"小众扩展"变成"浏览器默认行为"。

坏消息是Chrome还没动。而Chrome占了七成市场份额。

京东在你电脑上画了17次。你没同意过。你甚至不知道。

下次打开京东,你可以跑一下这个脚本看看。结果可能跟上面一样。


代码实测可跑。有问题直接留言。




上一篇:Kelly底线:长期博弈中风险控制的核心法则
下一篇:Linux与Unix稳定性深度对比:商业Unix真比Linux稳吗?
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-27 04:09 , Processed in 0.982703 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表