Reference

Persistensi Sesi Browser untuk Alur Kerja CAPTCHA

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.


Teknik paling sederhana: simpan cookie ke file JSON setelah tiap run, lalu muat ulang sebelum menyentuh halaman target.

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.


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.

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.

Komentar dinonaktifkan untuk artikel ini.