I’m working on creating a stealth puppeteer setup and having trouble with language configuration. I can successfully modify the user agent and change navigator.language from “en-US, in” to “es-ES, es”. However, I cannot override navigator.languages which stays stuck at “en-US, en” no matter what I try.
const puppeteerExtra = require("puppeteer-extra");
const stealthPlugin = require("puppeteer-extra-plugin-stealth");
const stealth = stealthPlugin();
puppeteerExtra.use(stealth);
const AgentOverride = require("puppeteer-extra-plugin-stealth/evasions/user-agent-override");
const userAgent = AgentOverride({
locale: "es-ES,es;q=0.9",
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.141 Safari/537.36",
platform: "MacIntel"
});
const pathModule = require('path');
const siteList = require('./sites.json');
async function start() {
puppeteerExtra.use(userAgent);
const browserInstance = await puppeteerExtra.launch({
headless: false,
userDataDir: "./data",
ignoreHTTPSErrors: true,
args: [
"--lang=es-ES,es;q=0.9",
"--no-sandbox",
"--disable-dev-shm-usage",
"--disable-gpu"
]
});
const newPage = await browserInstance.newPage();
const scriptPath = pathModule.join(__dirname, 'lib/main.js');
for (const site of siteList) {
require(scriptPath)(newPage, site);
}
}
start().catch(err => { console.error("Error occurred:", err); });
When I check the property descriptors for navigator.languages, it shows:
value: "en-US"
writable: true
enumerable: true
configurable: true
But it should be:
value: "es-ES"
writable: false
enumerable: true
configurable: false
The configuration works fine on blank pages but disappears once I navigate to any URL. How can I properly override navigator.languages to match my desired locale?
You’re handling this at the browser level when you should manage it at the workflow level.
I’ve hit similar detection issues scraping multilingual sites. Even if you patch navigator.languages correctly, sites are getting better at catching automation through inconsistencies between browser properties.
You need a proper automation platform that handles stealth configs automatically. I switched to Latenode for browser automation because it manages all the fingerprinting stuff behind the scenes.
Latenode sets up browser flows that handle language spoofing, user agents, and other stealth requirements without manually patching every navigator property. It runs browser instances in clean environments without puppeteer’s telltale signs.
You can also build the entire scraping workflow visually instead of debugging JavaScript property overrides. Way cleaner.
Check it out: https://latenode.com
The Problem:
You’re trying to override the navigator.languages property in Puppeteer to spoof the browser’s language settings, but the override is not persistent after navigating to a new URL. The navigator.languages property is being reset by the browser after page navigation. Your existing puppeteer-extra-plugin-stealth setup isn’t sufficient to address this persistent behavior.
Understanding the “Why” (The Root Cause):
Modern browsers actively work to detect and prevent automated browser control. They often reset dynamically generated properties like navigator.languages after page loads as a security measure against scraping and automation. Simply setting the property isn’t enough; the browser’s internal mechanisms will overwrite your changes. The key is to use a method that overrides the property descriptor itself, making it non-configurable so the browser can’t easily reset it. This is also why simply setting the locale property within userAgent is insufficient.
Step-by-Step Guide:
-
Override navigator.languages using a getter: The most effective way to override navigator.languages persistently is to use page.evaluateOnNewDocument(). This injects the override script before the page loads, making it resistant to subsequent browser resets. This creates a getter function that returns the desired language array and prevents the property from being reconfigured.
await newPage.evaluateOnNewDocument(() => {
Object.defineProperty(navigator, 'languages', {
get: () => ['es-ES', 'es'],
configurable: false,
enumerable: true
});
});
-
Ensure Consistent HTTP Headers: Inconsistencies between JavaScript overrides and HTTP headers are a major red flag for websites. To avoid this, set your Accept-Language header to match your overridden navigator.languages value. Add this code after creating your page:
await newPage.setExtraHTTPHeaders({'Accept-Language': 'es-ES,es;q=0.9,en;q=0.8'});
-
Verify Your Implementation: After adding the code above, relaunch your Puppeteer script. Inspect the navigator.languages property on a target page using your browser’s developer tools to confirm it shows ["es-ES", "es"] even after navigation.
Common Pitfalls & What to Check Next:
- Timing: Ensure that the
evaluateOnNewDocument call happens before any navigation to a new URL. If your navigation happens too early, it will override your language definition.
- Stealth Plugin Interference: Although
puppeteer-extra-plugin-stealth is helpful, it might have its own language overrides that conflict with this approach. Temporarily disable the plugin to isolate the impact of the new method, or review its configuration for potential conflicts.
- Website-Specific Anti-Scraping: Some websites employ more sophisticated anti-scraping techniques that go beyond simple checks on
navigator.languages. If the issue persists, consider more advanced techniques like rotating proxies, user agents, or using a headless browser that is specifically designed for bypassing anti-scraping measures.
Still running into issues? Share your (sanitized) config files, the exact command you ran, and any other relevant details. The community is here to help!
Had the same issue with locale spoofing. The stealth plugin runs its language override too early - before the browser’s internal language stuff kicks in after navigation. Even when you set the locale in userAgent, it doesn’t stick.
What fixed it for me: combine evaluateOnNewDocument with proper Accept-Language headers. You’ve got to set the navigator property AND make sure your HTTP headers match.
Add this to your page setup:
await newPage.setExtraHTTPHeaders({'Accept-Language': 'es-ES,es;q=0.9,en;q=0.8'});
The real problem is when navigator.languages and HTTP headers don’t match. Sites check these against each other - if navigator.languages says Spanish but you’re sending English headers, that’s a dead giveaway. Keep your JavaScript overrides and network headers consistent.
the stealth plugin isn’t overriding navigator.languages properly after u navigate. try using page.evaluateOnNewDocument() to inject the language override before each page loads - itll stick even when u hit new urls, unlike what u got now.
This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.