Begitu volume request Anda naik, scraping stock screener, filing SEC EDGAR, atau feed harga saham hampir pasti akan menabrak reCAPTCHA v2 atau Cloudflare Turnstile. Jalan keluarnya langsung: serahkan setiap challenge ke API CaptchaAI, ambil token hasilnya, lalu kirim ulang request dengan token tersebut — seluruhnya dari dalam skrip Python Anda, tanpa satu pun klik manual.
Pola integrasinya sama di semua contoh di bawah ini, dan selalu empat langkah:
- Kirim challenge ke
in.phpbeserta sitekey dan URL halaman. - Simpan task ID yang dikembalikan.
- Lakukan polling ke
res.phpsampai token siap. - Pakai token pada request asli.
Untuk operator data yang menjalankan riset pasar harian — termasuk banyak tim scraping freelance dan agensi price-monitoring di Indonesia — pola inilah yang membuat pipeline tetap jalan tanpa diawasi.
Di mana CAPTCHA muncul di data keuangan
Tiap sumber memicu challenge dengan cara berbeda, dan tahu tipe apa yang Anda hadapi menentukan method mana yang dikirim ke API:
| Sumber | Tipe CAPTCHA | Pemicu | Nilai data |
|---|---|---|---|
| SEC EDGAR | reCAPTCHA v2 | Request volume tinggi | Filing perusahaan |
| Yahoo Finance | reCAPTCHA v2 | Deteksi scraping | Harga saham, riwayat |
| Bloomberg | Cloudflare Turnstile | Semua akses otomatis | Data pasar |
| Finviz | reCAPTCHA v2 | Akses stock screener | Hasil screener |
| TradingView | Cloudflare Challenge | Rate limiting | Chart, indikator |
| Morningstar | reCAPTCHA v3 | Halaman ekspor data | Analisis reksa dana |
Semua tipe di kolom itu termasuk yang didukung CaptchaAI, jadi satu integrasi menutup hampir seluruh sumber:
- reCAPTCHA v2 — SEC EDGAR, Yahoo Finance, Finviz
- reCAPTCHA v3 — halaman ekspor bergaya Morningstar
- Cloudflare Turnstile dan Cloudflare Challenge — Bloomberg, TradingView
Scraping stock screener
Fungsi solve_captcha di bawah adalah inti dari semua contoh: ia mengirim challenge, melakukan polling sampai token siap, lalu mengembalikannya. Kelas FinancialScraper mendeteksi data-sitekey di halaman, menyelesaikan reCAPTCHA v2, dan mengirim ulang request dengan g-recaptcha-response:
import requests
import time
from bs4 import BeautifulSoup
import re
CAPTCHAAI_KEY = "YOUR_API_KEY"
CAPTCHAAI_URL = "https://ocr.captchaai.com"
def solve_captcha(method, sitekey, pageurl, **kwargs):
data = {
"key": CAPTCHAAI_KEY,
"method": method,
"googlekey": sitekey,
"pageurl": pageurl,
"json": 1,
}
data.update(kwargs)
resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data=data)
task_id = resp.json()["request"]
for _ in range(60):
time.sleep(5)
result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
"key": CAPTCHAAI_KEY, "action": "get",
"id": task_id, "json": 1,
})
r = result.json()
if r["request"] != "CAPCHA_NOT_READY":
return r["request"]
raise TimeoutError("Solve timeout")
class FinancialScraper:
def __init__(self, proxy=None):
self.session = requests.Session()
if proxy:
self.session.proxies = {"http": proxy, "https": proxy}
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/126.0.0.0 Safari/537.36",
"Accept-Language": "en-US,en;q=0.9",
})
def scrape_screener(self, url):
"""Scrape stock screener, handling CAPTCHA if triggered."""
resp = self.session.get(url, timeout=30)
# Check for CAPTCHA
sitekey_match = re.search(r'data-sitekey="([^"]+)"', resp.text)
if sitekey_match:
sitekey = sitekey_match.group(1)
token = solve_captcha("userrecaptcha", sitekey, url)
# Resubmit with token
resp = self.session.post(url, data={
"g-recaptcha-response": token,
})
return self._parse_stocks(resp.text)
def _parse_stocks(self, html):
soup = BeautifulSoup(html, "html.parser")
stocks = []
for row in soup.select("table.screener-table tr")[1:]:
cols = row.select("td")
if len(cols) >= 8:
stocks.append({
"ticker": cols[1].get_text(strip=True),
"company": cols[2].get_text(strip=True),
"sector": cols[3].get_text(strip=True),
"price": cols[6].get_text(strip=True),
"change": cols[7].get_text(strip=True),
})
return stocks
# Usage
scraper = FinancialScraper(
proxy="http://user:pass@residential.proxy.com:5000"
)
stocks = scraper.scrape_screener("https://screener.example.com/screener.ashx?v=111")
for stock in stocks[:5]:
print(f"{stock['ticker']}: {stock['price']} ({stock['change']})")
Perhatikan bahwa deteksi CAPTCHA bersifat bersyarat: selama request belum ditandai, skrip mem-parsing tabel secara langsung dan tidak memanggil API sama sekali — jadi Anda hanya memakai thread saat benar-benar dibutuhkan.
Ekstraksi filing SEC EDGAR
SEC EDGAR menerapkan rate limiting dan CAPTCHA untuk akses volume tinggi, dan mewajibkan User-Agent yang mengidentifikasi Anda lengkap dengan email kontak. Tanpa header itu, Anda akan menerima 403 sebelum CAPTCHA pun muncul:
import json
class SECFilingScraper:
BASE_URL = "https://efts.sec.gov/LATEST"
def __init__(self, user_agent_email, proxy=None):
self.session = requests.Session()
if proxy:
self.session.proxies = {"http": proxy, "https": proxy}
# SEC requires identifying User-Agent
self.session.headers.update({
"User-Agent": f"CompanyName admin@{user_agent_email}",
"Accept": "application/json",
})
def search_filings(self, company, filing_type="10-K"):
"""Search EDGAR for specific filing types."""
url = f"{self.BASE_URL}/search-index"
params = {
"q": company,
"dateRange": "custom",
"forms": filing_type,
}
resp = self.session.get(url, params=params, timeout=30)
# Handle CAPTCHA if triggered
if "captcha" in resp.text.lower() or resp.status_code == 403:
sitekey = self._extract_sitekey(resp.text)
if sitekey:
token = solve_captcha("userrecaptcha", sitekey, url)
resp = self.session.post(url, data={
**params,
"g-recaptcha-response": token,
})
return resp.json() if resp.status_code == 200 else {}
def download_filing(self, filing_url):
"""Download individual filing document."""
resp = self.session.get(filing_url, timeout=60)
if resp.status_code == 200:
return resp.text
return None
def _extract_sitekey(self, html):
match = re.search(r'data-sitekey="([^"]+)"', html)
return match.group(1) if match else None
# Usage
sec = SECFilingScraper(
user_agent_email="example.com",
proxy="http://user:pass@proxy.example.com:5000",
)
filings = sec.search_filings("Apple Inc", "10-K")
Karena skrip hanya memanggil solve_captcha ketika status 403 atau kata "captcha" muncul di respons, filing yang lolos tanpa challenge tetap gratis dari sisi solve.
Data pasar di balik Cloudflare Turnstile
Situs seperti Bloomberg memasang Cloudflare Turnstile pada seluruh akses otomatis. Alurnya sama, hanya method-nya berganti menjadi turnstile dan field token yang dikirim ulang menjadi cf-turnstile-response:
def scrape_turnstile_market_data(url, sitekey):
"""Handle Cloudflare Turnstile on financial data sites."""
token = solve_captcha("turnstile", sitekey, url)
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/126.0.0.0 Safari/537.36",
})
resp = session.post(url, data={
"cf-turnstile-response": token,
}, timeout=30)
return resp.json() if resp.status_code == 200 else None
Snapshot pasar harian terjadwal
Riset pasar jarang berhenti di satu request. Fungsi berikut membungkus scraper ke dalam loop harian: menjelajahi daftar ticker, memberi jeda antar-request, dan menulis hasilnya ke CSV bertanggal — cocok dijalankan sebagai cron job:
import csv
from datetime import datetime
def daily_market_snapshot(tickers, output_dir="data"):
"""Collect daily stock data, handling CAPTCHAs automatically."""
scraper = FinancialScraper(
proxy="http://user:pass@residential.proxy.com:5000"
)
date_str = datetime.now().strftime("%Y-%m-%d")
results = []
for ticker in tickers:
url = f"https://screener.example.com/quote.ashx?t={ticker}"
try:
data = scraper.scrape_screener(url)
if data:
results.extend(data)
time.sleep(2) # Rate limit
except Exception as e:
print(f"Error on {ticker}: {e}")
# Save to CSV
filepath = f"{output_dir}/market_{date_str}.csv"
with open(filepath, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["ticker", "company", "sector", "price", "change"])
writer.writeheader()
writer.writerows(results)
print(f"Saved {len(results)} records to {filepath}")
return results
# Run daily
tickers = ["AAPL", "GOOGL", "MSFT", "AMZN", "TSLA"]
daily_market_snapshot(tickers)
Dua hal yang perlu dijaga saat menjadikannya cron job:
- Pertahankan
time.sleepantar-request meski Anda men-deploy dekat pasar, misalnya AWSap-southeast-3(Jakarta) — kedekatan region bukan alasan untuk membanjiri sumber data. - Bungkus tiap ticker dalam
try/exceptseperti di atas agar satu kegagalan tidak menghentikan seluruh batch harian.
Aturan rate limiting untuk situs keuangan
Situs keuangan jauh lebih ketat terhadap akses otomatis dibanding situs biasa. Beberapa pedoman yang menjaga pipeline tetap sehat:
| Praktik | Rekomendasi |
|---|---|
| Delay antar request | 2–5 detik per halaman |
| Concurrent connection | Maks 3–5 per domain |
| Tipe egress | IP residensial atau ISP |
| Durasi sesi | Sesi sticky 5–10 menit |
| User-Agent | Realistis, konsisten per sesi |
| SEC EDGAR | Sertakan email kontak di UA (wajib) |
| Jam pasar | Scrape di luar jam sibuk bila memungkinkan |
Satu catatan kepatuhan yang relevan di Indonesia: UU Pelindungan Data Pribadi (UU 27/2022) mengatur pemrosesan data pribadi, jadi batasi scraping pada data pasar publik dan hindari data pribadi yang bukan hak Anda.
Pemecahan masalah
Sebagian besar kegagalan di pipeline keuangan bukan soal solving, melainkan header atau ritme request. Tabel ini memetakan gejala yang paling sering muncul ke perbaikannya:
| Masalah | Penyebab | Perbaikan |
|---|---|---|
| 403 di SEC EDGAR | User-Agent tanpa email tidak ada | Tambahkan header CompanyName email@domain |
| CAPTCHA di setiap request | Rate limit terlampaui | Tambahkan delay 3–5 detik antar request |
| Data harga basi | Respons ter-cache | Tambahkan query parameter cache-bust |
| JSON parse error | Halaman CAPTCHA yang dikembalikan | Cek CAPTCHA sebelum parsing |
| IP diblokir | Terlalu banyak request dari IP yang sama | Beralih ke rotating egress jaringan yang diotorisasi |
Pertanyaan umum
Berapa thread yang saya butuhkan untuk scraping data keuangan harian?
Tergantung berapa banyak challenge yang jalan bersamaan. CaptchaAI menagih per thread, bukan per solve, dengan solve tak terbatas per thread. Untuk snapshot beberapa ratus ticker sehari, paket BASIC ($15/bulan, 5 thread) biasanya cukup; naik ke STANDARD ($30/bulan, 15 thread) hanya jika Anda memparalelkan banyak sumber sekaligus.
Apakah CaptchaAI mendukung reCAPTCHA v3 di halaman ekspor seperti Morningstar?
Ya. reCAPTCHA v3 termasuk tipe yang didukung, jadi halaman ekspor data yang memakai v3 dapat ditangani dengan alur yang sama — hanya method dan parameter score-nya yang berbeda dari v2.
Bagaimana cara menghindari error 403 di SEC EDGAR?
403 di EDGAR hampir selalu berarti User-Agent Anda tidak menyertakan email kontak yang diwajibkan SEC. Set header User-Agent ke format CompanyName email@domain sebelum request pertama; ini menyelesaikan mayoritas blokir sebelum CAPTCHA sekalipun ikut bermain.
Apakah boleh melakukan scraping data keuangan?
Data keuangan publik seperti filing SEC dan harga saham umumnya boleh diakses, dan SEC EDGAR secara eksplisit menyediakan akses untuk tujuan penelitian. Hormati terms of service serta rate limit tiap situs, dan sesuai UU 27/2022 hindari mengumpulkan data pribadi yang tidak Anda miliki haknya.
Panduan terkait
- Rotating egress jaringan yang diotorisasi untuk penyelesaian CAPTCHA
Kumpulkan data pasar tanpa terhenti oleh CAPTCHA — ambil API key CaptchaAI Anda dan jalankan riset pasar secara otomatis.