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 fingerprints
  • fingerprint-injector: Injects fingerprints into Playwright/Puppeteer
  • header-generator: Generates matching HTTP headers
  • generative-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:

  • webdriver should be false or undefined
  • plugins.length should be greater than 0
  • languages should 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.