fix: derive Chromium User-Agent from actual browser version
The hardcoded DefaultChromiumUserAgent said Chrome/131 while the installed Chromium was v136. Chromium's sec-ch-ua header is generated from the real engine version, so sites comparing User-Agent against sec-ch-ua detected the mismatch as bot traffic and returned 403. Now the User-Agent is built after browser launch using browser.Version(), keeping the Chrome/N token in sync with sec-ch-ua's Chromium;v="N". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+15
-9
@@ -52,15 +52,11 @@ func initBrowser(opt BrowserOptions) (*browserInitResult, error) {
|
|||||||
return nil, ErrInvalidBrowserSelection
|
return nil, ErrInvalidBrowserSelection
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-select a User-Agent matching the browser engine when the caller hasn't set one.
|
// User-Agent auto-selection is deferred until after browser launch so we
|
||||||
if opt.UserAgent == "" {
|
// can read the real browser version and build a UA that matches the
|
||||||
switch opt.Browser {
|
// sec-ch-ua header Chromium sends automatically. A mismatched version
|
||||||
case BrowserChromium:
|
// (e.g. UA says Chrome/131 while sec-ch-ua says Chromium/136) is a
|
||||||
opt.UserAgent = DefaultChromiumUserAgent
|
// well-known bot-detection signal that causes 403s on many sites.
|
||||||
default:
|
|
||||||
opt.UserAgent = DefaultFirefoxUserAgent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect launch args and init scripts, starting with any stealth-mode presets.
|
// Collect launch args and init scripts, starting with any stealth-mode presets.
|
||||||
stealth := opt.Stealth == nil || *opt.Stealth
|
stealth := opt.Stealth == nil || *opt.Stealth
|
||||||
@@ -117,6 +113,16 @@ func initBrowser(opt BrowserOptions) (*browserInitResult, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-select User-Agent now that we know the real browser version.
|
||||||
|
if opt.UserAgent == "" {
|
||||||
|
switch opt.Browser {
|
||||||
|
case BrowserChromium:
|
||||||
|
opt.UserAgent = chromiumUserAgent(browser.Version())
|
||||||
|
default:
|
||||||
|
opt.UserAgent = DefaultFirefoxUserAgent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var viewport *playwright.Size
|
var viewport *playwright.Size
|
||||||
if opt.Dimensions.Width > 0 && opt.Dimensions.Height > 0 {
|
if opt.Dimensions.Width > 0 && opt.Dimensions.Height > 0 {
|
||||||
viewport = &playwright.Size{
|
viewport = &playwright.Size{
|
||||||
|
|||||||
+18
@@ -3,6 +3,7 @@ package extractor
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand/v2"
|
"math/rand/v2"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// stealthChromiumArgs are launch arguments that reduce automation detection for Chromium-based browsers.
|
// stealthChromiumArgs are launch arguments that reduce automation detection for Chromium-based browsers.
|
||||||
@@ -269,3 +270,20 @@ func buildFirefoxStealthScripts(p firefoxHWProfile) []string {
|
|||||||
})`,
|
})`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// chromiumUserAgent builds a Chromium User-Agent string from the actual
|
||||||
|
// browser version. This keeps the UA in sync with the sec-ch-ua header
|
||||||
|
// that Chromium sends automatically, avoiding a version mismatch that
|
||||||
|
// anti-bot systems use to detect automation.
|
||||||
|
func chromiumUserAgent(version string) string {
|
||||||
|
// version is typically "136.0.7103.25"; we need the major for the
|
||||||
|
// Chrome/MAJ.0.0.0 token and the full version for the template.
|
||||||
|
major := version
|
||||||
|
if i := strings.IndexByte(version, '.'); i > 0 {
|
||||||
|
major = version[:i]
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s.0.0.0 Safari/537.36",
|
||||||
|
major,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -420,6 +420,23 @@ func TestDefaultChromiumUserAgent_Content(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestChromiumUserAgent_MatchesVersion(t *testing.T) {
|
||||||
|
ua := chromiumUserAgent("136.0.7103.25")
|
||||||
|
if !strings.Contains(ua, "Chrome/136.0.0.0") {
|
||||||
|
t.Fatalf("expected Chrome/136.0.0.0 in UA, got %s", ua)
|
||||||
|
}
|
||||||
|
if strings.Contains(ua, "131") {
|
||||||
|
t.Fatalf("UA must not contain old hardcoded version 131: %s", ua)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChromiumUserAgent_MajorOnly(t *testing.T) {
|
||||||
|
ua := chromiumUserAgent("140")
|
||||||
|
if !strings.Contains(ua, "Chrome/140.0.0.0") {
|
||||||
|
t.Fatalf("expected Chrome/140.0.0.0 in UA, got %s", ua)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Viewport and UA defaults via mergeOptions ---
|
// --- Viewport and UA defaults via mergeOptions ---
|
||||||
|
|
||||||
func TestMergeOptions_DefaultViewport(t *testing.T) {
|
func TestMergeOptions_DefaultViewport(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user