Use Cases

Penanganan CAPTCHA untuk Pemantauan Situs Lelang

Kalau pemantau harga lelang Anda tiba-tiba berhenti mengambil data, penyebabnya hampir selalu satu hal: reCAPTCHA v2 muncul di tengah alur scraping. Solusinya bukan memperlambat semuanya, melainkan mendeteksi tantangan itu, menyelesaikan token lewat CaptchaAI, lalu melanjutkan permintaan seperti tidak terjadi apa-apa. Artikel ini menunjukkan cara membangun pemantau yang tetap jalan meski situs lelang menyalakan proteksi CAPTCHA.

Buat tim data di Indonesia yang memantau harga barang koleksi, elektronik bekas, atau lelang aset untuk klien reselling, keandalan jauh lebih penting daripada kecepatan mentah. Satu daftar yang terlewat karena CAPTCHA bisa berarti tawaran yang meleset. Karena itu penanganan CAPTCHA harus jadi bagian alur, bukan tambalan darurat.

Di mana reCAPTCHA v2 muncul di situs lelang

Sebelum menulis kode, petakan dulu di titik mana proteksi biasanya menyala. Polanya konsisten: semakin cepat dan semakin berulang permintaan dari satu IP, semakin besar peluang tantangan muncul.

Tindakan Jenis CAPTCHA Pola pemicu
Cari daftar /browse reCAPTCHA v2 Pencarian berurutan yang cepat
Lihat detail daftar reCAPTCHA v2 Volume tinggi dari IP yang sama
Periksa riwayat tawaran reCAPTCHA v2 Halaman detail berulang dimuat
Penjelajahan kategori Cloudflare Turnstile Kecepatan navigasi seperti bot
Halaman peringatan harga reCAPTCHA v2 Penyegaran yang sering

Menentukan frekuensi pengecekan lebih dulu

Sebelum mengeksekusi, tentukan seberapa sering Anda benar-benar perlu memeriksa. Interval yang lebih longgar secara langsung menurunkan frekuensi CAPTCHA, jadi ini keputusan desain, bukan sekadar tuning.

Frekuensi pengecekan Kasus penggunaan Perkiraan frekuensi CAPTCHA
Setiap 30 detik Penawaran menit terakhir Tinggi – putar proxy
Setiap 5 menit Pelacakan lelang aktif Sedang
Setiap 15 menit Pemantauan daftar pantauan Rendah
Setiap jam Riset harga jangka panjang Minimal

Membangun pemantau lelang di Python

Pola intinya sederhana: kirim permintaan biasa, cek apakah respons mengandung reCAPTCHA, dan hanya jika ada, ambil sitekey lalu selesaikan token via CaptchaAI sebelum mengirim ulang. Kelas berikut menggabungkan pencarian daftar, pemantauan satu item, dan pelacakan tawaran dalam satu alur.

import requests
import time
import re
from datetime import datetime

class AuctionMonitor:
    def __init__(self, api_key):
        self.api_key = api_key
        self.session = requests.Session()
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
        })

    def search_listings(self, auction_url, query, category=None):
        """Search auction listings, solving CAPTCHAs when triggered."""
        params = {"q": query}
        if category:
            params["category"] = category

        response = self.session.get(
            f"{auction_url}/search", params=params
        )

        if self._has_captcha(response.text):
            site_key = self._extract_site_key(response.text)
            token = self._solve_recaptcha(site_key, f"{auction_url}/search")
            response = self.session.post(
                f"{auction_url}/search",
                data={**params, "g-recaptcha-response": token}
            )

        return self._parse_listings(response.text)

    def monitor_listing(self, auction_url, listing_id):
        """Get current bid and listing details."""
        url = f"{auction_url}/item/{listing_id}"
        response = self.session.get(url)

        if self._has_captcha(response.text):
            site_key = self._extract_site_key(response.text)
            token = self._solve_recaptcha(site_key, url)
            response = self.session.post(url, data={
                "g-recaptcha-response": token
            })

        return self._parse_listing_detail(response.text)

    def track_bids(self, auction_url, listing_ids, interval=60):
        """Track bid changes across multiple listings."""
        history = {lid: [] for lid in listing_ids}

        while True:
            for listing_id in listing_ids:
                try:
                    detail = self.monitor_listing(auction_url, listing_id)
                    previous = history[listing_id]

                    if previous and detail["current_bid"] != previous[-1]["current_bid"]:
                        print(f"Bid change on {listing_id}: "
                              f"${previous[-1]['current_bid']} → ${detail['current_bid']}")

                    history[listing_id].append(detail)
                except Exception as e:
                    print(f"Error checking {listing_id}: {e}")

            time.sleep(interval)

    def _has_captcha(self, html):
        return "g-recaptcha" in html or "recaptcha" in html.lower()

    def _extract_site_key(self, html):
        match = re.search(r'data-sitekey="([^"]+)"', html)
        if match:
            return match.group(1)
        match = re.search(r"sitekey['\"]?\s*[:=]\s*['\"]([^'\"]+)", html)
        if match:
            return match.group(1)
        raise ValueError("Could not find reCAPTCHA site key")

    def _solve_recaptcha(self, site_key, page_url):
        resp = requests.post("https://ocr.captchaai.com/in.php", data={
            "key": self.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": self.api_key,
                "action": "get",
                "id": task_id,
                "json": 1
            })
            data = result.json()
            if data["status"] == 1:
                return data["request"]

        raise TimeoutError("reCAPTCHA solve timed out")

    def _parse_listings(self, html):
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(html, "html.parser")

        def text_or_none(node):
            return node.text.strip() if node and node.text else None

        def attr_or_none(node, attr):
            return node.get(attr) if node else None

        listings = []
        for item in soup.select(".listing-item, .auction-item"):
            listings.append({
                "title": text_or_none(item.select_one(".title")),
                "current_bid": text_or_none(item.select_one(".price, .bid")),
                "time_left": text_or_none(item.select_one(".time-left")),
                "url": attr_or_none(item.select_one("a"), "href")
            })
        return listings

    def _parse_listing_detail(self, html):
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(html, "html.parser")

        def text_or_none(node):
            return node.text.strip() if node and node.text else None

        return {
            "title": text_or_none(soup.select_one("h1, .item-title")),
            "current_bid": text_or_none(soup.select_one(".current-bid, .price")),
            "bid_count": text_or_none(soup.select_one(".bid-count")),
            "time_left": text_or_none(soup.select_one(".time-remaining")),
            "checked_at": datetime.now().isoformat()
        }

# Usage
monitor = AuctionMonitor("YOUR_API_KEY")
listings = monitor.search_listings(
    "https://auctions.example.com",
    "vintage electronics",
    category="collectibles"
)

Perhatikan bahwa CAPTCHA hanya diselesaikan saat benar-benar muncul. Selama respons normal, tidak ada task yang dikirim ke CaptchaAI, sehingga Anda tidak membuang thread pada permintaan yang sebenarnya lolos tanpa tantangan.

Sistem peringatan harga dengan JavaScript

Kalau alur Anda berbasis Node.js, pola yang sama diterapkan pada daftar pantauan: simpan URL dan batas harga per item, periksa semuanya secara berkala, lalu munculkan peringatan begitu tawaran mendekati ambang. Token reCAPTCHA diselesaikan on-demand di dalam solveAndFetch.

class AuctionTracker {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.watchList = new Map();
  }

  addWatch(listingId, url, maxPrice) {
    this.watchList.set(listingId, { url, maxPrice, history: [] });
  }

  async checkAll() {
    const alerts = [];

    for (const [id, watch] of this.watchList) {
      try {
        const detail = await this.fetchListing(watch.url);
        watch.history.push(detail);

        const price = parseFloat(detail.currentBid.replace(/[^0-9.]/g, ''));
        if (price >= watch.maxPrice * 0.9) {
          alerts.push({
            listing: id,
            price,
            threshold: watch.maxPrice,
            message: `Price approaching limit: $${price} / $${watch.maxPrice}`
          });
        }
      } catch (error) {
        alerts.push({ listing: id, error: error.message });
      }
    }

    return alerts;
  }

  async fetchListing(url) {
    const response = await fetch(url);
    const html = await response.text();

    if (html.includes('g-recaptcha')) {
      return this.solveAndFetch(url, html);
    }

    return this.parseDetail(html);
  }

  async solveAndFetch(url, html) {
    const siteKeyMatch = html.match(/data-sitekey="([^"]+)"/);
    if (!siteKeyMatch) throw new Error('No reCAPTCHA site key found');

    const submitResp = await fetch('https://ocr.captchaai.com/in.php', {
      method: 'POST',
      body: new URLSearchParams({
        key: this.apiKey,
        method: 'userrecaptcha',
        googlekey: siteKeyMatch[1],
        pageurl: url,
        json: '1'
      })
    });

    const { request: taskId } = await submitResp.json();

    for (let i = 0; i < 60; i++) {
      await new Promise(r => setTimeout(r, 3000));
      const result = await fetch(
        `https://ocr.captchaai.com/res.php?key=${this.apiKey}&action=get&id=${taskId}&json=1`
      );
      const data = await result.json();
      if (data.status === 1) {
        // Resubmit with token
        const response = await fetch(url, {
          method: 'POST',
          body: new URLSearchParams({ 'g-recaptcha-response': data.request })
        });
        return this.parseDetail(await response.text());
      }
    }

    throw new Error('reCAPTCHA solve timed out');
  }

  parseDetail(html) {
    // Parse auction listing details from HTML
    return {
      currentBid: html.match(/current.?bid[^>]*>([^<]+)/i)?.[1]?.trim(),
      bidCount: html.match(/(\d+)\s*bids?/i)?.[1],
      timeLeft: html.match(/time.?(?:left|remaining)[^>]*>([^<]+)/i)?.[1]?.trim(),
      checkedAt: new Date().toISOString()
    };
  }
}

// Usage
const tracker = new AuctionTracker('YOUR_API_KEY');
tracker.addWatch('item-123', 'https://auctions.example.com/item/123', 500);
tracker.addWatch('item-456', 'https://auctions.example.com/item/456', 200);
const alerts = await tracker.checkAll();

Menekan frekuensi CAPTCHA

Cara paling murah menangani CAPTCHA adalah memancing lebih sedikit tantangan sejak awal. Empat teknik ini menurunkan peluang munculnya reCAPTCHA v2 tanpa mengorbankan cakupan data.

Teknik Dampak
Gunakan kembali cookie sesi Mempertahankan status terotentikasi
Putar egress jaringan yang diotorisasi Mendistribusikan permintaan ke seluruh IP
Acak interval permintaan Menghindari pola deteksi berkala
Gunakan akun yang diautentikasi Turunkan ambang batas pemicu CAPTCHA

Untuk deployment, mendekatkan pemantau ke target juga membantu latensi keseluruhan. Tim di Indonesia yang memantau platform regional biasanya menaruh worker di region seperti AWS ap-southeast-1 (Singapura) atau ap-southeast-3 (Jakarta) agar putaran polling tetap ringan.

Mengatasi masalah umum

Kalau pemantau berperilaku aneh, tabel di bawah mempercepat diagnosis. Sebagian besar keluhan berujung pada sesi yang tidak dipertahankan atau token yang kedaluwarsa sebelum sempat dikirim.

Masalah Penyebab Solusi
CAPTCHA pada setiap permintaan Tidak ada persistensi sesi Gunakan kembali requests.Session()
Token reCAPTCHA ditolak Token kedaluwarsa (umur 2 menit) Selesaikan sesaat sebelum dikirim
Halaman daftar menunjukkan 0 hasil CAPTCHA memfilter hasil secara diam-diam Periksa elemen CAPTCHA tersembunyi
IP diblokir setelah banyak CAPTCHA Batas laju terlampaui Putar egress, perpanjang interval

Catatan kepatuhan: kumpulkan hanya data lelang publik yang memang boleh Anda proses, dan hindari data pribadi. Di Indonesia, UU Pelindungan Data Pribadi (UU 27/2022) menjadikan prinsip ini relevan secara hukum, bukan sekadar etika.

Pertanyaan yang sering diajukan

Berapa thread yang saya butuhkan untuk memantau banyak lelang sekaligus?

Jumlahnya bergantung pada berapa banyak CAPTCHA yang benar-benar muncul, bukan berapa banyak daftar yang Anda pantau. Karena token hanya diselesaikan saat tantangan muncul, satu paket thread CaptchaAI umumnya cukup untuk puluhan daftar; naikkan thread hanya jika Anda memantau pada interval 30 detik dan sering terkena tantangan.

Apakah CaptchaAI mendukung Cloudflare Turnstile bila situs lelang memakainya?

Ya. Sebagian situs memakai Cloudflare Turnstile pada penjelajahan kategori. CaptchaAI menyelesaikan Turnstile dengan token cf-turnstile-response; Anda tinggal mengarahkan alur ke endpoint yang sama dan menukar parameter tokennya.

Bagaimana cara mencegah token reCAPTCHA kedaluwarsa saat memantau?

Token reCAPTCHA v2 hanya berlaku sekitar dua menit. Selesaikan token tepat sebelum Anda mengirim permintaan, bukan di awal siklus polling, agar tidak basi saat sampai ke server. Jangan menyimpan token untuk siklus berikutnya.

Apakah menyelesaikan reCAPTCHA v2 memperlambat pemantauan?

Sesuai target layanan, reCAPTCHA v2 diselesaikan CaptchaAI umumnya di bawah 60 detik. Karena CAPTCHA hanya muncul pada sebagian permintaan, dampak rata-rata terhadap keseluruhan siklus pemantauan tetap kecil selama interval Anda realistis.

Artikel Terkait

Langkah Selanjutnya

Pantau lelang tanpa terputus – ambil kunci API CaptchaAI Anda dan selesaikan tantangan reCAPTCHA v2 secara otomatis di dalam alur pemantauan.

Komentar dinonaktifkan untuk artikel ini.