fingerprint-suite is a modular toolkit that generates and injects realistic browser fingerprints into Playwright and Puppeteer. It helps your scrapers bypass anti-bot detection by making headless browsers look like real users browsing the web.
If you've watched your scraper get blocked mid-session by Cloudflare, DataDome, or PerimeterX, you understand the frustration. One request works fine. The next triggers a CAPTCHA wall or returns a 403.
The culprit? Browser fingerprinting.
Headless browsers leak telltale signs—missing plugins, navigator.webdriver = true, suspicious WebGL renderers—that scream "I'm a bot!" Modern anti-bot systems spot these instantly.
This guide walks you through setting up fingerprint-suite from scratch. You'll learn installation, fingerprint generation, browser injection, testing, and advanced evasion tactics that actually work in 2026.
What You'll Learn
- Installing fingerprint-suite's core npm packages
- Generating realistic fingerprints with proper constraints
- Injecting fingerprints into Playwright and Puppeteer sessions
- Testing your setup against fingerprint detection tools
- Advanced stealth techniques including proxy matching and behavioral simulation
- Using fingerprints for HTTP-only scraping without a browser
- Working with browserforge (the Python alternative)
Why You Can Trust This Guide
Traditional scrapers fail against modern fingerprinting. Sites collect hundreds of browser attributes—User-Agent, screen resolution, timezone, WebGL renderer, canvas hash, audio context—and compare them against known bot patterns.
fingerprint-suite solves this by generating statistically realistic fingerprints based on actual browser traffic patterns. It uses a Bayesian generative network trained on real-world data to produce fingerprints that blend in with normal users.
The toolkit includes four npm packages:
fingerprint-generator: Creates realistic browser fingerprintsfingerprint-injector: Injects fingerprints into Playwright/Puppeteerheader-generator: Generates matching HTTP headersgenerative-bayesian-network: Powers the statistical generation
Built by Apify, this toolkit handles the heavy lifting so you can focus on scraping logic rather than fingerprint engineering.
Step 1: Install the Required Packages
Start by installing the fingerprint-suite components. The toolkit is modular—install only what your project needs.
For Playwright Users
npm install fingerprint-injector fingerprint-generator playwright
This gives you everything needed to generate and inject fingerprints into Playwright browser contexts.
For Puppeteer Users
npm install fingerprint-injector fingerprint-generator puppeteer
Puppeteer integration works similarly but injects fingerprints at the page level rather than context level.
For Header-Only Scraping
npm install header-generator
If you're making HTTP requests without rendering JavaScript, you only need realistic headers. The header-generator package handles this independently.
Version Compatibility
fingerprint-suite requires Node.js 16 or higher. The current stable version (2.1.x as of late 2025) works with:
- Playwright 1.40+
- Puppeteer 21+
- Chrome/Chromium 115+
Check your versions before installing:
node --version
npx playwright --version
Common Installation Mistakes
Avoid these pitfalls that break fingerprint injection:
Global installation issues. Always install to your project directory, not globally. Global packages can conflict with project dependencies.
Mismatched browser versions. The fingerprint must match your browser version. Generating a Chrome 120 fingerprint while running Chrome 125 creates detectable inconsistencies.
Missing peer dependencies. The injector requires both the generator and your browser automation library. Missing any component causes import errors.
Step 2: Generate Realistic Browser Fingerprints
Fingerprint generation creates a complete browser profile—User-Agent, screen resolution, plugins, codecs, timezone, and dozens more attributes.
The key is matching your fingerprint constraints to your actual browser and target site requirements.
Basic Fingerprint Generation
import { FingerprintGenerator } from 'fingerprint-generator';
const generator = new FingerprintGenerator({
browsers: [
{ name: 'chrome', minVersion: 115 },
{ name: 'firefox', minVersion: 115 }
],
devices: ['desktop'],
operatingSystems: ['windows', 'macos', 'linux']
});
const { fingerprint, headers } = generator.getFingerprint({
locales: ['en-US', 'en'],
httpVersion: '2'
});
console.log('User-Agent:', fingerprint.userAgent);
console.log('Screen:', fingerprint.screen);
console.log('Timezone:', fingerprint.timezone);
This generates a desktop fingerprint for Chrome or Firefox on any major OS. The locales option sets the Accept-Language header preference.
Understanding the Fingerprint Object
The generator returns a rich fingerprint structure:
{
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
cookiesEnabled: true,
timezone: "America/New_York",
timezoneOffset: -300,
audioCodecs: { ogg: "probably", mp3: "maybe", wav: "probably" },
videoCodecs: { ogg: "probably", h264: "probably", webm: "probably" },
videoCard: ["Intel", "Intel(R) UHD Graphics 630"],
hardwareConcurrency: 8,
platform: "Win32",
screenResolution: [1920, 1080],
colorDepth: 24,
languages: ["en-US", "en"]
}
Every attribute must stay consistent during a scraping session. Changing your fingerprint mid-session is a major red flag.
Constraining Fingerprints to Match Proxies
Here's a technique most guides miss: match your fingerprint to your proxy's geographic location.
If you're using a German residential proxy but your fingerprint shows timezone: "America/New_York" and Accept-Language: en-US, anti-bot systems notice this mismatch immediately.
// Generate a fingerprint matching German proxy location
const germanFingerprint = generator.getFingerprint({
locales: ['de-DE', 'de', 'en'],
operatingSystems: ['windows'],
devices: ['desktop']
});
// Manually override timezone to match proxy
const customFingerprint = {
...germanFingerprint.fingerprint,
timezone: 'Europe/Berlin',
timezoneOffset: -60
};
This creates a fingerprint that matches a German IP address. The locale, timezone, and language all align with the expected user profile for that region.
Mobile Fingerprints
Mobile fingerprints have different characteristics—touch support, smaller screens, different hardware concurrency values:
const mobileGenerator = new FingerprintGenerator({
browsers: [{ name: 'chrome', minVersion: 115 }],
devices: ['mobile'],
operatingSystems: ['android', 'ios']
});
const { fingerprint } = mobileGenerator.getFingerprint();
console.log('Touch points:', fingerprint.touchSupport.maxTouchPoints);
console.log('Screen:', fingerprint.screenResolution);
Mobile fingerprints work best with mobile proxies. Using a mobile fingerprint through a datacenter IP creates obvious inconsistencies.
Step 3: Inject Fingerprints into Your Browser
Generation creates the fingerprint. Injection applies it to your browser instance.
Playwright Integration
Playwright uses browser contexts to isolate sessions. The injector creates a context with the fingerprint pre-applied:
import { chromium } from 'playwright';
import { newInjectedContext } from 'fingerprint-injector';
async function scrapeWithFingerprint() {
const browser = await chromium.launch({
headless: false // Headful mode evades more detection
});
const context = await newInjectedContext(browser, {
fingerprintOptions: {
devices: ['desktop'],
operatingSystems: ['windows'],
browsers: [{ name: 'chrome', minVersion: 120 }]
},
newContextOptions: {
viewport: { width: 1920, height: 1080 },
geolocation: { latitude: 40.7128, longitude: -74.0060 },
permissions: ['geolocation'],
timezoneId: 'America/New_York'
}
});
const page = await context.newPage();
await page.goto('https://bot.sannysoft.com');
// Take screenshot to verify fingerprint
await page.screenshot({ path: 'fingerprint-test.png' });
await browser.close();
}
scrapeWithFingerprint();
The newInjectedContext function generates a fingerprint and injects it simultaneously. You can also inject a pre-generated fingerprint:
import { FingerprintGenerator } from 'fingerprint-generator';
import { FingerprintInjector } from 'fingerprint-injector';
const generator = new FingerprintGenerator();
const { fingerprint, headers } = generator.getFingerprint({
devices: ['desktop'],
browsers: [{ name: 'chrome' }]
});
const injector = new FingerprintInjector();
const context = await browser.newContext();
await injector.attachFingerprintToPlaywright(context, { fingerprint, headers });
This approach lets you persist fingerprints across sessions or generate them with specific constraints before injection.
Puppeteer Integration
Puppeteer handles injection at the page level:
import puppeteer from 'puppeteer';
import { newInjectedPage } from 'fingerprint-injector';
async function scrapeWithPuppeteer() {
const browser = await puppeteer.launch({
headless: false,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-blink-features=AutomationControlled'
]
});
const page = await newInjectedPage(browser, {
fingerprintOptions: {
devices: ['desktop'],
operatingSystems: ['windows']
}
});
await page.goto('https://example.com');
const content = await page.content();
console.log('Page loaded:', content.length, 'bytes');
await browser.close();
}
scrapeWithPuppeteer();
The --disable-blink-features=AutomationControlled argument removes one of the most common bot detection signals.
Header-Only Injection for HTTP Requests
Sometimes you don't need a full browser. For static pages or APIs, realistic headers alone might suffice:
import { HeaderGenerator } from 'header-generator';
import fetch from 'node-fetch';
const headerGenerator = new HeaderGenerator({
browsers: ['chrome'],
operatingSystems: ['windows'],
devices: ['desktop']
});
const headers = headerGenerator.getHeaders({
httpVersion: '2',
locales: ['en-US']
});
const response = await fetch('https://httpbin.org/headers', {
headers: {
...headers,
'Accept': 'text/html,application/xhtml+xml',
'Accept-Encoding': 'gzip, deflate, br'
}
});
const data = await response.json();
console.log('Request headers:', data.headers);
This approach uses far less resources than browser automation. It works for sites without heavy JavaScript fingerprinting but fails against any client-side checks.
Step 4: Validate Your Fingerprint Setup
Before scraping production targets, verify your fingerprint passes common detection tests.
Testing Against Bot Detection Sites
Several sites test fingerprint consistency:
async function runFingerprintTests(page) {
const testSites = [
'https://bot.sannysoft.com',
'https://abrahamjuliot.github.io/creepjs/',
'https://browserleaks.com/javascript'
];
for (const url of testSites) {
await page.goto(url, { waitUntil: 'networkidle' });
await page.waitForTimeout(3000); // Let tests complete
await page.screenshot({
path: `test-${new URL(url).hostname}.png`,
fullPage: true
});
}
}
bot.sannysoft.com checks navigator properties, WebDriver flag, and Chrome runtime. Green results mean you're passing basic checks.
CreepJS performs deeper analysis including canvas, WebGL, and audio context fingerprinting. It assigns a trust score—higher scores indicate more bot-like behavior.
Checking Specific Detection Vectors
Run targeted checks for common failure points:
async function checkDetectionVectors(page) {
const results = await page.evaluate(() => {
return {
webdriver: navigator.webdriver,
plugins: navigator.plugins.length,
languages: navigator.languages,
platform: navigator.platform,
hardwareConcurrency: navigator.hardwareConcurrency,
chromeRuntime: !!window.chrome,
permissions: !!navigator.permissions,
webgl: (() => {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
if (!gl) return 'not available';
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
return debugInfo ? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) : 'unknown';
})()
};
});
console.log('Detection check results:');
console.log('- WebDriver flag:', results.webdriver ? 'DETECTED' : 'Hidden');
console.log('- Plugins count:', results.plugins);
console.log('- Languages:', results.languages);
console.log('- Platform:', results.platform);
console.log('- CPU cores:', results.hardwareConcurrency);
console.log('- Chrome runtime:', results.chromeRuntime ? 'Present' : 'Missing');
console.log('- WebGL renderer:', results.webgl);
return results;
}
What to look for:
webdrivershould befalseorundefinedplugins.lengthshould be greater than 0languagesshould match your fingerprint configuration- Chrome runtime should exist if impersonating Chrome
Known fingerprint-suite Limitations
fingerprint-suite doesn't cover everything. These detection vectors still leak:
Canvas fingerprinting. The library doesn't spoof canvas rendering. Each browser produces unique canvas hashes based on GPU and rendering engine.
WebGL fingerprinting. While fingerprint-suite spoofs the WebGL vendor string, the actual rendering behavior remains detectable.
Chrome runtime tests. Some tests check for specific Chrome functions that headless browsers implement differently.
Plugin array contents. The plugin list exists but may not contain realistic plugin data.
Anti-bot services like DataDome and PerimeterX check these vectors. fingerprint-suite reduces your detection surface but doesn't eliminate it entirely.
Step 5: Implement Advanced Stealth Techniques
Basic fingerprint injection gets you past entry-level detection. Production scraping requires additional layers.
Proxy Rotation with Fingerprint Matching
Every proxy should have its own consistent fingerprint:
class FingerprintSessionManager {
constructor() {
this.sessions = new Map();
this.generator = new FingerprintGenerator();
}
getSession(proxyConfig) {
const key = `${proxyConfig.host}:${proxyConfig.port}`;
if (!this.sessions.has(key)) {
// Generate fingerprint matching proxy location
const { fingerprint, headers } = this.generator.getFingerprint({
devices: ['desktop'],
operatingSystems: ['windows'],
locales: [proxyConfig.locale || 'en-US'],
browsers: [{ name: 'chrome', minVersion: 120 }]
});
// Override timezone to match proxy geo
fingerprint.timezone = proxyConfig.timezone || 'America/New_York';
this.sessions.set(key, { fingerprint, headers, proxy: proxyConfig });
}
return this.sessions.get(key);
}
async createContext(browser, proxyConfig) {
const session = this.getSession(proxyConfig);
const context = await browser.newContext({
proxy: {
server: `http://${proxyConfig.host}:${proxyConfig.port}`,
username: proxyConfig.username,
password: proxyConfig.password
},
timezoneId: session.fingerprint.timezone
});
const injector = new FingerprintInjector();
await injector.attachFingerprintToPlaywright(context, session);
return context;
}
}
// Usage
const manager = new FingerprintSessionManager();
const context = await manager.createContext(browser, {
host: 'proxy.example.com',
port: 8080,
username: 'user',
password: 'pass',
locale: 'de-DE',
timezone: 'Europe/Berlin'
});
This ensures your fingerprint stays consistent for each proxy. Changing proxies without changing fingerprints creates session anomalies that trigger detection.
Human-Like Behavior Simulation
Fingerprints address static detection. Behavioral analysis requires dynamic simulation:
async function humanizeInteraction(page) {
// Random mouse movements
const viewportSize = await page.viewportSize();
const x = Math.random() * viewportSize.width;
const y = Math.random() * viewportSize.height;
await page.mouse.move(x, y, { steps: 10 });
// Random scroll with variable speed
await page.evaluate(() => {
const scrollAmount = Math.random() * 500 + 200;
const scrollDuration = Math.random() * 1000 + 500;
let start = null;
const startY = window.scrollY;
function step(timestamp) {
if (!start) start = timestamp;
const progress = Math.min((timestamp - start) / scrollDuration, 1);
// Ease-out curve for natural feel
const easeOut = 1 - Math.pow(1 - progress, 3);
window.scrollTo(0, startY + scrollAmount * easeOut);
if (progress < 1) {
requestAnimationFrame(step);
}
}
requestAnimationFrame(step);
});
// Variable delays between actions
await page.waitForTimeout(Math.random() * 2000 + 500);
}
async function typeWithHumanDelay(page, selector, text) {
await page.click(selector);
for (const char of text) {
await page.keyboard.type(char, {
delay: 50 + Math.random() * 100
});
}
}
Real users don't click instantly or type at constant speeds. These functions add natural variance that behavioral analysis systems expect.
WebRTC Leak Prevention
WebRTC can expose your real IP even through proxies. Block it:
async function blockWebRTC(page) {
await page.addInitScript(() => {
// Override RTCPeerConnection
const OriginalRTC = window.RTCPeerConnection;
window.RTCPeerConnection = function(config, constraints) {
if (config && config.iceServers) {
config.iceServers = []; // Remove STUN/TURN servers
}
return new OriginalRTC(config, constraints);
};
window.RTCPeerConnection.prototype = OriginalRTC.prototype;
// Block getUserMedia
navigator.mediaDevices.getUserMedia = () =>
Promise.reject(new Error('getUserMedia blocked'));
console.log('WebRTC protection active');
});
}
Call this before navigating to any page. It prevents IP leaks through WebRTC while maintaining browser functionality.
Fingerprint Rotation Strategy
Long scraping sessions need periodic fingerprint rotation:
class FingerprintRotator {
constructor(browser, rotationInterval = 30 * 60 * 1000) {
this.browser = browser;
this.rotationInterval = rotationInterval;
this.currentContext = null;
this.generator = new FingerprintGenerator();
}
async start() {
await this.rotate();
this.rotationTimer = setInterval(async () => {
await this.rotate();
}, this.rotationInterval);
}
async rotate() {
// Close existing context
if (this.currentContext) {
await this.currentContext.close();
}
// Create new context with fresh fingerprint
this.currentContext = await newInjectedContext(this.browser, {
fingerprintOptions: {
devices: ['desktop'],
operatingSystems: ['windows', 'macos']
}
});
console.log('Fingerprint rotated at', new Date().toISOString());
}
getContext() {
return this.currentContext;
}
stop() {
clearInterval(this.rotationTimer);
}
}
Rotating every 30-60 minutes prevents fingerprint tracking across extended sessions. Match rotation to your proxy rotation schedule for consistency.
Python Alternative: browserforge
Python users can use browserforge, a reimplementation of fingerprint-suite's algorithms:
pip install browserforge
python -m browserforge update # Download model files
Basic Usage with Playwright (Python)
from browserforge.fingerprints import FingerprintGenerator
from browserforge.injectors.playwright import NewContext
from playwright.sync_api import sync_playwright
# Generate fingerprint
generator = FingerprintGenerator()
fingerprint = generator.generate(
browser='chrome',
os='windows',
device='desktop'
)
# Inject into Playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
context = NewContext(browser, fingerprint=fingerprint)
page = context.new_page()
page.goto('https://bot.sannysoft.com')
page.screenshot(path='test.png')
browser.close()
Async Playwright Integration
import asyncio
from browserforge.fingerprints import FingerprintGenerator
from browserforge.injectors.playwright import AsyncNewContext
from playwright.async_api import async_playwright
async def scrape():
generator = FingerprintGenerator()
fingerprint = generator.generate(browser='chrome', os='windows')
async with async_playwright() as p:
browser = await p.chromium.launch(headless=False)
context = await AsyncNewContext(browser, fingerprint=fingerprint)
page = await context.new_page()
await page.goto('https://example.com')
content = await page.content()
await browser.close()
return content
asyncio.run(scrape())
browserforge uses the same Bayesian network and produces fingerprints compatible with the JavaScript version. The injection API mirrors fingerprint-suite's approach.
Using Proxies with fingerprint-suite
Fingerprints alone won't save you if your IP is flagged. Residential and mobile proxies complete the stealth setup.
When scraping at scale, use rotating residential proxies that match your fingerprint's geographic profile. Datacenter IPs get blocked quickly regardless of fingerprint quality.
Configure proxy authentication in your browser context:
const context = await newInjectedContext(browser, {
fingerprintOptions: {
devices: ['desktop'],
locales: ['en-US']
},
newContextOptions: {
proxy: {
server: 'http://proxy.example.com:8080',
username: 'your_username',
password: 'your_password'
}
}
});
For high-volume scraping, implement proxy rotation that preserves fingerprint-proxy associations. Using a German fingerprint through a US proxy creates detectable mismatches that trigger blocks.
Troubleshooting Common Issues
"Cannot find module 'fingerprint-injector'"
Check your Node.js version and reinstall:
node --version # Must be 16+
rm -rf node_modules package-lock.json
npm install
Fingerprint Not Being Applied
Verify injection happens before navigation:
// Wrong - fingerprint not applied
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://example.com'); // Too late!
// Correct - use injected context
const context = await newInjectedContext(browser, { fingerprintOptions: {} });
const page = await context.newPage();
await page.goto('https://example.com'); // Fingerprint active
Still Getting Blocked
fingerprint-suite doesn't handle:
- TLS fingerprinting (JA3/JA4 hashes)
- Behavioral analysis
- Rate limiting
- CAPTCHA challenges
- IP reputation scores
For sites with advanced protection (DataDome, Cloudflare Bot Management, Akamai), you'll need additional tools or a different approach entirely.
Bonus: Complete Production-Ready Scraper Example
Here's a full scraper combining everything from this guide:
import { chromium } from 'playwright';
import { newInjectedContext, FingerprintInjector } from 'fingerprint-injector';
import { FingerprintGenerator } from 'fingerprint-generator';
class StealthScraper {
constructor(options = {}) {
this.generator = new FingerprintGenerator();
this.proxyConfig = options.proxy || null;
this.rotationInterval = options.rotationInterval || 30 * 60 * 1000;
this.browser = null;
this.context = null;
this.rotationTimer = null;
}
async initialize() {
this.browser = await chromium.launch({
headless: false,
args: [
'--disable-blink-features=AutomationControlled',
'--disable-features=IsolateOrigins,site-per-process',
'--disable-dev-shm-usage'
]
});
await this.createContext();
this.startRotation();
console.log('Stealth scraper initialized');
}
async createContext() {
if (this.context) {
await this.context.close();
}
const fingerprintOptions = {
devices: ['desktop'],
operatingSystems: ['windows'],
browsers: [{ name: 'chrome', minVersion: 120 }],
locales: this.proxyConfig?.locale ? [this.proxyConfig.locale] : ['en-US']
};
const contextOptions = {};
if (this.proxyConfig) {
contextOptions.proxy = {
server: `http://${this.proxyConfig.host}:${this.proxyConfig.port}`,
username: this.proxyConfig.username,
password: this.proxyConfig.password
};
contextOptions.timezoneId = this.proxyConfig.timezone || 'America/New_York';
}
this.context = await newInjectedContext(this.browser, {
fingerprintOptions,
newContextOptions: contextOptions
});
// Apply WebRTC protection to all pages
this.context.on('page', async (page) => {
await this.applyWebRTCProtection(page);
});
}
async applyWebRTCProtection(page) {
await page.addInitScript(() => {
const OriginalRTC = window.RTCPeerConnection;
window.RTCPeerConnection = function(config, constraints) {
if (config?.iceServers) config.iceServers = [];
return new OriginalRTC(config, constraints);
};
window.RTCPeerConnection.prototype = OriginalRTC.prototype;
navigator.mediaDevices.getUserMedia = () =>
Promise.reject(new Error('Not allowed'));
});
}
startRotation() {
this.rotationTimer = setInterval(async () => {
console.log('Rotating fingerprint...');
await this.createContext();
}, this.rotationInterval);
}
async scrape(url, options = {}) {
const page = await this.context.newPage();
try {
// Human-like delay before navigation
await page.waitForTimeout(Math.random() * 1000 + 500);
await page.goto(url, {
waitUntil: options.waitUntil || 'domcontentloaded',
timeout: options.timeout || 30000
});
// Simulate human behavior
await this.humanize(page);
if (options.waitForSelector) {
await page.waitForSelector(options.waitForSelector);
}
const content = await page.content();
return { success: true, content, url };
} catch (error) {
console.error(`Scrape failed for ${url}:`, error.message);
return { success: false, error: error.message, url };
} finally {
await page.close();
}
}
async humanize(page) {
const viewport = page.viewportSize();
// Random mouse movement
await page.mouse.move(
Math.random() * viewport.width * 0.8,
Math.random() * viewport.height * 0.8,
{ steps: 5 + Math.floor(Math.random() * 10) }
);
// Natural scroll
await page.evaluate(() => {
window.scrollBy({
top: Math.random() * 300 + 100,
behavior: 'smooth'
});
});
// Variable delay
await page.waitForTimeout(Math.random() * 1500 + 500);
}
async close() {
if (this.rotationTimer) {
clearInterval(this.rotationTimer);
}
if (this.context) {
await this.context.close();
}
if (this.browser) {
await this.browser.close();
}
}
}
// Usage example
async function main() {
const scraper = new StealthScraper({
proxy: {
host: 'proxy.example.com',
port: 8080,
username: 'user',
password: 'pass',
locale: 'en-US',
timezone: 'America/New_York'
},
rotationInterval: 45 * 60 * 1000 // 45 minutes
});
await scraper.initialize();
const urls = [
'https://example.com/page1',
'https://example.com/page2',
'https://example.com/page3'
];
for (const url of urls) {
const result = await scraper.scrape(url);
console.log(`${url}: ${result.success ? 'OK' : 'FAILED'}`);
// Delay between requests
await new Promise(r => setTimeout(r, 2000 + Math.random() * 3000));
}
await scraper.close();
}
main().catch(console.error);
This production-ready example includes:
- Automatic fingerprint rotation
- Proxy integration with locale matching
- WebRTC leak protection
- Human behavior simulation
- Error handling and cleanup
- Configurable scraping options
Understanding Anti-Bot Detection Layers
Modern anti-bot systems use multiple detection layers. fingerprint-suite addresses only one of them.
Layer 1: IP Reputation
Before fingerprinting even begins, your IP gets scored. Datacenter IPs have low reputation by default. VPN exit nodes get flagged. Only residential and mobile IPs maintain high trust scores.
fingerprint-suite doesn't help here. You need quality proxies.
Layer 2: TLS Fingerprinting
During the HTTPS handshake, your client exposes cipher suites, extensions, and protocol versions. These create a JA3/JA4 fingerprint that identifies your HTTP client.
Standard Node.js requests have fingerprints distinct from real browsers. Tools like curl-impersonate or got-scraping help here, but fingerprint-suite doesn't address TLS.
Layer 3: HTTP Header Analysis
Request headers reveal client characteristics. Header order, missing headers, and mismatched values trigger detection.
fingerprint-suite's header-generator addresses this layer effectively.
Layer 4: JavaScript Fingerprinting
This is where fingerprint-suite shines. Browser properties like navigator.userAgent, screen.width, navigator.plugins, and WebGL renderer get checked against bot patterns.
Layer 5: Behavioral Analysis
Mouse movements, scroll patterns, click timing, and navigation sequences get analyzed for human-like variance. fingerprint-suite doesn't handle this—you need behavioral simulation.
Layer 6: Canvas and Audio Fingerprinting
Rendering unique images or audio signals creates hardware-specific fingerprints. fingerprint-suite has limited coverage here—the canvas and audio hashes remain consistent per system.
Layer 7: CAPTCHA Challenges
When other signals look suspicious, CAPTCHAs gate access. fingerprint-suite bypasses some initial challenges but won't solve CAPTCHAs automatically.
Understanding these layers helps you build a complete evasion strategy rather than relying on any single tool.
Comparing fingerprint-suite Alternatives
fingerprint-suite isn't the only option. Here's how alternatives compare:
puppeteer-extra-plugin-stealth
A Puppeteer plugin that patches common detection vectors:
import puppeteer from 'puppeteer-extra';
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
puppeteer.use(StealthPlugin());
const browser = await puppeteer.launch({ headless: true });
Pros: Simple setup, actively maintained, handles webdriver flag and console.debug.
Cons: No fingerprint randomization—every session looks identical. Less effective against sites that expect fingerprint variance.
playwright-stealth
Ports puppeteer-stealth to Playwright:
import { chromium } from 'playwright-extra';
import stealth from 'puppeteer-extra-plugin-stealth';
chromium.use(stealth());
const browser = await chromium.launch();
Pros: Works with Playwright's API. Applies common evasions automatically.
Cons: Same limitation as puppeteer-stealth—no realistic fingerprint generation.
Camoufox
A patched Firefox build designed for scraping:
from camoufox.sync_api import Camoufox
with Camoufox() as browser:
page = browser.new_page()
page.goto('https://example.com')
Pros: Deep browser modifications. Uses browserforge for fingerprint generation. Firefox-based (fewer anti-bot tools target Firefox specifically).
Cons: Python only. Requires downloading a custom browser build.
When to Use fingerprint-suite
Choose fingerprint-suite when you need:
- Node.js/TypeScript support
- Playwright or Puppeteer integration
- Realistic fingerprint variance between sessions
- Header generation for HTTP-only scraping
- Geographic fingerprint matching for proxy rotation
For Python projects, use browserforge directly or consider Camoufox for heavier protection.
Performance Optimization Tips
Fingerprint injection adds overhead. Here's how to minimize impact:
Reuse Fingerprint Instances
Don't regenerate fingerprints for every request:
// Inefficient - generates new fingerprint per request
async function scrape(url) {
const context = await newInjectedContext(browser, { fingerprintOptions: {} });
const page = await context.newPage();
// ...
}
// Efficient - reuse generator and fingerprint
const generator = new FingerprintGenerator();
const { fingerprint, headers } = generator.getFingerprint();
const injector = new FingerprintInjector();
async function scrape(url) {
const context = await browser.newContext();
await injector.attachFingerprintToPlaywright(context, { fingerprint, headers });
const page = await context.newPage();
// ...
}
Pre-generate Fingerprint Pools
For high-volume scraping, generate fingerprints in advance:
class FingerprintPool {
constructor(size = 100) {
this.generator = new FingerprintGenerator();
this.pool = [];
this.index = 0;
// Pre-generate fingerprints
for (let i = 0; i < size; i++) {
this.pool.push(this.generator.getFingerprint({
devices: ['desktop'],
operatingSystems: ['windows', 'macos']
}));
}
}
get() {
const fp = this.pool[this.index];
this.index = (this.index + 1) % this.pool.length;
return fp;
}
}
const pool = new FingerprintPool(50);
const fingerprint = pool.get(); // Instant retrieval
Use Context Pooling
Keep browser contexts alive between requests:
class ContextPool {
constructor(browser, size = 5) {
this.browser = browser;
this.maxSize = size;
this.available = [];
this.inUse = new Set();
}
async acquire() {
if (this.available.length > 0) {
const context = this.available.pop();
this.inUse.add(context);
return context;
}
const context = await newInjectedContext(this.browser, {
fingerprintOptions: { devices: ['desktop'] }
});
this.inUse.add(context);
return context;
}
release(context) {
this.inUse.delete(context);
if (this.available.length < this.maxSize) {
this.available.push(context);
} else {
context.close();
}
}
}
Context pooling avoids the cost of creating new browser contexts for each request.
What's New in 2026
The fingerprinting landscape continues evolving. Here's what changed since 2025:
Chrome's Privacy Sandbox Impact
Chrome's Privacy Sandbox reduces third-party cookie access and limits some fingerprinting vectors. However, it doesn't eliminate fingerprinting—it shifts which vectors matter.
fingerprint-suite's Bayesian network adapts to current browser distributions, so generated fingerprints match what real Chrome users now expose.
Enhanced Client Hints
User-Agent Client Hints (Sec-CH-UA-* headers) provide structured browser information. Anti-bot systems now verify Client Hints match User-Agent strings.
fingerprint-suite generates consistent Client Hints automatically:
const { headers } = generator.getFingerprint();
console.log(headers['sec-ch-ua']);
// '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"'
console.log(headers['sec-ch-ua-platform']);
// '"Windows"'
WebGPU Fingerprinting
WebGPU is replacing WebGL for graphics operations. Anti-bot systems are beginning to fingerprint WebGPU adapters and limits.
fingerprint-suite doesn't yet spoof WebGPU properties. Sites using WebGPU fingerprinting may detect mismatches between spoofed WebGL and actual WebGPU values.
Audio Context Entropy
Audio fingerprinting via AudioContext oscillators and analyzers has become more sophisticated. fingerprint-suite handles basic audio codec support but doesn't spoof the actual audio signal fingerprint.
For sites checking audio context deeply, you'll need additional patches or accept this as a detection vector.
Final Thoughts
fingerprint-suite handles the technical complexity of browser fingerprinting—generating realistic profiles and injecting them into your automation. It's a solid foundation for any scraping project facing fingerprint-based detection.
But remember: fingerprinting is just one layer of modern anti-bot systems. Success requires combining fingerprints with residential proxies, behavioral simulation, and request rate management.
Start with the basic setup in Step 3. Test against detection sites in Step 4. Then layer in the advanced techniques as you scale.
The anti-bot landscape evolves constantly. What works today might not work next month. Keep testing, keep adapting, and stay ahead of detection updates.
FAQ
Does fingerprint-suite work with headless browsers?
Yes, but headful mode (setting headless: false) evades more detection. Headless browsers have additional signatures that some anti-bot systems check specifically.
Can fingerprint-suite bypass Cloudflare?
It helps with Cloudflare's basic JavaScript challenges and fingerprint checks. Advanced Cloudflare protection (Bot Management, Turnstile) requires additional techniques beyond fingerprinting.
How often should I rotate fingerprints?
Every 30-60 minutes for long sessions, or with each proxy rotation. Never change fingerprints mid-session on the same proxy—this creates detectable inconsistencies.
What's the difference between fingerprint-generator and fingerprint-injector?
The generator creates fingerprint data. The injector applies that data to browser instances. You can use the generator alone for HTTP requests, but browser automation requires both.
Does fingerprint-suite work with Python?
Use browserforge instead—it's a Python reimplementation with identical functionality. It supports both Playwright and Pyppeteer.