package browser import "context" // Fingerprint 是一次「这台浏览器到底长什么样」的自检读数,对应 // `DevDocs/reference/browser-stealth/10 §4.1` 的落地自检项 L2~L5。它存在的意义是 // **可证伪**:有头到底有没有生效、webdriver 有没有被点亮、屏幕/时区/语言与设定是否 // 一致 —— 全靠这组**页面自报**的数字,而不是我们以为的启动参数。 type Fingerprint struct { // Headless 来自本次会话的启动参数(我们有权威来源:参数是我们自己拼的), // 不靠嗅探 UA —— `--headless=new` 的 UA **不含** Headless 字样(reference/05)。 Headless bool `json:"headless"` // Webdriver 是 `navigator.webdriver`:false 才正常(我们不传 --enable-automation)。 Webdriver bool `json:"webdriver"` // UserAgent / Platform 是页面自报的 UA 与平台串(原样,不做任何改写)。 UserAgent string `json:"userAgent"` Platform string `json:"platform"` // Language / Languages 是页面自报的语言列表(应与启动的 --lang 一致)。 Language string `json:"language"` Languages []string `json:"languages"` // Timezone 是 `Intl.DateTimeFormat().resolvedOptions().timeZone`(应与出口 IP 同源)。 Timezone string `json:"timezone"` // ScreenWidth / ScreenHeight / ColorDepth 是 `screen.*`(应与 Xvfb 参数或真实桌面一致)。 ScreenWidth int `json:"screenWidth"` ScreenHeight int `json:"screenHeight"` ColorDepth int `json:"colorDepth"` // OuterWidth / OuterHeight 是窗口外框尺寸:有头下应 > 0(无头常见 0)。 OuterWidth int `json:"outerWidth"` OuterHeight int `json:"outerHeight"` // DevicePixelRatio / HardwareConcurrency 是两条常见的指纹量。 DevicePixelRatio float64 `json:"devicePixelRatio"` HardwareConcurrency int `json:"hardwareConcurrency"` } // fingerprintExpr 一次求值取回整组读数:分多次往返会让页面中途重定向后的读数互相 // 矛盾(一半是旧页、一半是新页)。它只在**隔离世界**里跑(session.evaluate)。 const fingerprintExpr = `(() => ({ webdriver: navigator.webdriver === true, userAgent: navigator.userAgent, platform: navigator.platform, language: navigator.language, languages: navigator.languages, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, screenWidth: screen.width, screenHeight: screen.height, colorDepth: screen.colorDepth, outerWidth: window.outerWidth, outerHeight: window.outerHeight, devicePixelRatio: window.devicePixelRatio, hardwareConcurrency: navigator.hardwareConcurrency }))()` // readFingerprint 读一次页面指纹。Headless 取自会话的启动参数,其余取自页面自报。 func readFingerprint(ctx context.Context, s *session) (Fingerprint, error) { fp := Fingerprint{Headless: s.headless} if err := s.evaluate(ctx, fingerprintExpr, &fp); err != nil { return Fingerprint{}, err } return fp, nil }