CAPTCHA yang terus muncul setiap kali skrip scraping dijalankan ulang biasanya bukan soal proxy atau sinyal browser yang mencurigakan — akar masalahnya lebih sederhana: sesi selalu dimulai dari nol. Tanpa cookie atau riwayat dari eksekusi sebelumnya, reCAPTCHA dan Cloudflare Turnstile memperlakukan tiap kunjungan sebagai pengunjung baru — dan tantangan pun lebih sering muncul.
Solusinya: persistensi sesi — menyimpan cookie, profil Chrome, dan localStorage/sessionStorage sehingga browser Anda "diingat" sistem CAPTCHA. Panduan ini membahas enam teknik dengan Selenium dan Puppeteer, dari cookie sederhana sampai rotasi multi-profil. Frekuensi CAPTCHA turun signifikan, tapi tidak pernah hilang total — untuk tantangan yang tetap lolos, API CaptchaAI jadi jaring pengaman.
Kenapa Sesi Browser Persisten Menekan Frekuensi CAPTCHA
Sistem deteksi CAPTCHA menilai risiko berdasarkan riwayat, bukan cuma perilaku sesaat. Semakin banyak sinyal kepercayaan yang terbawa dari sesi sebelumnya, semakin jarang tantangan muncul:
| Status Sesi | Frekuensi CAPTCHA | Kenapa |
|---|---|---|
| Sesi baru tanpa cookie | Tinggi | Tidak ada riwayat kepercayaan |
| Sesi dengan cookie Google | Sedang | reCAPTCHA mengenali akun yang login |
| Sesi yang sudah "dihangatkan" | Rendah | Sinyal perilaku browsing organik |
| Profil persisten berumur beberapa hari | Sangat rendah | Skor kepercayaan sudah terbentuk |
Contoh dari lapangan: agensi pemantauan harga di Jakarta dengan 20 worker scraping di VPS ap-southeast-3 biasanya mulai di paket STANDARD CaptchaAI ($30/bulan, 15 thread). Tanpa sesi persisten, tiap restart worker memicu tantangan baru sampai volume solve memaksa upgrade ke ADVANCE ($90/bulan, 50 thread). Setelah tiap worker punya profil Chrome sendiri dan sesi dihangatkan lebih dulu, tim bisa bertahan di STANDARD berbulan-bulan.
Simpan Cookie Sesi dengan Selenium (Python)
Teknik paling sederhana: simpan cookie ke file JSON setelah tiap run, lalu muat ulang sebelum menyentuh halaman target.
Class PersistentSession: Simpan dan Pulihkan Cookie
Class di bawah menangani siklus penuh — simpan, muat, dan jalankan task — dalam satu pemanggilan:
import json
import os
import time
from selenium import webdriver
class PersistentSession:
def __init__(self, profile_name="default", cookie_dir="./sessions"):
self.profile_name = profile_name
self.cookie_dir = cookie_dir
self.cookie_file = os.path.join(cookie_dir, f"{profile_name}_cookies.json")
os.makedirs(cookie_dir, exist_ok=True)
def create_driver(self):
options = webdriver.ChromeOptions()
options.add_argument("--window-size=1920,1080")
return webdriver.Chrome(options=options)
def save_cookies(self, driver):
"""Save all cookies to disk."""
cookies = driver.get_cookies()
with open(self.cookie_file, "w") as f:
json.dump(cookies, f, indent=2)
print(f"Saved {len(cookies)} cookies to {self.cookie_file}")
def load_cookies(self, driver, domain=None):
"""Restore cookies from disk."""
if not os.path.exists(self.cookie_file):
print("No saved cookies found")
return False
with open(self.cookie_file) as f:
cookies = json.load(f)
loaded = 0
for cookie in cookies:
# Filter by domain if specified
if domain and domain not in cookie.get("domain", ""):
continue
# Remove problematic fields
cookie.pop("sameSite", None)
cookie.pop("storeId", None)
try:
driver.add_cookie(cookie)
loaded += 1
except Exception as e:
print(f"Skip cookie {cookie.get('name')}: {e}")
print(f"Loaded {loaded}/{len(cookies)} cookies")
return loaded > 0
def run_with_session(self, url, callback):
"""Run a task with persistent session."""
driver = self.create_driver()
try:
# Navigate to domain first (required for cookie loading)
driver.get(url)
time.sleep(1)
# Load saved cookies
self.load_cookies(driver)
# Refresh to apply cookies
driver.get(url)
time.sleep(2)
# Execute task
result = callback(driver)
# Save updated cookies
self.save_cookies(driver)
return result
finally:
driver.quit()
# Usage
session = PersistentSession("target-site")
def my_task(driver):
# Check if already logged in
if "dashboard" in driver.current_url:
print("Session restored — no login needed")
return driver.page_source
else:
print("Need to login + solve CAPTCHA")
# Solve CAPTCHA with CaptchaAI...
return None
result = session.run_with_session("https://example.com", my_task)
Penting: driver.get(url) harus dipanggil sebelum add_cookie() — Selenium menolak cookie untuk domain yang belum dikunjungi di sesi berjalan. Karena itu class di atas navigasi dua kali: sekali untuk membuka domain, sekali lagi setelah cookie dimuat.
Direktori Data Pengguna Chrome untuk Persistensi Profil Penuh
Cookie saja tidak cukup. Untuk persistensi paling lengkap — cookie, localStorage, cache, riwayat, dan status internal browser — pakai direktori data pengguna Chrome (--user-data-dir) alih-alih menyimpan cookie manual:
import os
from selenium import webdriver
PROFILE_DIR = os.path.abspath("./chrome-profiles/profile-1")
def create_persistent_driver():
options = webdriver.ChromeOptions()
options.add_argument(f"--user-data-dir={PROFILE_DIR}")
options.add_argument("--profile-directory=Default")
options.add_argument("--no-sandbox")
return webdriver.Chrome(options=options)
# First run: builds fresh profile
driver = create_persistent_driver()
driver.get("https://example.com")
# ... solve CAPTCHA, login, etc.
driver.quit()
# Second run: same profile, cookies and state preserved
driver = create_persistent_driver()
driver.get("https://example.com")
# Often skips CAPTCHA because session is recognized
driver.quit()
Efeknya terasa di run kedua: profil yang sama dipakai ulang membuat halaman yang biasa memicu CAPTCHA langsung mengenali sesi sebagai tepercaya.
Yang Tersimpan di Direktori Profil dan Dampaknya
| Yang Tersimpan | Dampak terhadap CAPTCHA |
|---|---|
| Cookie | Token sesi, cookie NID Google |
| LocalStorage | Token kepercayaan khusus situs |
| IndexedDB | Status internal reCAPTCHA |
| Cache | Halaman termuat lebih cepat |
| Riwayat | Sinyal pola browsing |
| Service Worker | Pemeriksaan CAPTCHA di latar belakang |
Direktori profil dan file cookie bisa memuat token akun pengguna sungguhan — simpan di storage yang aksesnya dibatasi.
Konteks Sesi Persisten di Puppeteer
Pola yang sama berlaku di Puppeteer lewat opsi userDataDir — cocok untuk tim yang sudah pakai Node.js untuk QA atau scraping:
const puppeteer = require("puppeteer-extra");
const StealthPlugin = require("puppeteer-extra (mode standar)");
const path = require("path");
puppeteer.use(StealthPlugin());
const USER_DATA_DIR = path.resolve("./chrome-profiles/profile-1");
async function runWithPersistentProfile() {
const browser = await puppeteer.launch({
headless: false,
userDataDir: USER_DATA_DIR,
args: [
"--no-sandbox",
"--window-size=1920,1080",
],
});
const page = await browser.newPage();
await page.goto("https://example.com", { waitUntil: "networkidle0" });
// Check if session is active
const isLoggedIn = await page.evaluate(() =>
document.querySelector(".user-menu") !== null
);
if (isLoggedIn) {
console.log("Session active — no CAPTCHA needed");
} else {
console.log("Session expired — solving CAPTCHA");
// Solve with CaptchaAI...
}
await browser.close();
}
Cek isLoggedIn ini praktis: sebelum keluar biaya solve, pastikan sesi yang tersimpan masih aktif — kadang solve bisa dilewati sama sekali.
localStorage dan sessionStorage dalam Sesi Persisten
Direktori profil Chrome sudah membawa localStorage otomatis, tapi kalau Anda memakai penyimpanan cookie manual, localStorage dan sessionStorage perlu disimpan terpisah lewat execute_script:
def save_storage(driver, filepath):
"""Save localStorage and sessionStorage."""
storage = driver.execute_script("""
return {
localStorage: Object.fromEntries(
Object.entries(localStorage)
),
sessionStorage: Object.fromEntries(
Object.entries(sessionStorage)
),
};
""")
with open(filepath, "w") as f:
json.dump(storage, f, indent=2)
def restore_storage(driver, filepath):
"""Restore localStorage and sessionStorage."""
if not os.path.exists(filepath):
return
with open(filepath) as f:
storage = json.load(f)
for key, value in storage.get("localStorage", {}).items():
driver.execute_script(
f"localStorage.setItem('{key}', '{value}')"
)
for key, value in storage.get("sessionStorage", {}).items():
driver.execute_script(
f"sessionStorage.setItem('{key}', '{value}')"
)
Pakai pola ini kalau infrastruktur Anda tidak cocok dengan --user-data-dir yang persisten — misalnya container yang dibuat ulang di setiap run.
Strategi Pemanasan Sesi (Session Warmup)
Sesi baru memicu lebih banyak CAPTCHA. "Menghangatkan" sesi dengan perilaku browsing organik sebelum menyentuh halaman target membangun kepercayaan lebih cepat:
import random
import time
def warm_session(driver, warm_urls=None):
"""Simulate organic browsing to build session trust."""
default_urls = [
"https://www.google.com",
"https://www.google.com/search?q=weather",
"https://www.wikipedia.org",
]
urls = warm_urls or default_urls
for url in urls:
driver.get(url)
time.sleep(random.uniform(2, 5))
# Simulate scroll
driver.execute_script(
f"window.scrollTo(0, {random.randint(200, 800)})"
)
time.sleep(random.uniform(1, 3))
print(f"Session warmed with {len(urls)} pages")
# Usage
driver = create_persistent_driver()
warm_session(driver)
# Now navigate to target — lower CAPTCHA chance
driver.get("https://staging.example.com/form")
Praktik murah tapi efektif: beberapa detik membuka Google atau Wikipedia sebelum task inti sering cukup mengubah klasifikasi sesi dari "baru" ke "tepercaya sebagian" di mata reCAPTCHA v3.
Manajer Sesi Multi-Profil untuk Rotasi Worker
Untuk automasi paralel, satu profil saja tidak cukup — dipakai terus oleh banyak proses justru mempercepat rate limiting. SessionManager mengatur rotasi profil berdasarkan pemakaian paling jarang:
import os
import json
import time
from datetime import datetime
class SessionManager:
"""Manage multiple persistent browser profiles."""
def __init__(self, base_dir="./sessions"):
self.base_dir = base_dir
self.meta_file = os.path.join(base_dir, "profiles.json")
os.makedirs(base_dir, exist_ok=True)
if os.path.exists(self.meta_file):
with open(self.meta_file) as f:
self.profiles = json.load(f)
else:
self.profiles = {}
def _save_meta(self):
with open(self.meta_file, "w") as f:
json.dump(self.profiles, f, indent=2)
def get_profile_dir(self, name):
return os.path.join(self.base_dir, f"profile-{name}")
def create_profile(self, name, proxy=None):
"""Create a new browser profile."""
profile_dir = self.get_profile_dir(name)
os.makedirs(profile_dir, exist_ok=True)
self.profiles[name] = {
"created": datetime.now().isoformat(),
"last_used": None,
"use_count": 0,
"proxy": proxy,
"captcha_solves": 0,
}
self._save_meta()
return profile_dir
def get_least_used_profile(self):
"""Get the profile used least recently."""
if not self.profiles:
return None
return min(
self.profiles.items(),
key=lambda x: x[1].get("last_used") or ""
)[0]
def record_use(self, name, solved_captcha=False):
"""Record profile usage."""
if name in self.profiles:
self.profiles[name]["last_used"] = datetime.now().isoformat()
self.profiles[name]["use_count"] += 1
if solved_captcha:
self.profiles[name]["captcha_solves"] += 1
self._save_meta()
def get_stats(self):
"""Print profile statistics."""
for name, meta in self.profiles.items():
print(f"Profile: {name}")
print(f" Uses: {meta['use_count']}")
print(f" CAPTCHAs: {meta['captcha_solves']}")
print(f" Last used: {meta.get('last_used', 'never')}")
print()
# Usage
manager = SessionManager()
# Create 5 rotating profiles
for i in range(5):
manager.create_profile(f"worker-{i}")
# Get next profile to use
profile_name = manager.get_least_used_profile()
profile_dir = manager.get_profile_dir(profile_name)
# Use with Selenium
options = webdriver.ChromeOptions()
options.add_argument(f"--user-data-dir={os.path.abspath(profile_dir)}")
driver = webdriver.Chrome(options=options)
# After task
manager.record_use(profile_name, solved_captcha=True)
manager.get_stats()
Field captcha_solves membantu memantau profil mana yang paling sering memicu tantangan — kalau satu profil jauh lebih sering solve dari yang lain, itu biasanya tanda IP-nya mulai dicurigai.
Rotasi dan Kedaluwarsa Cookie
Cookie kedaluwarsa hanya membebani penyimpanan, dan menggabungkan cookie baru dengan lama perlu logika yang mencegah duplikasi per domain:
from datetime import datetime, timezone
def clean_expired_cookies(cookie_file):
"""Remove expired cookies from saved file."""
if not os.path.exists(cookie_file):
return
with open(cookie_file) as f:
cookies = json.load(f)
now = datetime.now(timezone.utc).timestamp()
valid = [c for c in cookies if c.get("expiry", float("inf")) > now]
removed = len(cookies) - len(valid)
if removed > 0:
with open(cookie_file, "w") as f:
json.dump(valid, f, indent=2)
print(f"Removed {removed} expired cookies")
def merge_cookies(existing_file, new_cookies):
"""Merge new cookies with existing, preferring newer values."""
existing = []
if os.path.exists(existing_file):
with open(existing_file) as f:
existing = json.load(f)
# Index by (name, domain)
cookie_map = {}
for c in existing:
key = (c["name"], c.get("domain", ""))
cookie_map[key] = c
for c in new_cookies:
key = (c["name"], c.get("domain", ""))
cookie_map[key] = c # Newer overwrites
merged = list(cookie_map.values())
with open(existing_file, "w") as f:
json.dump(merged, f, indent=2)
return len(merged)
Jalankan clean_expired_cookies() secara berkala — misalnya sebelum setiap run batch — supaya file cookie tidak membengkak dan proses loading tetap cepat.
Pemecahan Masalah Sesi Persisten
Masalah yang paling sering muncul saat menerapkan persistensi sesi, dan cara mengatasinya:
| Masalah | Penyebab | Solusi |
|---|---|---|
| Cookie tidak termuat | Belum navigasi ke domain sebelum add_cookie |
Panggil driver.get(url) dulu, baru muat cookie |
| Error "profile is locked" | Chrome sebelumnya belum ditutup bersih | Matikan proses Chrome yang tersisa, hapus file SingletonLock |
| Sesi tetap dianggap kedaluwarsa | Field sameSite pada cookie tidak cocok |
Hapus sameSite sebelum add_cookie dipanggil |
| Storage gagal dimuat | Konteks CORS/keamanan browser | Muat storage setelah navigasi ke origin yang benar |
| Frekuensi CAPTCHA naik seiring waktu meski profil sama | Alamat IP sudah ditandai | Rotasi proxy per profil, bukan hanya rotasi profil |
Kalau semua langkah di atas sudah benar tapi CAPTCHA masih sering muncul, masalahnya kemungkinan besar bukan lagi di sesi — melainkan reputasi IP di luar cakupan panduan ini.
Pertanyaan Umum seputar Persistensi Sesi Browser
Berapa lama efek sesi tersimpan bertahan sebelum CAPTCHA muncul lagi?
Cookie NID Google umumnya bertahan sekitar 6 bulan, sementara cookie validasi Cloudflare Turnstile hanya berlaku 15 menit sampai 1 jam. Jadwalkan penyegaran sesi berdasarkan komponen yang paling cepat kedaluwarsa — biasanya Cloudflare, bukan Google.
Bisakah satu profil sesi dipakai bersamaan oleh beberapa worker atau VPS?
Sebaiknya tidak bersamaan — profil Chrome yang terbuka akan terkunci (SingletonLock), jadi dua proses paralel di profil sama akan bentrok. Buat satu profil per worker (lihat SessionManager), dan samakan zona waktu serta proxy kalau memindahkan file profil antar mesin.
Apakah menyimpan file cookie sesi berisiko secara hukum di Indonesia?
Cookie sesi bisa memuat token yang terhubung ke akun pengguna sungguhan, jadi perlakukan sebagai data pribadi. UU Pelindungan Data Pribadi (UU 27/2022) mengharuskan Anda hanya memproses data yang memang berwenang Anda akses — batasi cookie yang disimpan pada akun uji milik sendiri, bukan akun pihak ketiga.
Apakah sesi yang sudah "dihangatkan" berarti CAPTCHA tidak akan pernah muncul lagi?
Tidak. Sesi persisten menurunkan frekuensi tantangan, bukan menghilangkannya total. Perubahan IP, aksi sensitif (login, checkout), atau lonjakan volume request tetap bisa memicu CAPTCHA meski profilnya sudah lama dipakai — di titik itu API CaptchaAI tetap jadi jalur penyelesaian.
Bagaimana cara mengetahui sebuah profil sudah dianggap "tepercaya" oleh reCAPTCHA?
Tidak ada skor yang diekspos langsung, tapi indikatornya bisa dilihat tidak langsung: pantau captcha_solves per profil di SessionManager — solve rate yang turun drastis padahal volume trafik stabil biasanya tanda profil sudah cukup tepercaya.
Panduan Terkait
Bangun sesi browser yang persisten untuk menekan frekuensi CAPTCHA — dan siapkan API key CaptchaAI Anda untuk tantangan yang tetap lolos.