Use Cases

Penanganan CAPTCHA untuk Scraping Perbandingan Harga Apotek

Portal harga obat online hampir selalu memasang reCAPTCHA v2 di formulir pencarian dan Cloudflare Turnstile di halaman hasil. Kalau Anda membangun agregator harga apotek, penyelesaian CAPTCHA otomatis jadi syarat agar pipeline scraping tetap jalan. Satu API seperti CaptchaAI menyelesaikan reCAPTCHA v2, Turnstile, dan image CAPTCHA yang biasa dipakai portal farmasi, jadi scraper Anda tinggal mengikuti pola kirim–simpan task ID–polling–pakai token — dari solve satu portal sampai membandingkan banyak apotek sekaligus.

Berapa pun portalnya, integrasi mengikuti empat langkah yang sama:

  1. Kirim sitekey dan URL halaman ke CaptchaAI.
  2. Simpan task ID dari respons.
  3. Polling hasil sampai token siap.
  4. Pakai token di field form, lalu kirim pencarian.

Di mana CAPTCHA muncul saat scraping harga obat

Sebelum menulis kode, petakan di mana proteksi biasanya dipasang. Polanya konsisten antar portal:

Jenis halaman CAPTCHA yang biasa dipakai Pemicu
Formulir pencarian obat reCAPTCHA v2 Setiap kueri pencarian
Halaman hasil harga Cloudflare Turnstile Akses otomatis yang terdeteksi
Pencari lokasi apotek reCAPTCHA v2 Kueri berbasis lokasi
Pencarian kupon/diskon image CAPTCHA Sebelum kode diskon ditampilkan
Formularium asuransi reCAPTCHA v2 Gerbang login/pencarian

Alur kerja dasar: solve reCAPTCHA v2 lalu kirim pencarian

Pola intinya sama untuk hampir semua portal: muat halaman, ambil sitekey, kirim ke CaptchaAI, polling sampai token siap, lalu POST pencarian beserta token:

import requests
import time

session = requests.Session()
session.headers.update({
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
})

def solve_recaptcha(site_key, page_url):
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": "YOUR_API_KEY",
        "method": "userrecaptcha",
        "googlekey": site_key,
        "pageurl": page_url,
        "json": 1
    })
    task_id = resp.json()["request"]

    for _ in range(60):
        time.sleep(3)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": "YOUR_API_KEY",
            "action": "get",
            "id": task_id,
            "json": 1
        })
        data = result.json()
        if data["status"] == 1:
            return data["request"]
    raise TimeoutError("Solve timed out")

def search_drug_prices(drug_name, zipcode):
    search_url = "https://pharmacy.example.com/search"

    # Load the search page to get the site key
    page = session.get(search_url)

    # Extract reCAPTCHA site key
    import re
    match = re.search(r'data-sitekey="([^"]+)"', page.text)
    site_key = match.group(1)

    # Solve the CAPTCHA
    token = solve_recaptcha(site_key, search_url)

    # Submit the search with token
    results = session.post(search_url, data={
        "drug": drug_name,
        "zip": zipcode,
        "g-recaptcha-response": token
    })

    return parse_prices(results.text)

solve_recaptcha mengirim task ke in.php, lalu polling res.php tiap 3 detik hingga status bernilai 1; token hasilnya masuk ke field g-recaptcha-response.

Membandingkan harga dari banyak apotek sekaligus

Nilai agregator muncul saat Anda menarik harga dari beberapa sumber lalu mengurutkannya. Definisikan daftar sumber beserta tipe CAPTCHA masing-masing:

PHARMACY_SOURCES = [
    {
        "name": "PharmacyA",
        "url": "https://pharmacya.example.com/pricing",
        "captcha_type": "recaptcha_v2"
    },
    {
        "name": "PharmacyB",
        "url": "https://pharmacyb.example.com/drugs",
        "captcha_type": "turnstile"
    },
    {
        "name": "PharmacyC",
        "url": "https://pharmacyc.example.com/search",
        "captcha_type": "image"
    }
]

def compare_drug_price(drug_name, zipcode):
    results = []

    for source in PHARMACY_SOURCES:
        try:
            prices = fetch_prices(
                source["url"],
                source["captcha_type"],
                drug_name,
                zipcode
            )
            results.append({
                "source": source["name"],
                "prices": prices,
                "status": "success"
            })
        except Exception as e:
            results.append({
                "source": source["name"],
                "error": str(e),
                "status": "failed"
            })

    return sorted(results, key=lambda r: r.get("prices", {}).get("lowest", float("inf")))

Hasil diurutkan dari harga terendah, jadi pengguna langsung melihat apotek termurah.

Menangani beberapa tipe CAPTCHA dalam satu pipeline

Tidak semua portal memakai reCAPTCHA — sebagian pindah ke Turnstile, sebagian masih memakai image CAPTCHA lama. Satu fungsi dispatcher menjaga logika tetap rapi:

def fetch_prices(url, captcha_type, drug_name, zipcode):
    page = session.get(url)

    if captcha_type == "recaptcha_v2":
        import re
        match = re.search(r'data-sitekey="([^"]+)"', page.text)
        token = solve_recaptcha(match.group(1), url)
        field_name = "g-recaptcha-response"

    elif captcha_type == "turnstile":
        import re
        match = re.search(r'data-sitekey="(0x[^"]+)"', page.text)
        token = solve_turnstile(match.group(1), url)
        field_name = "cf-turnstile-response"

    elif captcha_type == "image":
        img_data = extract_captcha_image(page.text)
        token = solve_image_captcha(img_data)
        field_name = "captcha"

    return session.post(url, data={
        "drug": drug_name,
        "zip": zipcode,
        field_name: token
    })

Field token berbeda per tipe: g-recaptcha-response untuk reCAPTCHA v2, cf-turnstile-response untuk Turnstile. Salah field, portal menolak submit.

Kelola sesi agar CAPTCHA tidak muncul di setiap pencarian

Memicu CAPTCHA di setiap request itu mahal sekaligus lambat. Simpan cookie sesi, dan solve CAPTCHA hanya saat halaman memintanya:

class PharmacySession:
    def __init__(self, base_url):
        self.session = requests.Session()
        self.base_url = base_url
        self.searches_since_captcha = 0

    def search(self, drug_name, zipcode):
        result = self.session.post(f"{self.base_url}/search", data={
            "drug": drug_name,
            "zip": zipcode
        })

        if self.is_captcha_page(result.text):
            token = self.solve_page_captcha(result.text)
            result = self.session.post(f"{self.base_url}/search", data={
                "drug": drug_name,
                "zip": zipcode,
                "g-recaptcha-response": token
            })
            self.searches_since_captcha = 0
        else:
            self.searches_since_captcha += 1

        return result

    def is_captcha_page(self, html):
        return "g-recaptcha" in html or "cf-turnstile" in html

    def solve_page_captcha(self, html):
        import re
        match = re.search(r'data-sitekey="([^"]+)"', html)
        return solve_recaptcha(match.group(1), self.base_url)

Cookie hasil solve pertama sering bertahan untuk beberapa pencarian berikutnya sebelum verifikasi diminta lagi — itu langsung memangkas biaya solve.

Versi Node.js untuk pengambilan paralel

Kalau stack Anda berbasis JavaScript, pola yang sama berjalan dengan Promise.all untuk menembak beberapa sumber sekaligus:

async function comparePharmacyPrices(drugName, zipCode, sources) {
  const results = await Promise.all(
    sources.map(async (source) => {
      try {
        const price = await fetchDrugPrice(source, drugName, zipCode);
        return { source: source.name, price, status: 'success' };
      } catch (error) {
        return { source: source.name, error: error.message, status: 'failed' };
      }
    })
  );

  return results
    .filter(r => r.status === 'success')
    .sort((a, b) => a.price - b.price);
}

async function fetchDrugPrice(source, drugName, zipCode) {
  // Solve CAPTCHA for this source
  const token = await solveCaptcha(source.siteKey, source.url);

  const response = await fetch(source.url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      drug: drugName,
      zip: zipCode,
      'g-recaptcha-response': token
    })
  });

  return parsePrice(await response.text());
}

Untuk menekan latency dari Indonesia, deploy worker di region terdekat seperti AWS ap-southeast-3 (Jakarta) atau ap-southeast-1 (Singapura).

Mengatasi masalah yang sering muncul

Masalah Penyebab Solusi
CAPTCHA muncul di setiap pencarian Sesi tidak menyimpan cookie Pakai requests.Session() dan pertahankan cookie
Harga tidak muncul setelah solve Token CSRF hilang Ekstrak dan sertakan hidden field pada form
Situs memblokir setelah banyak pencarian Kena rate limiting Tambahkan jeda antar permintaan
Harga beda dengan yang di browser Cookie atau header sesi tidak cocok Samakan header browser secara persis

Mengatur laju permintaan (rate limiting)

Portal apotek memantau pola akses dengan ketat. Beberapa taktik menurunkan frekuensi CAPTCHA:

  • Beri jeda antar permintaan: 5–10 detik antar pencarian.
  • Rotasi sesi: buka sesi baru setiap 20–30 kueri.
  • Variasikan pola pencarian: hindari mencari obat yang sama berulang-ulang.
  • Pakai egress jaringan yang diotorisasi: IP pusat data memicu lebih banyak CAPTCHA dibanding IP residensial.

Pertanyaan yang sering diajukan

Berapa biaya solve CAPTCHA untuk ribuan pencarian harga obat per hari?

CaptchaAI menagih per thread (kapasitas paralel), bukan per solve, dengan solve tak terbatas selama masa aktif paket. BASIC ($15/bulan, 5 threads) cukup untuk uji coba; agregator lebih besar biasanya naik ke ADVANCE ($90/bulan, 50 threads). Biaya tetap berapa pun volumenya.

Mengambil harga obat publik untuk perbandingan umumnya wajar, tetapi UU Pelindungan Data Pribadi (UU 27/2022) dan UU ITE mewajibkan Anda hanya memproses data yang boleh diakses. Batasi scraping pada harga publik; hindari data pribadi pasien atau halaman di balik login milik orang lain. Ini bukan nasihat hukum.

Bagaimana kalau portal mengganti reCAPTCHA jadi Turnstile atau image CAPTCHA?

Fungsi dispatcher menangani ini: periksa tipe CAPTCHA saat memuat halaman, lalu panggil solver yang sesuai. CaptchaAI mendukung reCAPTCHA v2/v3, Cloudflare Turnstile dan Challenge, GeeTest v3, serta image dan grid, jadi peralihan tipe tidak mengubah alur kirim–polling–pakai token.

Kenapa CAPTCHA tetap muncul di setiap pencarian meski sudah pakai session?

Biasanya cookie sesi tidak tersimpan atau token CSRF hilang. Pakai requests.Session(), simpan cookie antar request, dan sertakan hidden field form. Kalau masih sering muncul, turunkan laju pencarian.

Artikel Terkait

Langkah Selanjutnya

Siap mulai? Ambil kunci API CaptchaAI Anda, pasang pola kirim–polling–pakai token di scraper Anda, dan tarik harga dari semua portal apotek dalam satu pipeline.

Komentar dinonaktifkan untuk artikel ini.