VPS वर AI agent साठी headless Chromium कसे चालवावे
VPS वरील headless Chromium मध्ये /dev/shm कमी पडणे, sandbox flags, fonts नसणे आणि leaked processes अशा अडचणी येतात. Agent सुरू करण्यापूर्वी limits सेट करा.
तुम्ही काय चालवत आहात
VPS वर headless browser म्हणजे window शिवाय चालणारा Chromium. तो एखाद्या व्यक्तीऐवजी तुमच्या code द्वारे नियंत्रित केला जातो. Server वर तो दीर्घकाळ चालणारा process tree असतो. तुमचा agent local socket द्वारे त्याच्याशी संवाद साधतो. त्याची installation एका command ने होते. त्यानंतरचे काम अधिक महत्त्वाचे असते. Browser ला machine मधून किती resources वापरता येतील यावर मर्यादा घाला. तसेच त्याचा control endpoint public internet पासून दूर ठेवा.
या guide मध्ये tool ची निवड आधीच झालेली आहे आणि आता तुम्हाला ते operate करायचे आहे, असे गृहीत धरले आहे. तुम्ही अजून crawlers आणि extractors ची तुलना करत असाल, तर self-hosted Firecrawl alternatives पासून सुरुवात करा आणि नंतर येथे परत या. खालील सर्व उदाहरणांमध्ये Playwright's Chromium वापरले आहे. Playwright स्वतःचा browser build आणि dependency installer देते. त्यामुळे हेच commands bare Ubuntu VPS वर आणि container च्या आतही चालतात. Versions August 2026 पर्यंत अद्ययावत आहेत.
अवलंबनांचा अंदाज न लावता Chromium install करा
npm i -D playwright@1.62.0
npx playwright install --with-deps chromium--with-deps Chromium ला आवश्यक असलेल्या shared libraries आणि fonts साठी apt चालवते आणि त्या टप्प्यावर root खाते आवश्यक असल्यास ते मागते. Browser build स्वतः command चालवणाऱ्या user साठी ~/.cache/ms-playwright मध्ये download होते. Server वर हे महत्त्वाचे आहे, कारण service user हा तुम्ही login करण्यासाठी वापरत असलेला user सहसा नसतो. sudo npx playwright install-deps chromium वापरून admin म्हणून system packages एकदा install करा. त्यानंतर install command आणि service unit दोन्हीमध्ये PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers सेट करा, जेणेकरून एकच copy shared करता येईल. ज्या service ला तिचा browser दिसत नाही, ती launch वेळी fail होते आणि तिने शोधलेला path संदेशात दाखवते.
Playwright ची version pin करा. प्रत्येक release एका browser build शी जोडलेले असते. त्यामुळे unpinned npm update चालू service खालील browser बदलू शकते. August 2026 नुसार Playwright 1.62 current आहे.
Chromium चे दोन builds उपलब्ध आहेत आणि ते एकच program नाहीत. Default download म्हणजे headless shell. हा लहान binary फक्त headless mode मध्ये चालतो आणि npx playwright install --with-deps --only-shell केवळ तोच install करते. Full browser chromium channel वापरल्यावर मिळतो. Playwright च्या browser documentation मध्ये त्याला "खरा Chrome browser, त्यामुळे अधिक authentic, reliable आणि अधिक features देणारा" असे म्हटले आहे. मोठ्या प्रमाणातील fetching साठी shell वापरा. एखादी site वेगळी वागत असल्यास आणि त्यामागचे कारण शोधायचे असल्यास full browser वापरा.
कंटेनरमध्ये headless browser का crash होतो
Docker प्रत्येक कंटेनरला 64 MB /dev/shm देतो. Docker documentation मध्ये स्पष्टपणे नमूद आहे: "जर size पूर्णपणे वगळला, तर system 64m वापरते". Chromium आपल्या process मधील rendered content या shared memory area मधून पाठवतो. त्यामुळे एक जड page हे area पूर्ण भरू शकते. त्यानंतर renderer बंद होतो आणि तुमचा client crashed target दाखवतो, जरी तो page तुमच्या laptop वर व्यवस्थित चालत असला तरी. काहीही बदलण्यापूर्वी कंटेनरच्या आतून size तपासा.
df -h /dev/shmयासाठी दोन वास्तविक उपाय आहेत. ते पर्यायी आहेत; दोन्ही एकत्र वापरण्याची गरज नाही. --ipc=host कंटेनरला host च्या IPC namespace मध्ये ठेवते. त्यामुळे तो host चे /dev/shm वापरतो, जे साधारणपणे RAM च्या निम्मे असते. Playwright's Docker guide हा पर्याय सुचवते, कारण तो नसल्यास "Chromium can run out of memory and crash". याची किंमत म्हणजे कंटेनर आणि host यांच्यातील IPC isolation कमी होते. --shm-size=1g private namespace कायम ठेवते आणि mount फक्त मोठा करते.
docker run --rm -it --init --ipc=host --user pwuser mcr.microsoft.com/playwright:v1.62.0-noble /bin/bash--disable-dev-shm-usage हा flag बहुतेक search results मध्ये दिसणारा उपाय आहे. मात्र तो वेगळे काम करतो: तो त्या files /dev/shm मधून temporary directory मध्ये हलवतो. /tmp disk वर असेल, तर crash ऐवजी rendering मंद होणे आणि disk writes वाढणे अशी समस्या निर्माण होते. /tmp tmpfs असेल, तर data पुन्हा RAM मध्येच राहतो आणि त्याला कोणतीही size limit नसते. त्यामुळे browser लहान VPS ची RAM पूर्णपणे वापरू शकतो. त्याऐवजी /dev/shm योग्य size ने configure करा.
--no-sandbox मुळे प्रत्यक्षात काय किंमत मोजावी लागते
Chromium प्रत्येक renderer ला Linux user namespaces वर आधारित sandbox मध्ये वेगळे ठेवतो. हा sandbox धोकादायक page आणि तुमच्या server मधील सीमारेषा आहे. तो सुरू होऊ शकला नाही, तर Chromium चालण्यास नकार देतो आणि log मध्ये पुढीलप्रमाणे ओळ दिसते:
Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Operation not permittedसामान्य सल्ला म्हणजे --no-sandbox. Chromium च्या स्वतःच्या security documentation मध्ये या पर्यायाची किंमत स्पष्टपणे सांगितली आहे: हा flag "Chromium ची महत्त्वाची security features बंद करतो आणि open web ब्राउझ करताना तो कधीही वापरू नये." Links चे अनुसरण करणारा agent व्याख्येप्रमाणे open web ब्राउझ करत असतो. त्यामुळे खरे कारण शोधा.
जवळजवळ प्रत्येक प्रकरणामागे दोन कारणे असतात. Browser root म्हणून चालवल्यास sandbox बंद होतो, कारण आधीपासून असलेले privileges तो सोडू शकत नाही. याच कारणामुळे Playwright च्या image मध्ये pwuser नावाचा सामान्य user असतो. Ubuntu 24.04 आणि त्यानंतरच्या आवृत्त्यांमध्ये AppArmor unprivileged user namespaces वर निर्बंध घालतो. कोणत्याही उपलब्ध profile मध्ये समाविष्ट नसलेल्या path वरील Chromium binary ला परवानगी नाकारली जाते. ~/.cache/ms-playwright अंतर्गत Playwright चे download अशाच path वर असते. दोन्ही बाबी तपासा:
id -u
sysctl kernel.apparmor_restrict_unprivileged_userns
sudo dmesg | grep -i userns_createsysctl मधील 1 आणि apparmor="DENIED" operation="userns_create" असलेली kernel line आढळल्यास दुसरे कारण निश्चित होते. /etc/apparmor.d/pw-chromium मध्ये त्या एकाच binary ला परवानगी द्या. त्यामुळे server वरील इतर सर्व गोष्टींसाठी निर्बंध कायम राहतात:
abi <abi/4.0>,
include <tunables/global>
profile pw-chromium /home/*/.cache/ms-playwright/*/chrome-linux/{chrome,headless_shell} flags=(unconfined) {
userns,
}sudo apparmor_parser -r /etc/apparmor.d/pw-chromium वापरून ते load करा. त्या path मध्ये browser revision असतो, त्यामुळे प्रत्येक Playwright upgrade नंतर तो बदलतो. वरील globs या बदलानंतरही लागू राहतात. एका अचूक path वर आधारित profile नंतर शांतपणे match होणे थांबवतो. त्यानंतर असंबंधित वाटणाऱ्या update नंतर browser पुन्हा fail होऊ लागतो.
स्क्रीनशॉट रिकामे किंवा चौकटींनी भरलेले का दिसतात
रिकामा स्क्रीनशॉट किंवा रिकाम्या आयतांनी भरलेला स्क्रीनशॉट सामान्यतः rendering bug पेक्षा font समस्येमुळे तयार होतो. install-deps कार्यरत base वापरतो: fonts-liberation, fonts-freefont-ttf, fonts-noto-color-emoji, fonts-unifont, जपानी भाषेसाठी fonts-ipafont-gothic, चिनी भाषेसाठी fonts-wqy-zenhei आणि थाई भाषेसाठी fonts-tlwg-loma-otf. या संचात Noto CJK नाही. त्यामुळे Korean आणि इतर काही scripts साठी fontconfig ला सापडणाऱ्या font वर fallback होते. अंदाज न लावता fontconfig कडून माहिती घ्या:
fc-match "sans-serif:lang=ko"
fc-match "sans-serif:lang=ar"
fc-list | wc -lतुमच्यासाठी आवश्यक असलेली भाषा unifont किंवा वास्तविक glyph नसलेल्या fallback font वर resolve होत असल्यास fonts-noto-core आणि fonts-noto-cjk install करा. त्यानंतर पुन्हा तपासणी करा. fontconfig त्याचे results cache करते. त्यामुळे fonts install केल्यानंतर browser restart करा. कोणतेही fonts नसलेल्या stripped image मध्ये startup वेळी Fontconfig error: Cannot load default config file log होते आणि प्रत्येक page रिकामा render होतो.
Locale आणि time zone हे fonts पासून स्वतंत्र असतात. ते page कसा दिसतो एवढेच बदलत नाहीत, तर page वर काय लिहिलेले असते तेही बदलतात. Container मध्ये सामान्यतः LANG unset असतो आणि TZ UTC वर असतो. त्यामुळे sites English content देतात आणि UTC timestamps दाखवतात. तुमचा agent त्या देशातील व्यक्तीला दिसणाऱ्या वेळांपेक्षा वेगळ्या वेळा report करतो. हे machine नुसार न करता प्रत्येक browser context नुसार set करा. त्यामुळे एकाच browser मधून वेगवेगळ्या regions साठी tasks चालवता येतील.
const context = await browser.newContext({
locale: 'en-GB',
timezoneId: 'Europe/Paris',
});लीक झालेल्या browser processes मुळे सर्व्हर swap का वापरतो
"zombie" या नावाखाली दोन वेगळ्या समस्या येतात. खरा zombie म्हणजे पूर्ण झालेला process ज्याच्या parent ने कधीही wait() कॉल केलेले नसते. त्याच्याकडे फक्त PID entry असते; इतर काहीही नसते. त्यामुळे तो memory वापरत नाही. Container मध्ये browser PID 1 म्हणून चालत असेल, तर असे processes साचू शकतात, कारण PID 1 कडे default reaper नसतो. Docker मधील --init flag हेच दुरुस्त करते. ते "forwards signals and reaps processes" करणारा छोटा init चालवते. Compose मध्ये यासाठी init: true वापरले जाते.
प्रत्यक्षात सर्व्हरला swap वापरायला लावणारी leak वेगळी असते: कोणीही बंद न केलेले live Chromium processes. newContext() आणि close() यांच्या दरम्यान एखादे task अपयशी ठरल्यास किंवा controlling script kill झाल्याने त्याचे browser tree orphaned राहिल्यास हे घडते. प्रत्येक request साठी नवीन browser सुरू करणारा code हा सर्वात खराब प्रकार आहे. त्यांची संख्या मोजा:
pgrep -c -f 'headless_shell|chrome'
ps -eo pid,ppid,rss,etime,comm --sort=-rss | head -20Tasks दरम्यान ही संख्या idle अवस्थेतील मूल्यावर परत आली पाहिजे. ती एका दिवसात वाढत असेल, तर उपाय launch flags मध्ये नाही; तो तुमच्या code मध्ये आहे. finally block मध्ये context बंद करा, SIGTERM वर browser बंद करा आणि browser एक महिना चालू ठेवण्याऐवजी निश्चित संख्येच्या tasks नंतर तो पुन्हा सुरू करा. systemd अंतर्गत stop किंवा restart केल्यावर त्या unit च्या cgroup मधील सर्वकाही kill होते. त्यामुळे sudo systemctl restart browser.service हा विश्वसनीय reset आहे. Terminal multiplexer मध्ये हाताने सुरू केलेल्या browser साठी अशी हमी नसते. त्याचे orphaned processes session संपल्यानंतरही चालू राहतात.
एका browser context ला किती RAM आवश्यक असते
हा प्रश्न अचूकपणे विचारा, कारण “एक browser” म्हणजे एकच process नसतो. Chromium एक browser process, एक GPU process, utility processes आणि प्रत्येक site साठी एक renderer process चालवतो. Site isolation मुळे cross-site iframes साठीही स्वतंत्र renderer process तयार होतो. एक BrowserContext त्याच process tree मधील स्वतंत्र cookie jar आणि storage area असतो. त्यामुळे दुसरा context कमी अतिरिक्त memory वापरतो. दुसरे page तसे नाही, कारण त्यासाठी renderer processes सुरू होतात. जाहिराती जास्त असलेले page अनेक processes सुरू करू शकते.
म्हणून तुमच्या स्वतःच्या workload अंतर्गत संपूर्ण process tree ची peak memory मोजा. दुसऱ्याच्या blog मधील आकडा येथे उपयोगाचा नाही, कारण तुमचा agent कोणती pages उघडतो त्यावर उत्तर अवलंबून असते. तुम्ही वापरणार असलेल्या machine वर आणि तुम्ही भेट देणार असलेल्या sites विरुद्ध मोजमाप करा:
sudo systemd-run --unit=browser-probe -p MemoryMax=2G -p MemorySwapMax=0 -p WorkingDirectory=/srv/agent /usr/bin/node worker.js
systemctl status browser-probeUbuntu 24.04 वर त्या output मधील Memory: line unit चा current आणि peak वापर दोन्ही दाखवते. Worker एकावेळी एक page उघडून चालवा आणि peak नोंदवा. त्यानंतर दोन pages उघडी ठेवून पुन्हा मोजा, म्हणजे दुसऱ्या page साठी प्रत्यक्षात किती memory लागते ते समजेल. त्यानंतर concurrency चे गणित सोपे आहे: एकूण RAM मधून उर्वरित system ला लागणारी memory वजा करा, काहीशे MB headroom ठेवा आणि उरलेल्या memory ला प्रत्येक worker च्या मोजलेल्या peak वापराने भागा. हा agent VPS उभारण्यासाठी किती RAM आणि CPU आवश्यक आहे, यासाठी agent VPS ला किती RAM आणि CPU आवश्यक आहे पहा.
ही मर्यादा दोन ठिकाणी लागू करा. तुमच्या code मध्ये fixed worker pool किंवा semaphore वापरा. त्यामुळे agent requests अचानक वाढल्यास नवीन browsers सुरू होण्याऐवजी त्या queue मध्ये थांबतील. OS मध्ये cgroup limit वापरा. त्यामुळे queue मधील bug मुळे संपूर्ण machine बंद पडणार नाही:
[Service]
MemoryMax=2G
MemorySwapMax=0
TasksMax=512
Restart=alwaysMemorySwapMax=0 अपेक्षेपेक्षा अधिक महत्त्वाचे आहे. ते नसल्यास cgroup limit गाठल्यावर pages swap मध्ये पाठवतो. त्यामुळे machine सुरू राहते, पण प्रत्येक request मंद होते. स्वच्छपणे होणाऱ्या failure पेक्षा हे निदान करणे अधिक कठीण असते. MemorySwapMax=0 वापरल्यास kernel त्या cgroup मधील browser tree बंद करतो, systemd unit पुन्हा सुरू करते आणि sshd टिकून राहते. Compose मधील याच controls साठी mem_limit, shm_size आणि init वापरले जातात. त्यांचे वर्णन Docker Compose मध्ये memory limits सेट करणे येथे आहे.
ब्राउझर endpoint सार्वजनिक इंटरनेटपासून दूर ठेवा
Playwright ब्राउझरला server म्हणून चालवू शकते आणि तुमच्या agent ला WebSocket URL देऊ शकते:
const { chromium } = require('playwright');
const server = await chromium.launchServer({ port: 3000 });
console.log(server.wsEndpoint());त्या endpoint वर login नाही. Playwright च्या API documentation मध्ये हे स्पष्टपणे नमूद आहे: "wsPath ची माहिती असलेली कोणतीही process किंवा web page (Playwright मध्ये चालणाऱ्यांसह) OS user वर नियंत्रण मिळवू शकते." Default host localhost आहे. तो "केवळ loopback interface कडून येणारी connections स्वीकारतो". तसेच, 0.0.0.0 सारखा explicit address दिल्यास "listening port पर्यंत पोहोचू शकणाऱ्या कोणत्याही गोष्टीसमोर browser RPC उघडतो", असा documentation मध्ये इशारा दिला आहे. Chrome चे स्वतःचे --remote-debugging-port याहूनही धोकादायक आहे. DevTools protocol मध्ये कोणत्याही प्रकारचे authentication नाही आणि तो पूर्णपणे loopback ला bind केलेला असण्यावर अवलंबून असतो.
तुम्ही प्रत्यक्षात काय publish केले आहे ते तपासा. हा तपास VPS वरून तसेच दुसऱ्या machine वरूनही करा:
ss -ltnp0.0.0.0 ला bind केलेल्या browser port वरील कोणतीही गोष्ट security finding आहे. बहुतेक providers त्यांच्या control panel मध्ये स्वतंत्र network firewall चालवतात. तुमच्या ufw rules ना त्याची कोणतीही माहिती नसते, हे लक्षात ठेवा. दुसऱ्या machine वरून SSH tunnel किंवा private VPN द्वारे endpoint पर्यंत पोहोचा:
ssh -N -L 3000:127.0.0.1:3000 you@your-vpsयेथील धोका केवळ कोणी browser चा वापर करून घेण्यापुरता मर्यादित नाही. तुम्ही नियंत्रण करू शकणारा browser म्हणजे तुमच्या network मध्ये असलेले request-forgery machine आहे. त्या socket पर्यंत पोहोचणारी व्यक्ती त्याला http://127.0.0.1:8080, तुमच्या database admin page किंवा 169.254.169.254 वरील cloud metadata address fetch करण्यास सांगू शकते. त्यानंतर page मधून response वाचता येतो. तुमच्या firewall ला request VPS मधूनच आलेली दिसते आणि ती परवानगी असलेली असते. या control endpoint ला त्या box वरील shell access इतकेच संवेदनशील समजा.
MCP servers ची रचनाही अशीच असते. npx @playwright/mcp@latest --headless --port 8931 localhost वर HTTP द्वारे सेवा देते आणि --host 0.0.0.0 हा local tool सार्वजनिक करण्यासाठी वापरला जाणारा flag आहे. प्रकल्पाच्या README मध्ये स्पष्टपणे म्हटले आहे की Playwright MCP "हे security boundary नाही". Port loopback वरच ठेवा आणि agent ला त्याच tunnel द्वारे त्याच्यापर्यंत पोहोचू द्या.
तुमचा agent वाचत असलेली पृष्ठे अविश्वसनीय इनपुट असतात
Open web वर browsing करणारा agent अनोळखी लोकांनी लिहिलेला मजकूर अशा model ला पुरवतो, ज्याच्याकडे तुमच्या सूचना देखील असतात. एखाद्या पृष्ठावर त्या model साठी लिहिलेला मजकूर असू शकतो. त्यात task सोडून देण्यास, एखादे tool call करण्यास किंवा एखाद्या URL वर data post करण्यास सांगितलेले असू शकते. Model ला दोन्ही मजकूररूपात मिळतात. त्यामुळे पृष्ठावरील शब्द आणि तुमच्या सूचना यांच्यातील फरक ओळखण्याचा त्याच्याकडे विश्वासार्ह मार्ग नसतो. Hostile पृष्ठाला मर्यादित संधी मिळतील अशा पद्धतीने setup करा.
- Browser स्वतंत्र OS user अंतर्गत चालवा. त्या user कडे SSH keys नसाव्यात आणि त्याच्या environment मध्ये cloud credentials नसावेत.
- प्रत्येक task साठी नवीन context वापरा आणि Playwright MCP सोबत
--isolatedकरा, जेणेकरून एका site वरील session पुढील पृष्ठाला उपलब्ध होणार नाही. - ज्या job मध्ये origin allowlist लागू करता येते, तेथे ती ठेवावी. Playwright MCP
--allowed-originsआणि--blocked-originsहे semicolon-separated lists म्हणून स्वीकारते. - Mail पाठवणे किंवा पैसे खर्च करणे यांसारखी state बदलणारी कोणतीही action करण्यापूर्वी human step आवश्यक करा.
याहून चांगले म्हणजे संपूर्ण browser अशा machine वर चालवा जी फेकून देऊन पुन्हा तयार करता येईल. हा युक्तिवाद disposable VM मध्ये coding agents चालवण्यासारखाच आहे. Agent चे खरे काम open-ended browsing ऐवजी search असेल, तर full browser पेक्षा narrower tool अधिक सुरक्षित असते: तुमच्या स्वतःच्या SearXNG वर आधारित search skill hostile पृष्ठ कधीही load न करता results परत करते.
FAQ
Chromium Docker मध्ये crash का होते, पण त्याच VPS वर थेट चालवल्यावर व्यवस्थित का चालते?
कारण container ला default ने 64 MB /dev/shm मिळते, तर host वर त्यापेक्षा खूप मोठे shared memory क्षेत्र असते. Chromium rendered content त्या shared memory क्षेत्रातून पाठवते. त्यामुळे मोठे page ते भरते आणि renderer बंद पडतो. खात्री करण्यासाठी container मध्ये df -h /dev/shm चालवा. त्यानंतर browser एकतर --ipc=host सह सुरू करा; यामुळे host चे shared memory वापरले जाते. किंवा --shm-size=1g सह सुरू करा; यामुळे container चे स्वतःचे shared memory क्षेत्र मोठे होते. --disable-dev-shm-usage वापरल्याने समस्या फक्त /tmp येथे हलते.
VPS वर दुसरे काहीही चालत नसेल, तर --no-sandbox सुरक्षित आहे का?
नाही. Sandbox मुळे malicious page ला उर्वरित machine पर्यंत पोहोचण्यापासून अडवता येते. Chromium च्या documentation नुसार हा flag "Chromium ची महत्त्वाची सुरक्षा वैशिष्ट्ये बंद करतो आणि open web browse करताना तो कधीही वापरू नये". Links follow करणारा agent open web browse करत असतो. त्याऐवजी मूळ कारण दुरुस्त करा. Browser root म्हणून चालवू नका. Ubuntu 24.04 वर browser binary path साठी userns, असलेले AppArmor profile जोडा. त्यामुळे त्या एकाच program साठी unprivileged user namespaces ला परवानगी मिळते.
लहान VPS वर मी किती browsers चालवू शकतो?
एखादी संख्या तशीच वापरू नका; मोजमाप करा. Chromium प्रत्येक site साठी एक renderer process सुरू करते. त्यामुळे उत्तर तुम्ही उघडत असलेल्या pages वर अवलंबून असते. MemoryMax सेट करून systemd-run अंतर्गत एक worker चालवा. systemctl status मधील Memory: line वरून peak वापर वाचा. त्यानंतर तुमची मोकळी RAM त्या peak ने भागा आणि पुरेशी headroom ठेवा. हा परिणाम दोन ठिकाणी लागू करा: तुमच्या code मध्ये queue वापरा आणि unit file मध्ये MemoryMax द्या. त्यामुळे requests चा अचानक वाढलेला भार machine ला swapping करण्याऐवजी प्रतीक्षा करेल.
माझा agent दुसऱ्या machine वरून browser शी connect होऊ शकतो का?
होय. मात्र port कधीही 0.0.0.0 वर bind करू नका. Playwright server endpoint आणि Chrome DevTools port दोन्ही password शिवाय त्यांच्यापर्यंत पोहोचू शकणारा कोणताही client स्वीकारतात. Listener 127.0.0.1 वरच ठेवा आणि connection SSH tunnel किंवा private VPN मधून पाठवा. Server वर ss -ltnp वापरून पडताळणी करा. बाहेरून port check करा. तसेच provider चा स्वतंत्र network firewall देखील तपासा.
Page स्पष्टपणे load झाले असताना माझे screenshots blank का दिसतात?
Fonts उपलब्ध नाहीत. Page च्या script ला समर्थ font नसल्यास text रिकाम्या चौकटींसारखे दिसते किंवा अजिबात render होत नाही. त्यामुळे images कमी असलेले page blank दिसू शकते. तुम्ही scrape करत असलेल्या प्रत्येक language साठी fc-match "sans-serif:lang=ko" चालवा. उत्तर generic fallback असल्यास fonts-noto-core आणि fonts-noto-cjk install करा. त्यानंतर browser restart करा, जेणेकरून fontconfig cache पुन्हा load करेल. अजिबात fonts नसलेला container startup वेळी Fontconfig error: Cannot load default config file log करतो.