SEC EDGAR, Yahoo Finance, Bloomberg và Finviz đều bật CAPTCHA ngay khi phát hiện request tần suất cao từ cùng một IP — script chạy êm buổi sáng có thể dừng hẳn buổi chiều. Gắn bước giải reCAPTCHA v2 hoặc Cloudflare Turnstile qua CaptchaAI vào pipeline, request tiếp tục chạy mà không cần giải captcha thủ công. Nhiều đội outsourcing tại TP.HCM và Hà Nội xây dashboard chứng khoán cho khách hàng nước ngoài đang dùng cách này để giữ pipeline ổn định.
CAPTCHA xuất hiện ở đâu trong dữ liệu tài chính
- SEC EDGAR — reCAPTCHA v2, kích hoạt khi request tần suất cao; bảo vệ hồ sơ công ty.
- Yahoo Finance — reCAPTCHA v2, kích hoạt khi phát hiện hành vi scraping; bảo vệ báo giá và lịch sử giá.
- Bloomberg — Cloudflare Turnstile, áp dụng cho mọi truy cập tự động; bảo vệ dữ liệu thị trường.
- Finviz — reCAPTCHA v2, kích hoạt khi truy cập công cụ sàng lọc cổ phiếu.
- TradingView — Cloudflare Challenge, kích hoạt theo giới hạn tần suất; bảo vệ biểu đồ và chỉ số.
- Morningstar — reCAPTCHA v3, kích hoạt trên trang xuất dữ liệu; bảo vệ phân tích quỹ.
Thu thập dữ liệu từ công cụ sàng lọc cổ phiếu
- Tìm
data-sitekeytrong HTML khi CAPTCHA xuất hiện trên trang sàng lọc. - Giải qua CaptchaAI rồi submit lại token trong request tiếp theo.
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:[email protected]: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']})")
Trích xuất hồ sơ SEC EDGAR
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:[email protected]:5000",
)
filings = sec.search_filings("Apple Inc", "10-K")
Dữ liệu thị trường được bảo vệ bằng Cloudflare Turnstile
- Bloomberg chặn toàn bộ truy cập tự động bằng Cloudflare Turnstile thay vì reCAPTCHA.
- Token
cf-turnstile-responseđược submit lại như một field form bình thường.
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
Lên lịch thu thập dữ liệu thị trường hằng ngày
Với danh sách ticker cố định, chạy theo lịch hằng ngày ổn định hơn hẳn so với gọi liên tục trong ngày:
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:[email protected]: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)
Vận hành ổn định: lỗi thường gặp và giới hạn tần suất
Lỗi thường gặp
| Vấn đề | Nguyên nhân | Cách xử lý |
|---|---|---|
| Lỗi 403 trên SEC EDGAR | Thiếu User-Agent kèm email |
Thêm header dạng CompanyName email@domain |
| CAPTCHA xuất hiện ở mọi request | Vượt giới hạn tần suất | Thêm độ trễ 3-5 giây giữa các request |
| Dữ liệu giá bị cũ | Response đến từ cache | Thêm query parameter xoá cache |
| Lỗi parse JSON | Server trả về trang CAPTCHA thay vì JSON | Kiểm tra CAPTCHA trước khi parse response |
| IP bị chặn | Quá nhiều request từ cùng một địa chỉ IP | Đa dạng hoá nguồn gửi request, tránh dồn về một IP duy nhất |
Giới hạn tần suất request
| Thực hành | Khuyến nghị |
|---|---|
| Độ trễ giữa các request | 2-5 giây giữa các trang |
| Kết nối đồng thời | Tối đa 3-5 mỗi domain |
| Chọn proxy | Ưu tiên IP ổn định, tránh dải IP datacenter bị đánh dấu |
| Thời lượng phiên | Duy trì một phiên ổn định trong 5-10 phút |
| User-Agent | Thực tế, giữ nguyên trong cùng một phiên |
| SEC EDGAR | Bắt buộc kèm email liên hệ trong User-Agent |
| Khung giờ chạy | Ưu tiên giờ thấp điểm ngoài giờ giao dịch |
| Log & dữ liệu cá nhân | Nếu log kèm email nhà đầu tư, giới hạn thu thập theo Nghị định 13/2023/NĐ-CP dù dữ liệu gốc công khai |
Câu hỏi thường gặp
CaptchaAI giải được những CAPTCHA nào trên các nền tảng tài chính?
Có — reCAPTCHA v2 (SEC EDGAR, Yahoo Finance, Finviz), Cloudflare Turnstile (Bloomberg), Cloudflare Challenge (TradingView) và reCAPTCHA v3 (Morningstar) đều được hỗ trợ qua cùng một API.
Thu thập dữ liệu tài chính công khai có hợp pháp không?
Phần lớn được phép — hồ sơ SEC, báo giá cổ phiếu là dữ liệu công khai. Tôn trọng điều khoản dịch vụ và giới hạn tần suất; SEC EDGAR còn khuyến khích truy cập cho mục đích nghiên cứu.
Thu thập dữ liệu vài trăm mã cổ phiếu mỗi ngày cần gói nào?
Tuỳ số request chạy song song, không phải số mã cổ phiếu — CaptchaAI tính phí theo thread, không theo lượt giải. BASIC ($15/tháng, 5 thread) đủ cho pipeline nhỏ; scale lên STANDARD ($30/tháng, 15 thread) hoặc ADVANCE ($90/tháng, 50 thread) khi chạy nhiều ticker song song.
Vì sao các nền tảng tài chính chặn bot gắt hơn các trang khác?
Trích xuất tự động số lượng lớn có thể bị lợi dụng để thao túng thị trường, thu thập lợi thế cạnh tranh hoặc gây quá tải máy chủ — nên CAPTCHA ở đây gắt hơn một trang bán lẻ thông thường.
Tài nguyên liên quan
- Proxy ảnh hưởng thế nào đến tỷ lệ giải CAPTCHA
- Chọn nguồn IP ổn định khi giải CAPTCHA số lượng lớn
Giữ pipeline dữ liệu tài chính chạy liên tục, không bị CAPTCHA làm gián đoạn — lấy API key CaptchaAI và tự động hoá bước giải token ngay trong request tiếp theo.