ছোট VPS-এ Moli headless browser কীভাবে সেটআপ করবেন
ছোট VPS-এ headless Chrome চালানো কঠিন। Moli ব্যবহার করে CDP-এর মাধ্যমে এজেন্ট কানেক্ট করার উপায় জানুন। এর সীমাবদ্ধতা এবং কোন ক্ষেত্রে এটি কাজ করবে না তা বিস্তারিত দেখুন।
একটি headless browser যা ছোট VPS-এ চলে
Moli হলো AI এজেন্টদের জন্য একটি headless browser, যা এতই ছোট যে এটি এমন VPS-এ self-host করা যায় যেখানে headless Chrome জায়গা পায় না। এটি Rust-এ লেখা একটি ব্রাউজার ইঞ্জিন, Chromium-এর কোনো wrapper নয়। এটি Chrome DevTools Protocol (CDP) সমর্থন করে, যে প্রোটোকলটি আপনার অটোমেশন লাইব্রেরি ইতিমধ্যে ব্যবহার করে। আপনি একটি মাত্র binary ইনস্টল করবেন, moli serve রান করবেন, তারপর Playwright বা আপনার নিজস্ব এজেন্ট কোডকে http://127.0.0.1:9222-এর দিকে নির্দেশ করবেন।
কিছু ইনস্টল করার আগে এর সীমাবদ্ধতাগুলো পড়ে নিন। প্রকল্পটি তার পরিধি স্পষ্টভাবে উল্লেখ করেছে: কোনো GUI ব্রাউজার নেই, কোনো GPU compositor নেই, Chrome-এর সাথে পিক্সেল-টু-পিক্সেল মিল নেই এবং উচ্চ মানের Canvas বা মিডিয়া প্লেব্যাক নেই। যেসব পেজে এগুলোর প্রয়োজন হয়, সেগুলো কাজ করবে না। Playwright-এর অধীনে আসল Chrome-ই বিকল্প হিসেবে থাকবে এবং শেষ অংশে দেখানো হয়েছে কীভাবে সিদ্ধান্ত নেবেন কোন পেজগুলোর জন্য সেটি প্রয়োজন।
নিচের প্রতিটি কমান্ড প্রকল্পের README এবং তাদের প্রকাশিত স্কিল ফাইল থেকে নেওয়া হয়েছে, যা আগস্ট 2026-এ যাচাই করা হয়েছে। চার্টের প্রতিটি সংখ্যা প্রকল্পের নিজস্ব ইঞ্জিন সম্পর্কে তাদের প্রকাশিত তথ্য, এই সাইটের কোনো পরিমাপ নয়, এবং প্রতিটি চার্টের ক্যাপশনে তা উল্লেখ করা আছে। আপনি যদি এখনও ইঞ্জিন নির্বাচন করে থাকেন, তবে VPS-এ এজেন্টদের জন্য headless browser-এর বিস্তৃত সমীক্ষায় বিকল্পগুলো সম্পর্কে জানতে পারবেন।
হেডলেস (headless) Chrome কেন এত বেশি মেমরি ব্যবহার করে?
Chrome একটি মাল্টি-প্রসেস ব্রাউজার। প্রতিটি ট্যাব এবং প্রতিটি ক্রস-সাইট iframe-এর নিজস্ব রেন্ডারার প্রসেস থাকে এবং প্রতিটি রেন্ডারার তার নিজস্ব V8 হিপ (heap) ও গ্রাফিক্স বাফার বহন করে। ডেস্কটপের জন্য এই ডিজাইনটি সঠিক, কারণ সেখানে একটি ট্যাব ক্র্যাশ করলে যেন পুরো উইন্ডো বন্ধ না হয়ে যায়। একটি 2 GB VPS-এর ক্ষেত্রে এর অর্থ হলো, একটি সাধারণ ব্রাউজিং ধাপ আপনার মূল অ্যাপ্লিকেশনের চেয়েও বেশি মেমরি খরচ করতে পারে।
প্রকল্পটি চারটি ইঞ্জিন ব্যবহার করে 192টি মিশ্র পাবলিক URL ক্রল করেছে এবং ফলাফল প্রকাশ করেছে।
The data behind this chart
[
{
"engine": "Moli",
"useful_pages": 103,
"median_rss_mib": 73
},
{
"engine": "Chrome Headless",
"useful_pages": 101,
"median_rss_mib": 773
},
{
"engine": "Lightpanda",
"useful_pages": 85,
"median_rss_mib": 40
},
{
"engine": "Obscura",
"useful_pages": 57,
"median_rss_mib": 39
}
]Chrome Headless 101টি কার্যকর পেজ এবং Moli 103টি কার্যকর পেজ রিটার্ন করেছে, তাই এই নমুনায় ইঞ্জিন দুটি ওয়েবের প্রায় সমান অংশ পড়েছে। মেমরির ক্ষেত্রে এদের পার্থক্য স্পষ্ট: Chrome-এর ক্ষেত্রে মিডিয়ান RSS (resident set size, অর্থাৎ একটি প্রসেস বাস্তবে RAM-এ যে পরিমাণ মেমরি ধরে রাখে) হলো 773 MiB, যেখানে Moli-এর ক্ষেত্রে তা 73 MiB। এই ফলাফলটি বিশ্বাসযোগ্য কারণ এটি প্রসেস আর্কিটেকচারের ওপর ভিত্তি করে এসেছে। আপনার পেজগুলোর ক্ষেত্রে সঠিক অনুপাতটি এমন হবে, তা ধরে নেওয়া ঠিক হবে না।
মিডিয়ান মানটি আপনার জন্য সমস্যার কারণ নয়। সমস্যার কারণ হলো পিক (peak) বা সর্বোচ্চ ব্যবহার। যখন একটি 2 GB-এর বক্সে মেমরি শেষ হয়ে যায়, তখন কার্নেল একটি প্রসেস নির্বাচন করে সেটিকে বন্ধ (kill) করে দেয় এবং সেই রেকর্ডটি dmesg -T বা journalctl -k-এ জমা হয়:
Out of memory: Killed process 4211 (chrome) total-vm:2318936kB, anon-rss:1418324kB, file-rss:0kB, shmem-rss:0kB, UID:1000 pgtables:3540kB oom_score_adj:0আপনার এজেন্ট কখনোই সেই লাইনটি দেখতে পায় না। এটি এমন একটি ব্রাউজার দেখে যা সাড়া দেওয়া বন্ধ করে দিয়েছে, সাধারণত এটি Playwright-এর page.goto: Page crashed এর মতো কোনো এরর বা ক্লোজড টার্গেট হিসেবে দেখা দেয়। সেই এরর মেসেজে মেমরির কোনো উল্লেখ থাকে না, আর এই কারণেই ছোট বক্সে কোনো এজেন্ট হঠাৎ ব্যর্থ হলে সবার আগে OOM (out of memory) কিলার চেক করা উচিত। পিক মেমরির জন্য সাইজিং করা ঠিক এজেন্ট VPS-এর জন্য RAM এবং CPU নির্বাচন করার মতোই একটি কাজ।
Moli বাইনারি ইনস্টল করা এবং একটি নির্দিষ্ট ভার্সন পিন করা
প্রকল্পটি GitHub releases-এ একটি শেল ইনস্টলার এবং প্রি-বিল্ট টারবল প্রকাশ করে। আগস্ট 2026 অনুযায়ী বর্তমান রিলিজ হলো 1.0.1, যা 18 আগস্ট 2026 তারিখে প্রকাশিত হয়েছে। এই গাইডে উল্লেখিত বেঞ্চমার্কের পরিসংখ্যানগুলো প্রকল্পটির পক্ষ থেকে 0.1.1 ভার্সনে পরিমাপ করা হয়েছিল, তাই সেগুলোকে আপনার ইনস্টল করা বিল্ডের নিশ্চয়তা হিসেবে না দেখে ইঞ্জিনের একটি সাধারণ ধারণা হিসেবে বিবেচনা করুন।
ভার্সনটি পিন করুন। যে ইনস্টলার সবসময় latest রেজলভ করে, তা পরবর্তী রিবিল্ডের সময় আপনার এজেন্টকে ভিন্ন একটি ব্রাউজার ইঞ্জিনে নিয়ে যাবে। ব্রাউজারের আচরণে কোনো পরিবর্তন এলে তা আগে থেকে পরিকল্পনা করে করা উচিত, হঠাৎ আবিষ্কার করা উচিত নয়।
শেল ইনস্টলার হলো দ্রুততম উপায়, তবে এটি চালানোর আগে পড়ে নেওয়া বুদ্ধিমানের কাজ।
curl --proto '=https' --tlsv1.2 -fsSL \
-o /tmp/moli-installer.sh \
https://github.com/lexmount/moli/releases/download/v1.0.1/moli-installer.sh
less /tmp/moli-installer.sh
sh /tmp/moli-installer.shস্ক্রিপ্টটি চালানোর আগে পড়ে নিন। এটি ছোট। এটি আপনার uname -m থেকে একটি আর্কাইভ বেছে নেয় এবং তারপর একটি একক বাইনারি ~/.local/bin-এ আনপ্যাক করে। x86_64 আর্কিটেকচারে এটি moli-x86_64-unknown-linux-gnu.tar.gz নেয় এবং Arm সার্ভারে এটি aarch64 আর্কাইভটি নেয়, ফলে Arm এবং x86 উভয় VPS প্ল্যানই এর আওতাভুক্ত। অন্য কোথাও ইনস্টল করতে MOLI_INSTALL_DIR সেট করুন। লক্ষ্য করুন এটি ভার্সনের জন্য কী রেজলভ করে: এটি নতুন রিলিজটি নেয়, সেই ট্যাগটি নয় যেখান থেকে আপনি স্ক্রিপ্টটি এনেছেন। প্রথমবার দেখার জন্য এটি ঠিক আছে, কিন্তু পুনরাবৃত্তিযোগ্য (repeatable) রিবিল্ডের জন্য এটি সঠিক নয়।
তাই স্থায়ী কোনো কিছুর জন্য, ইনস্টলার যা করে তা নিজে হাতে করুন এবং আর্কাইভের নাম নির্দিষ্ট করে দিন। এটি করার মাধ্যমে আপনি বাইনারিটিকে এমন জায়গায় রাখতে পারবেন যেখানে সিস্টেম সার্ভিস সেটিকে খুঁজে পাবে এবং এটি ডাউনলোড করা স্ক্রিপ্ট সরাসরি শেল-এ পাইপ করা থেকে আপনাকে বাঁচাবে।
cd /tmp
curl --proto '=https' --tlsv1.2 -fsSLO \
https://github.com/lexmount/moli/releases/download/v1.0.1/moli-x86_64-unknown-linux-gnu.tar.gz
mkdir -p moli-pkg
tar -xzf moli-x86_64-unknown-linux-gnu.tar.gz -C moli-pkg --strip-components=1
sudo install -m 0755 moli-pkg/moli /usr/local/bin/moli
moli --versionmoli --version আপনার পিন করা ভার্সনটি প্রিন্ট করাই হলো সম্পূর্ণ চেক। ইনস্টলারের ঠিক পরেই moli: command not found আসার মানে হলো ইনস্টলেশন ডিরেক্টরিটি আপনার PATH-এ নেই, এবং ইনস্টলার একটি লাইনে সেই ডিরেক্টরির নাম প্রিন্ট করে যা আপনাকে যুক্ত করতে হবে।
moli fetch দিয়ে ওয়ান-শট এক্সট্রাকশন
একটি এজেন্ট ব্রাউজারকে যেসব কাজ দেয়, তার বেশিরভাগই হলো "এই URL-টি লোড করো এবং কী আছে তা জানাও"। এর জন্য কোনো সার্ভারের প্রয়োজন নেই। moli fetch ইঞ্জিন চালু করে, একটি পেজ লোড করে, একটি আর্টিফ্যাক্ট স্ট্যান্ডার্ড আউটপুটে লিখে প্রস্থান করে, তাই কলগুলোর মাঝে কোনো কিছুই মেমোরি দখল করে রাখে না।
moli fetch --dump markdown --wait-until networkidle https://example.com
moli fetch --dump semantic_tree_text --wait-selector "main" https://example.com
moli fetch --dump json --wait-until networkidle https://example.com > page.jsonপ্রথমটি পেজটিকে Markdown হিসেবে প্রিন্ট করে, যা # Example Domain দিয়ে শুরু হয়। মডেলকে দেওয়ার জন্য Markdown সবচেয়ে সাশ্রয়ী, কারণ এটি মার্কআপ বাদ দিয়ে শুধু টেক্সট রাখে। semantic_tree_text রোল এবং কাঠামো বজায় রাখে, যা নেভিগেশন-নির্ভর পেজের ক্ষেত্রে প্রয়োজন, যেখানে লিঙ্কের গুরুত্ব গদ্যের মতোই সমান। --dump json HTTP স্ট্যাটাস এবং রিকোয়েস্ট ট্রেস বহন করে, তাই যখন কোনো ফেচ খালি আসে এবং কারণ জানার প্রয়োজন হয়, তখন এটি ব্যবহার করুন।
ওয়েট স্ট্র্যাটেজি নির্ধারণ করে আপনি কন্টেন্ট পাবেন নাকি একটি খালি শেল। --wait-until networkidle নেটওয়ার্ক শান্ত হলে রিটার্ন করে। --wait-until domstable DOM পরিবর্তন হওয়া বন্ধ হলে রিটার্ন করে, যা এমন পেজের জন্য ভালো পছন্দ যেখানে ব্যাকগ্রাউন্ডে পোলিং চলতে থাকে এবং নেটওয়ার্ক কখনোই পুরোপুরি শান্ত হয় না। --wait-selector আপনার দেওয়া একটি সিলেক্টরের জন্য অপেক্ষা করে। এটিই একমাত্র স্ট্র্যাটেজি যা আপনি যে পেজটি ফেচ করছেন সে সম্পর্কে জানে, ফলে যখন আপনি টার্গেট সম্পর্কে নিশ্চিত থাকেন, তখন এটিই সবচেয়ে নির্ভরযোগ্য।
স্ক্রিনশট এবং PDF-এর জন্য রিয়েল লেআউট প্রয়োজন, এবং লেআউট ডিফল্টভাবে বন্ধ থাকে:
moli fetch --layout --dump screenshot https://example.com > page.png
moli fetch --layout --dump screenshot_full https://example.com > full-page.png
moli fetch --layout --dump pdf https://example.com > page.pdfREADME-তে ডিফল্ট লেআউট পলিসিকে LayoutPolicy::Mock বলা হয়েছে: জ্যামিতি নকল করা হয় এবং কিছুই পেইন্ট করা হয় না, কারণ লেআউট এবং পেইন্ট ব্রাউজারের সবচেয়ে ব্যয়বহুল অংশ। এই ডিফল্ট সেটিংসের কারণেই উপরের মেমোরি ফিগারগুলো এমন দেখায়। এর মানে হলো, একটি খালি PNG সাধারণত একটি ভাঙা পেজের চেয়ে বরং একটি অনুপস্থিত --layout ফ্ল্যাগের কারণে হয়।
আপনার বেছে নেওয়া URL-এর পরিবর্তে এজেন্ট যে URL খুঁজে পেয়েছে তার জন্য --block-private-networks যোগ করুন। একটি এজেন্ট যা পেজে পড়া লিঙ্ক অনুসরণ করে, তাকে ভুল পথে চালিত করে ক্লাউড ইনস্ট্যান্স ক্রেডেনশিয়ালের জন্য http://169.254.169.254/ ফেচ করানো হতে পারে, অথবা localhost-এর এমন কোনো ডাটাবেস পোর্ট ফেচ করানো হতে পারে যা ইন্টারনেটের জন্য উন্মুক্ত করার কথা ছিল না। এই ফ্ল্যাগটি প্রাইভেট অ্যাড্রেস স্পেসে নেভিগেশন প্রত্যাখ্যান করে, এবং --block-cidrs এটিকে আরও সংকুচিত করে। যখন কাজটি একটি পেজ পড়ার পরিবর্তে ক্রলিং হয়, তখন সেই পাইপলাইনের গঠন self-hosted Firecrawl alternatives-এ আলোচনা করা হয়েছে, এবং এর আগের ধাপ, অর্থাৎ URL খুঁজে বের করার বিষয়টি a SearXNG-backed search skill for agents-এ কভার করা হয়েছে।
CDP-এর মাধ্যমে Moli-তে একটি এজেন্ট নির্দেশ করা
যে এজেন্ট অনেকগুলো ধাপ জুড়ে নেভিগেট করে এবং ক্লিক করে, তার জন্য পরিবর্তে সার্ভারটি চালান।
moli serve --host 127.0.0.1 --port 9222127.0.0.1 এবং port 9222 হলো ডিফল্ট, তাই একটি সাধারণ moli serve কমান্ড দিলেই তা শুধুমাত্র loopback-এ bind হয়। যেকোনো স্থায়ী কনফিগারেশনের ক্ষেত্রে উভয়ই স্পষ্টভাবে লিখে রাখুন, কারণ আপনার সার্ভিস ফাইলটি পরবর্তীতে যিনি পড়বেন, তাকে যেন ডিফল্ট মানগুলো মনে রাখতে না হয়।
ক্লায়েন্ট সংযুক্ত করার আগে সার্ভারটি পরীক্ষা করুন:
curl -s http://127.0.0.1:9222/json/versionএকটি সচল সার্ভার একটি JSON অবজেক্টের মাধ্যমে উত্তর দেয় যাতে একটি webSocketDebuggerUrl ফিল্ড থাকে, এবং সেই URL-টিই CDP ক্লায়েন্ট সংযুক্ত করার জন্য ব্যবহার করে। curl: (7) Failed to connect to 127.0.0.1 port 9222: Connection refused মানে হলো কোনো কিছু listen করছে না, তাই আপনি যে টার্মিনালে এটি শুরু করেছিলেন সেটি দেখুন, অথবা সার্ভিস হিসেবে চলার সময় journalctl -u moli -n 50 ব্যবহার করুন। /json/list ওপেন টার্গেটগুলোর তালিকা দেখায় এবং /json/protocol এই বিল্ডে বাস্তবায়িত ডোমেইনগুলোর তালিকা দেখায়, যার মাধ্যমে আপনি জানতে পারবেন আপনার প্রয়োজনীয় CDP মেথডটি এখানে আছে কি না।
Playwright নিজস্ব ব্রাউজার চালু করার পরিবর্তে সেই এন্ডপয়েন্টে সংযুক্ত হয়:
import { chromium } from "playwright";
const browser = await chromium.connectOverCDP("http://127.0.0.1:9222");
const context = browser.contexts()[0];
const page = context.pages()[0] ?? await context.newPage();
await page.goto("https://example.com");
console.log(await page.locator("body").innerText());
await browser.close();এখানে গুরুত্বপূর্ণ লাইনটি হলো connectOverCDP, chromium.launch() নয়। এখানে কোনো চাইল্ড Chromium প্রসেস নেই, তাই executablePath এবং --no-sandbox-এর মতো সাধারণ কন্টেইনার ফ্ল্যাগগুলোর কাজ করার মতো কিছু নেই। একই কারণে, প্রক্সি, কুকি এবং ইউজার-এজেন্ট সেটিংস Moli সার্ভারের নিজস্ব ফ্ল্যাগ হিসেবে পাঠাতে হয়। সম্পূর্ণ Chrome প্রোটোকলের পরিবর্তে নির্বাচিত CDP কাভারেজ আশা করুন: একটি স্পষ্ট unsupported-method এরর হলো ইঞ্জিনের সীমাবদ্ধতা, আপনার কোডের কোনো বাগ নয়।
দুটি সার্ভার ফ্ল্যাগ নির্ধারণ করে এজেন্ট কী করতে পারবে। --layout প্রকৃত জ্যামিতি (geometry) চালু করে, যা কোঅর্ডিনেট ক্লিক এবং স্ক্রিনশটের জন্য প্রয়োজন। --resource ঐচ্ছিক ইমেজ, ফন্ট এবং মিডিয়া নিয়ে আসে; এটি প্রতিবার পেজ লোড হওয়ার সময় ব্যান্ডউইথ এবং মেমোরি খরচ করে, তাই কোনো পেজের প্রয়োজন প্রমাণিত না হওয়া পর্যন্ত এটি বন্ধ রাখুন। --profile-dir রানগুলোর মধ্যে কুকি এবং স্টোরেজ ধরে রাখে, এটি ছাড়া প্রতিটি রানই অস্থায়ী (disposable) হয়ে যায়।
The data behind this chart
[
{
"engine": "Moli",
"cdp_ready_ms": 34.85,
"peak_pss_mib": 102.46,
"processes": 1
},
{
"engine": "Chromium",
"cdp_ready_ms": 169.37,
"peak_pss_mib": 348.82,
"processes": 11
}
]প্রজেক্টের নমুনা এজেন্ট ওয়ার্কলোডে, Moli একটি CDP কানেকশন গ্রহণ করেছে 34.85 ms সময়ে, যেখানে Chromium-এর ক্ষেত্রে সময় লেগেছে 169.37 ms। সর্বোচ্চ PSS (proportional set size, শেয়ারড পেজগুলো প্রসেসগুলোর মধ্যে ভাগ করে মেমোরি গণনা করা) ছিল 102.46 MiB, যেখানে Chromium-এর ক্ষেত্রে তা ছিল 348.82 MiB। কাঠামোগত পার্থক্যটি শেষ কলামে দেখা যাচ্ছে: 1 টি প্রসেস, যেখানে Chromium-এর ক্ষেত্রে ছিল 11 টি। একটি প্রসেস মানে systemd-এর তত্ত্বাবধানের জন্য একটি একক বিষয় এবং cgroup-এর সীমাবদ্ধতার জন্য একটি একক ইউনিট, যা পরবর্তী সেকশনটিকে সংক্ষিপ্ত করে তুলেছে।
moli serve-কে loopback-এ systemd service হিসেবে চালানো
যখন কোনো agent-এর জন্য একটি browser প্রস্তুত থাকা প্রয়োজন, তখন server-টিকে service হিসেবে চালান। যখন প্রয়োজন নেই, তখন প্রতি URL-এর জন্য moli fetch ব্যবহার করা চালিয়ে যান, কারণ একটি idle server-ও তার memory দখল করে রাখে।
port 9222-কে public interface-এ রাখবেন না। CDP-তে কোনো ধরনের authentication ধাপ নেই। যে কেউ এই port-এ পৌঁছাতে পারলে সে browser-টিকে নিয়ন্ত্রণ করতে পারবে এবং browser যা যা দেখতে পায় তার সবকিছুই পড়তে পারবে, যার মধ্যে আপনার profile directory-র cookies-ও অন্তর্ভুক্ত। এটিকে 127.0.0.1-এ সীমাবদ্ধ রাখুন। SSH tunnel (ssh -L 9222:127.0.0.1:9222 user@your-vps) বা private VPN interface-এর মাধ্যমে অন্য কোনো machine থেকে এতে প্রবেশ করুন এবং agent-কে তার নিজের tunnel-এর দিক থেকে http://127.0.0.1:9222-এ connect করতে দিন।
একটি service user তৈরি করুন, তারপর unit file-টি তৈরি করুন:
sudo useradd --system --home-dir /var/lib/moli --shell /usr/sbin/nologin moli/etc/systemd/system/moli.service লিখুন:
[Unit]
Description=Moli headless browser CDP server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=moli
Group=moli
ExecStart=/usr/local/bin/moli serve --host 127.0.0.1 --port 9222 --profile-dir /var/lib/moli/profile --block-private-networks
Restart=on-failure
RestartSec=2
StateDirectory=moli
MemoryAccounting=yes
MemoryMax=768M
NoNewPrivileges=yes
PrivateTmp=yes
ProtectHome=yes
ProtectSystem=strict
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now moli.service
systemctl status moli.service
curl -s http://127.0.0.1:9222/json/versionsystemctl status-এ active (running) দেখানো উচিত এবং curl-এর discovery JSON ফেরত দেওয়া উচিত। ProtectSystem=strict এই unit-এর জন্য পুরো filesystem-কে read-only হিসেবে mount করে, যে কারণে StateDirectory=moli এখানে ঐচ্ছিক নয়: এটি service user-এর মালিকানাধীন /var/lib/moli তৈরি করে এবং শুধুমাত্র সেই path-টিকে writable করে তোলে। যে unit শুরু হওয়ার পর journalctl -u moli-এ permission error নিয়ে বন্ধ হয়ে যায়, সেটি প্রায় সবসময়ই এমন কোথাও লেখার চেষ্টা করে যা ProtectSystem কেবল read-only করেছে, তাই সেই path-টিকে state directory-র অধীনে সরিয়ে নিন।
আপনার application-এর পাশে এটি নিরাপদে চালানোর জন্য MemoryMax=768M প্রয়োজন। unit-টি তার নিজস্ব cgroup পায় এবং যখন সেই cgroup তার সীমা অতিক্রম করে, তখন kernel তার ভেতরের কোনো কিছুকে বন্ধ করে দেয় এবং box-এর বাকি অংশকে অক্ষত রাখে। journal-এ এটি রেকর্ড হয়:
moli.service: A process of this unit has been killed by the OOM killer.এই লাইনটিকে sizing signal হিসেবে পড়ুন। হয় page-গুলো আপনার পরিকল্পনার চেয়ে বেশি ভারী, অথবা সীমাটি খুব কম। আপনার নিজের page-গুলোর পরিমাপ থেকে সংখ্যাটি নির্ধারণ করুন, যা পরবর্তী অংশে রয়েছে। একই accounting flag-গুলো box-এর অন্য যেকোনো service-কে সীমাবদ্ধ রাখে এবং capping memory and CPU with systemd বাকিগুলোর ক্ষেত্রে কাজ করে।
নিজের সার্ভারের সর্বোচ্চ মেমোরি ব্যবহার পরিমাপ করুন
প্রকাশিত পরিসংখ্যানগুলো অন্য কারো হার্ডওয়্যার এবং অন্য কারো পেজ থেকে নেওয়া হয়েছে। আপনার সার্ভার টিকে থাকবে কি না তা নির্ভর করে সর্বোচ্চ মেমোরি ব্যবহারের ওপর, আর এই ব্যবহার সম্পূর্ণভাবে নির্ভর করে আপনি কী লোড করছেন তার ওপর। তাই সার্ভারের আকার নির্ধারণের আগে পরিমাপ করুন।
একবার কোনো কিছু ফেচ (fetch) করার জন্য time বাইনারিটি ব্যবহার করুন, যা একই নামের শেল বিল্টইন কমান্ডের চেয়ে অনেক বেশি তথ্য প্রদান করে:
sudo apt update && sudo apt install -y time
/usr/bin/time -v moli fetch --dump markdown --wait-until networkidle https://example.com > /dev/nullআউটপুটটি রিসোর্স পরিসংখ্যানের একটি ব্লকের মাধ্যমে শেষ হয়, যার মধ্যে Maximum resident set size (kbytes) অন্তর্ভুক্ত থাকে। MiB-এ রূপান্তর করতে একে 1024 দিয়ে ভাগ করুন। আপনার এজেন্ট সচরাচর যে দশটি পেজ ভিজিট করে তার ওপর এটি চালান, example.com-এর ওপর নয়। গড় ফলাফলের পরিবর্তে সবচেয়ে খারাপ ফলাফলটি বিবেচনা করুন, কারণ OOM killer সর্বোচ্চ ব্যবহারের ওপর ভিত্তি করেই কাজ করে।
সার্ভিসের ক্ষেত্রে, কার্নেল তার cgroup-এর জন্য যে কাউন্টারটি রাখে তা পড়ুন:
cat /sys/fs/cgroup/system.slice/moli.service/memory.peak
systemd-cgtop -mmemory.peak হলো বাইট কাউন্ট এবং এটি ইউনিটটি সর্বশেষ চালু হওয়ার পর থেকে সর্বোচ্চ ব্যবহারের নির্দেশক (high-water mark), তাই রিস্টার্ট দিলে এটি রিসেট হয়ে যায়। এই সংখ্যাটির চেয়ে MemoryMax-এর মান বেশি হতে হবে এবং এর ওপর আপনার ভিজিট করা হয়নি এমন সবচেয়ে ভারী পেজটির জন্য পর্যাপ্ত জায়গা রাখতে হবে। systemd-cgtop -m প্রতিটি ইউনিটের বর্তমান ব্যবহার দেখায়, যা সার্ভারের কোন সার্ভিসটি বর্তমানে সবচেয়ে বেশি মেমোরি খরচ করছে তা দেখার দ্রুততম উপায়।
Moli কোথায় ব্যর্থ হয় এবং কখন আপনার এখনও Chrome প্রয়োজন?
এই প্রজেক্টটি 1,308টি তুলনামূলক ব্রাউজার অটোমেশন টাস্কের একটি বেঞ্চমার্ক চালায় এবং বেশ কয়েকটি ইঞ্জিনের স্কোর প্রকাশ করে।
The data behind this chart
[
{
"engine": "Chrome",
"success_rate_pct": 99.85
},
{
"engine": "Moli 0.1.1",
"success_rate_pct": 81.88
},
{
"engine": "Kitesurf",
"success_rate_pct": 62.08
},
{
"engine": "Lightpanda",
"success_rate_pct": 53.29
},
{
"engine": "Obscura",
"success_rate_pct": 44.88
}
]এই 5 টি ইঞ্জিনের মধ্যে, Moli 0.1.1 মোট টাস্কের 81.88 শতাংশ সম্পন্ন করেছে এবং রেফারেন্স ইঞ্জিন হিসেবে Chrome সম্পন্ন করেছে 99.85 শতাংশ। যেহেতু প্রজেক্টটি নিজস্ব সুইটের ওপর ভিত্তি করে এই স্কোর দিয়েছে, তাই এটিকে একটি নিরপেক্ষ ফলাফলের পরিবর্তে তাদের নিজস্ব দাবি হিসেবে দেখা উচিত।
ব্যবহারিক দিক থেকে বিষয়টি সহজ। Chrome যে কাজগুলো সম্পন্ন করতে পারে, তার মধ্যে প্রতি পাঁচটি কাজের প্রায় একটি Moli-তে ব্যর্থ হয়। যদি আপনার এজেন্ট আপনার নিয়ন্ত্রণে থাকা নির্দিষ্ট কিছু পেজ ভিজিট করে, তবে এই অনুপাতটি খুব একটা গুরুত্বপূর্ণ নয়, কারণ আপনার পেজগুলো হয় কাজ করবে অথবা করবে না—যা আপনি আজ বিকেলেই যাচাই করে নিতে পারবেন। কিন্তু যদি আপনার এজেন্ট উন্মুক্ত ওয়েব ব্রাউজ করে, তবে এটি একটি বাস্তব ব্যর্থতার হার, যার জন্য আপনাকে আগে থেকেই পরিকল্পনা করতে হবে।
প্রজেক্টের ঘোষিত পরিধি অনুযায়ী কী কী ব্যর্থ হবে তা অনুমান করা সম্ভব।
- যেসব অ্যাপ্লিকেশন তাদের ইন্টারফেস DOM-এর পরিবর্তে Canvas এলিমেন্টে ড্র করে, কারণ Canvas-এর নির্ভুলতা স্পষ্টভাবে প্রজেক্টের পরিধির বাইরে।
- WebGL বা GPU কম্পোজিটিং প্রয়োজন এমন যেকোনো কিছু, কারণ এতে কোনো GPU কম্পোজিটর নেই।
- DRM-সুরক্ষিত ভিডিও এবং জটিল মিডিয়া প্লেব্যাক।
- যেসব ভিজ্যুয়াল টেস্ট Chrome-এর সাথে পিক্সেল-নিখুঁত স্ক্রিনশট মেলানোর দাবি করে, কারণ Chrome-এর সাথে সামঞ্জস্য বজায় রাখা এর লক্ষ্য নয়।
প্রজেক্টটি যে আরেকটি পরিসংখ্যান উল্লেখ করেছে—1.612 মিলিয়ন ওয়েব প্ল্যাটফর্ম টেস্ট সফলভাবে সম্পন্ন হওয়া—তা মূলত স্ট্যান্ডার্ড কভারেজ সম্পর্কে একটি বিবৃতি। এটি আপনার এজেন্টের ভিজিট করা সাইটগুলোর জন্য কোনো নিশ্চয়তা নয়। একটি পেজ শুধুমাত্র সু-সমর্থিত স্ট্যান্ডার্ড ব্যবহার করেও বট চেকের কারণে ব্যর্থ হতে পারে, এবং কোনো ইঞ্জিনের স্কোরই তা কভার করে না।
তাই ডিজাইনে ফলব্যাক (fallback) ব্যবস্থা রাখুন। প্রতিটি URL প্রথমে Moli-তে পাঠান। যখন কোনো পেজ থেকে কোনো তথ্য না আসে বা কোনো সিলেক্টর খুঁজে পাওয়া না যায়, তখন সেই নির্দিষ্ট URL-টি Playwright ব্যবহার করে আসল Chrome-এর মাধ্যমে পুনরায় চেষ্টা করুন। এটি বড় কোনো মেশিনে অথবা এমন কোনো শিডিউলে করুন যেখানে 773 MiB প্রসেস চালানো সাশ্রয়ী। বেশিরভাগ এজেন্ট তাদের সময়ের বড় অংশ সাধারণ পেজগুলোতে ব্যয় করে, তাই ছোট ইঞ্জিনটি মূল কাজের চাপ সামলায় এবং ব্যয়বহুল ইঞ্জিনটি বাকি কাজগুলো সম্পন্ন করে।
FAQ
Moli কি আমার এজেন্টের জন্য headless Chrome-এর বিকল্প হতে পারে?
পেজ পড়া, টেক্সট এক্সট্রাক্ট করা এবং সাধারণ ক্লিকের ক্ষেত্রে সাধারণত হ্যাঁ। প্রজেক্টটির নিজস্ব 1,308টি টাস্কের বেঞ্চমার্কে এটি 81.88 শতাংশ সফল হয়েছে, যেখানে Chrome-এর সাফল্যের হার 99.85 শতাংশ। অর্থাৎ, প্রতি পাঁচটি টাস্কের মধ্যে একটির ক্ষেত্রে Moli-এর সীমাবদ্ধতা থাকতে পারে। Canvas-রেন্ডার করা অ্যাপ্লিকেশন, WebGL এবং DRM ভিডিওর ক্ষেত্রে এটি কাজ করে না। সব কিছু পরিবর্তন না করে, শুধুমাত্র সেই নির্দিষ্ট URL-গুলোকে আসল Chrome-এ রাউট করুন।
একটি VPS-এ Moli চালানোর জন্য কতটুকু RAM প্রয়োজন?
প্রজেক্টটির রিপোর্ট অনুযায়ী, 192টি URL ক্রল করার সময় এর median RSS 73 MiB এবং একটি স্যাম্পল এজেন্ট এপিসোডে peak PSS 102.46 MiB, যেখানে headless Chrome-এর ক্ষেত্রে এই মান 773 MiB। এগুলো তাদের নিজস্ব পেজের তথ্য। একবার ব্যবহারের জন্য /usr/bin/time -v ব্যবহার করে moli fetch কলের মাধ্যমে আপনার নিজের ব্যবহারের পরিমাপ করুন, অথবা সার্ভিসের জন্য /sys/fs/cgroup/system.slice/moli.service/memory.peak পড়ুন, তারপর আপনার দেখা সর্বোচ্চ মানের চেয়ে বেশি MemoryMax সেট করুন।
ইন্টারনেটে port 9222 উন্মুক্ত রাখা কি নিরাপদ?
না। CDP-তে কোনো authentication নেই, তাই যে কেউ এই পোর্টে পৌঁছাতে পারলে আপনার ব্রাউজার নিয়ন্ত্রণ করতে পারবে এবং ব্রাউজার যা কিছু অ্যাক্সেস করতে পারে তা পড়তে পারবে। --host 127.0.0.1 বজায় রাখুন এবং SSH tunnel বা private VPN interface-এর মাধ্যমে অন্য মেশিন থেকে endpoint-এ পৌঁছান। যদি অন্য কোনো ঠিকানায় bind করতেই হয়, তবে তা private interface-এ রাখুন এবং firewall দিয়ে অ্যাক্সেস নিয়ন্ত্রণ করুন।
আমার স্ক্রিনশট কেন খালি আসছে, অথবা ক্লিক কেন কাজ করছে না?
Layout ডিফল্টভাবে বন্ধ থাকে। README অনুযায়ী ডিফল্ট পলিসি হলো LayoutPolicy::Mock, তাই এলিমেন্টের জ্যামিতি সঠিক থাকে না এবং পেজের কোনো বক্সের ওপর নির্ভরশীল কাজগুলো কাজ করে না। সার্ভারটি moli serve --layout দিয়ে শুরু করুন, অথবা moli fetch-এ --layout যোগ করুন, তাহলে স্ক্রিনশট এবং কোঅর্ডিনেট পাথ কাজ করা শুরু করবে। ছবি না আসার কারণ ভিন্ন একটি ফ্ল্যাগ: --resource।
আমার Moli-এর কোন ভার্সনটি ইনস্টল করা উচিত?
একটি ভার্সন নির্দিষ্ট করুন এবং সেটি রেকর্ড করে রাখুন। আগস্ট 2026 অনুযায়ী বর্তমান রিলিজ হলো 1.0.1, কিন্তু প্রজেক্টটি যে বেঞ্চমার্ক প্রকাশ করেছে তা 0.1.1 ভার্সনে পরিমাপ করা হয়েছিল। তাই অন্যদের সাথে তুলনা করার সময় এই দুটি ভার্সন অদলবদল করা যাবে না। শেল ইনস্টলারের ওপর নির্ভর না করে সেই ট্যাগের moli-x86_64-unknown-linux-gnu.tar.gz ডাউনলোড করুন এবং বাইনারিটি নিজে ইনস্টল করুন, কারণ শেল ইনস্টলারটি আপনার ফেচ করা ট্যাগের পরিবর্তে নতুন রিলিজটি ইনস্টল করে ফেলে। এরপর moli --version দিয়ে নিশ্চিত করুন।