Undetected ChromeDriver for Go
English | 中文
A Selenium-based implementation of undetected-chromedriver for Go. This package helps bypass bot detection systems like Cloudflare, Distil Networks, and others by making ChromeDriver harder to detect.
Features
- 🚀 Automatic ChromeDriver patching to remove detection signatures
- 🔍 Bypasses common bot detection mechanisms
- 🛠️ Simple API based on tebeka/selenium
- 🎯 Removes CDP runtime properties that expose automation
- 🌐 Cross-platform support (Linux, macOS, Windows)
- ⚙️ Highly configurable with functional options
Installation
go get github.com/Flying-Tom/undetected-chromedriver-go
Quick Start
package main
import (
"log"
"time"
ucg "github.com/Flying-Tom/undetected-chromedriver-go"
)
func main() {
// Create Chrome driver
driver, err := ucg.NewChromeDriver(
ucg.WithDebug(),
)
if err != nil {
log.Fatalf("Failed to create driver: %v", err)
}
defer driver.Quit()
// Navigate to a website
if err = driver.Get("https://nowsecure.nl"); err != nil {
log.Fatalf("Failed to navigate: %v", err)
}
time.Sleep(10 * time.Second)
}
Configuration Options
The driver supports various configuration options through functional options:
driver, err := ucg.NewChromeDriver(
ucg.WithDebug(), // Enable debug logging
ucg.WithHeadless(), // Run in headless mode
ucg.WithUserDataDir("/path/to/profile"), // Set user data directory
ucg.WithPort(9515), // Set ChromeDriver port
ucg.WithVersion(120), // Specify Chrome version
ucg.WithBrowserExecutable("/path/to/chrome"), // Custom Chrome binary
ucg.WithDriverExecutable("/path/to/driver"), // Custom ChromeDriver binary
ucg.WithSuppressWelcome(), // Suppress welcome screen
ucg.WithSandbox(), // Enable sandbox (disabled by default)
ucg.WithChromeArgs( // Additional Chrome arguments
"--disable-blink-features=AutomationControlled",
),
)
Available Options
| Option |
Description |
WithDebug() |
Enable debug output |
WithHeadless() |
Run browser in headless mode |
WithUserDataDir(path) |
Set Chrome user data directory |
WithPort(port) |
Set ChromeDriver port (default: auto-assign) |
WithVersion(version) |
Specify Chrome major version (e.g., 120) |
WithDriverExecutable(path) |
Path to custom ChromeDriver binary |
WithBrowserExecutable(path) |
Path to custom Chrome binary |
WithChromeArgs(...args) |
Additional Chrome command-line arguments |
WithSuppressWelcome() |
Suppress Chrome welcome screen |
WithSandbox() |
Enable Chrome sandbox (disabled by default) |
How It Works
- Automatic Version Detection: Detects your installed Chrome version
- Driver Patching: Downloads and patches ChromeDriver to remove CDC (Chrome DevTools Protocol) signatures
- Runtime Property Removal: Removes automation-related JavaScript properties
- Stealth Configuration: Applies various arguments to make the browser appear more human-like
Advanced Example
package main
import (
"log"
"time"
ucg "github.com/Flying-Tom/undetected-chromedriver-go"
"github.com/tebeka/selenium"
)
func main() {
driver, err := ucg.NewChromeDriver(
ucg.WithDebug(),
ucg.WithHeadless(),
ucg.WithUserDataDir("/tmp/chrome-profile"),
ucg.WithSuppressWelcome(),
ucg.WithChromeArgs("--disable-blink-features=AutomationControlled"),
)
if err != nil {
log.Fatalf("Failed to create driver: %v", err)
}
defer driver.Quit()
// Navigate to bot detection test page
if err = driver.Get("https://bot.sannysoft.com"); err != nil {
log.Fatalf("Failed to navigate: %v", err)
}
time.Sleep(2 * time.Second)
// Check if navigator.webdriver is properly hidden
result, err := driver.ExecuteScript("return navigator.webdriver", nil)
if err != nil {
log.Printf("Failed to execute script: %v", err)
} else {
log.Printf("navigator.webdriver = %v", result)
}
// Get page title
title, err := driver.Title()
if err != nil {
log.Printf("Failed to get title: %v", err)
} else {
log.Printf("Page title: %s", title)
}
// Find element
elem, err := driver.FindElement(selenium.ByTagName, "body")
if err != nil {
log.Printf("Failed to find element: %v", err)
} else {
text, err := elem.Text()
if err != nil {
log.Printf("Failed to get text: %v", err)
} else {
log.Printf("Body text length: %d bytes", len(text))
}
}
time.Sleep(10 * time.Second)
}
Requirements
- Go 1.20 or higher
- Google Chrome installed (or use the built-in installer - see below)
- Internet connection (for downloading ChromeDriver)
Installing Google Chrome
If you don't have Google Chrome installed, you can use the built-in installer:
package main
import (
"log"
"github.com/Flying-Tom/undetected-chromedriver-go/installer"
)
func main() {
chromeInstaller := installer.NewChromeInstaller()
// Check if Chrome is installed
if !chromeInstaller.IsInstalled() {
log.Println("Installing Google Chrome...")
if err := chromeInstaller.Install(); err != nil {
log.Fatalf("Failed to install Chrome: %v", err)
}
}
// Now you can use undetected-chromedriver
}
Note:
- On Linux:
- Supports multiple distributions (Debian/Ubuntu, Alpine, Arch, Fedora/RHEL/CentOS)
- Automatically detects the distribution and uses the appropriate package manager
- On Alpine and Arch, installs Chromium instead of Google Chrome
- On Alpine Linux, also install
chromium-chromedriver: apk add chromium-chromedriver
- Requires
sudo privileges for installation
- On macOS, the installer will copy Chrome to
/Applications
- On Windows, the installer runs silently
- ✅ Linux
- Debian/Ubuntu (Google Chrome via .deb)
- Alpine Linux (Chromium via apk) - Requires
chromium-chromedriver package
- Arch Linux (Chromium via pacman)
- Fedora/RHEL/CentOS (Google Chrome via dnf/yum)
- ✅ macOS (Google Chrome)
- ✅ Windows (Google Chrome)
Important Note for Alpine Linux:
Alpine Linux uses musl libc instead of glibc, so the official ChromeDriver binaries from Google won't work. You must install chromium-chromedriver from the Alpine package repository:
apk add chromium chromium-chromedriver
The library will automatically detect Alpine Linux and use the system-installed chromedriver at /usr/bin/chromedriver.
Testing
# Run basic example
make run-basic
# Run advanced example
make run-advanced
# Run tests
go test ./...
Comparison with Python's undetected-chromedriver
This is a Go port inspired by ultrafunkamsterdam/undetected-chromedriver. Key differences:
- Native Go implementation using tebeka/selenium
- Functional options pattern for configuration
- Cross-platform ChromeDriver patching
- Automatic Chrome version detection
Troubleshooting
User data directory already in use
Error: session not created: probably user data directory is already in use,
please specify a unique value for --user-data-dir argument, or don't use --user-data-dir
Cause: Multiple Chrome instances are trying to use the same user data directory, or a previous Chrome instance didn't shut down properly.
Solutions:
-
Let the library auto-generate a unique directory (Recommended):
driver, err := ucg.NewChromeDriver(
// Don't use WithUserDataDir() - library will auto-generate unique temp dir
ucg.WithHeadless(),
)
The library will automatically create a unique temporary directory for each instance and clean it up when Quit() is called.
-
Use a unique directory for each instance:
import (
"fmt"
"time"
)
userDataDir := fmt.Sprintf("/tmp/chrome-profile-%d", time.Now().UnixNano())
driver, err := ucg.NewChromeDriver(
ucg.WithUserDataDir(userDataDir),
)
-
Kill existing Chrome processes:
# Linux/macOS
pkill -f chrome
# Windows
taskkill /F /IM chrome.exe
-
Always call Quit() in a defer statement:
driver, err := ucg.NewChromeDriver()
if err != nil {
log.Fatal(err)
}
defer driver.Quit() // Ensures cleanup even if program crashes
Chrome not found
Error: failed to get chrome version
Solution: Make sure Google Chrome is installed in the default location.
ChromeDriver download fails
Error: download driver: unexpected status code: 404
Solution: Specify a valid Chrome version using WithVersion() option.
Permission denied on Linux/macOS
Error: failed to start chromedriver: permission denied
Solution: The patched driver should be executable. This is handled automatically.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments