Anwendungsfälle

Finanzdaten-Scraping mit CAPTCHA-Verwaltung

Eine Marktdaten-Pipeline läuft zuverlässig – bis auf einer Screener-Seite plötzlich ein reCAPTCHA oder eine Cloudflare-Turnstile-Abfrage auftaucht und der nächtliche Job leer zurückkommt. Die Lösung ist kein Reverse Engineering des Schutzes, sondern ein sauberer Zwischenschritt: Abfrage erkennen, Token über eine API lösen, mit dem Token erneut senden. Genau das übernimmt CaptchaAI programmgesteuert – für reCAPTCHA v2/v3, Cloudflare Turnstile und Cloudflare Challenge sowie Bild- und OCR-CAPTCHAs, wie sie auf Aktien-Screenern, SEC EDGAR und Marktdatenportalen vorkommen.

Dieser Leitfaden zeigt an lauffähigen Python-Beispielen, wie Sie CAPTCHAs an genau den Stellen abfangen, an denen sie einen Finanzdaten-Workflow unterbrechen: beim Screener-Scraping, beim Abruf von Pflichtveröffentlichungen und bei Turnstile-geschützten Marktdaten.


Warum Finanzportale den automatisierten Zugriff drosseln

Finanzseiten reagieren empfindlicher auf Bots als die meisten anderen Websites. Kursdaten, Screening-Ergebnisse und Pflichtveröffentlichungen sind wirtschaftlich wertvoll, und übermäßiger automatisierter Zugriff erzeugt echte Serverlast. Deshalb schalten Portale bei verdächtigen Mustern eine CAPTCHA-Abfrage vor – mal dauerhaft für jeden automatisierten Zugriff, mal erst, wenn das Anfragevolumen einen Schwellenwert überschreitet.

Welcher Typ auftaucht, hängt vom Portal ab. Die folgende Übersicht zeigt typische Quellen, den jeweils üblichen CAPTCHA-Typ und den Auslöser:

Quelle CAPTCHA-Typ Auslöser Datenwert
SEC EDGAR reCAPTCHA v2 Anfragen mit hohem Volumen Unternehmensunterlagen
Yahoo Finance reCAPTCHA v2 Scraping-Erkennung Kursdaten, Historie
Bloomberg Cloudflare Turnstile Jeder automatisierte Zugriff Marktdaten
Finviz reCAPTCHA v2 Zugriff auf den Aktien-Screener Screening-Ergebnisse
TradingView Cloudflare Challenge Rate-Limiting Charts, Indikatoren
Morningstar reCAPTCHA v3 Datenexport-Seiten Fondsanalysen

Für DACH-Leser gilt: Das Muster ist dasselbe bei europäischen Quellen. Der Bundesanzeiger und das Unternehmensregister veröffentlichen Jahresabschlüsse öffentlich, drosseln Massenabrufe aber ebenfalls über Rate-Limiting und Abfragen. Die Vorgehensweise in diesem Artikel überträgt sich eins zu eins.


Aktien-Screener scrapen und CAPTCHAs abfangen

Der Kern jedes Workflows ist immer derselbe Zyklus:

  1. Seite abrufen und prüfen, ob statt der Daten eine CAPTCHA-Abfrage zurückkam.
  2. Das erkannte CAPTCHA über CaptchaAI lösen und das Token entgegennehmen.
  3. Die Anfrage mit dem Token in derselben Session wiederholen.

Die Funktion solve_captcha übermittelt die Aufgabe an in.php und fragt anschließend den Status an res.php ab, bis das Token bereitsteht.

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']})")

Entscheidend ist, dass Sie das reCAPTCHA-Token über das Feld g-recaptcha-response an dieselbe Session zurückgeben, die die Seite geladen hat. So bleiben Cookies und Session-Kontext erhalten, und die Antwort enthält die Screening-Ergebnisse statt einer erneuten Abfrage.


Pflichtveröffentlichungen aus SEC EDGAR abrufen

SEC EDGAR kombiniert Rate-Limiting mit CAPTCHAs, sobald der Zugriff ein hohes Volumen erreicht. Zwei Details sind hier nicht optional: EDGAR verlangt einen aussagekräftigen User-Agent mit Kontakt-E-Mail, und bei einer 403-Antwort oder einer CAPTCHA-Seite muss der Abruf mit gelöstem Token wiederholt werden.

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")

Dasselbe Muster deckt europäische Register ab: Wer statt US-10-K-Filings deutsche Jahresabschlüsse aus dem Bundesanzeiger zieht, tauscht lediglich Basis-URL und Parser aus – die CAPTCHA- und Rate-Limiting-Logik bleibt identisch.


Turnstile-geschützte Marktdaten verarbeiten

Portale wie Bloomberg setzen auf Cloudflare Turnstile statt auf reCAPTCHA. Der Ablauf ändert sich kaum: Sie rufen solve_captcha mit der Methode turnstile auf und geben das Ergebnis über das Feld cf-turnstile-response zurück.

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

Turnstile-Token laufen wie reCAPTCHA-Token nach kurzer Zeit ab. Lösen Sie das Token daher unmittelbar vor der Übermittlung und halten Sie es nicht auf Vorrat.


Tägliche Marktdaten planen und speichern

Für wiederkehrende Erfassung – etwa einen Snapshot am Handelsende – kapseln Sie den Scraper in einen geplanten Job, der pro Ticker eine kurze Pause einlegt und das Ergebnis als CSV ablegt.

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)

Ein solcher Nightly-Job belegt selten mehr als eine Handvoll gleichzeitiger Threads. Der Einstiegstarif BASIC (15 $/Monat, 5 Threads) deckt das in der Regel ab; wer mehrere Portale parallel abfragt, greift zu STANDARD (30 $/Monat, 15 Threads) oder ADVANCE (90 $/Monat, 50 Threads). Abgerechnet wird pro gleichzeitigem Thread – nicht pro gelöstem CAPTCHA, und die Lösungen pro Thread sind im Monat unbegrenzt.


Rate-Limiting: die Grenzen der Portale respektieren

Finanzseiten sind beim automatisierten Zugriff strenger als die meisten anderen. Konservative Grenzen senken die CAPTCHA-Rate spürbar.

Faustregel: Jedes vermiedene CAPTCHA ist eine gesparte API-Anfrage – niedrige Anfrageraten zahlen sich also doppelt aus, in Stabilität und in Kosten.

Maßnahme Empfehlung
Verzögerung zwischen Anfragen 2–5 Sekunden pro Seite
Gleichzeitige Verbindungen maximal 3–5 pro Domain
Proxy-Typ Residential oder ISP
Sitzungsdauer 5–10 Minuten Sticky-Session
User-Agent realistisch, pro Sitzung konsistent
SEC EDGAR Kontakt-E-Mail im User-Agent (erforderlich)
Handelszeiten wenn möglich außerhalb der Hauptlast scrapen

Häufige Fehler und ihre Ursachen

Problem Ursache Lösung
403 auf SEC EDGAR User-Agent ohne E-Mail Header CompanyName email@domain ergänzen
CAPTCHA bei jeder Anfrage Rate-Limit überschritten 3–5 Sekunden Verzögerung zwischen Anfragen einbauen
Veraltete Kursdaten zwischengespeicherte Antwort Cache-Bust-Parameter an die Abfrage hängen
JSON-Parsing schlägt fehl statt Daten kam die CAPTCHA-Seite zurück vor dem Parsen auf CAPTCHA prüfen
IP blockiert zu viele Anfragen von derselben IP auf rotierenden Residential-Proxy wechseln

DSGVO und rechtlicher Rahmen

Öffentliche Finanzdaten wie Pflichtveröffentlichungen und Kursdaten lassen sich grundsätzlich erfassen, doch für DACH-Leser kommen zwei Punkte hinzu. Erstens gelten IP-Adressen nach der DSGVO als personenbezogene Daten – prüfen Sie Ihre Datenflüsse und die Rechtsgrundlage, sobald ein Workflow personenbezogene Informationen berührt. Zweitens sind die Nutzungsbedingungen und Rate-Limits jeder Quelle verbindlich; halten Sie diese ein und beschränken Sie sich auf öffentlich zugängliche Daten, die Sie nutzen dürfen. Diese Einschätzung ist keine Rechtsberatung – im Zweifel klären Sie den konkreten Fall mit einer fachkundigen Stelle.


FAQ

Löst CaptchaAI jedes CAPTCHA auf Finanzportalen?

CaptchaAI deckt die Typen ab, die auf Finanzseiten üblich sind: reCAPTCHA v2 und v3, Cloudflare Turnstile und Cloudflare Challenge sowie Bild- und OCR-CAPTCHAs. hCaptcha und FunCaptcha werden nicht unterstützt; GeeTest v4 ist als „bald verfügbar" angekündigt, aber noch nicht verfügbar.

Was kostet ein täglicher Marktdaten-Job?

Abgerechnet wird nach gleichzeitigen Threads, nicht pro Lösung. Ein nächtlicher Snapshot mit wenigen parallelen Anfragen passt meist in BASIC (15 $/Monat, 5 Threads). Für mehrere Portale gleichzeitig eignen sich STANDARD (30 $/Monat, 15 Threads) oder ADVANCE (90 $/Monat, 50 Threads). Alle Preise in US-Dollar.

Welcher Proxy-Typ eignet sich für Finanzseiten?

Residential- oder ISP-Proxys mit Sticky-Sessions von 5–10 Minuten. Rechenzentrums-Proxys werden von Finanzportalen schneller erkannt und lösen häufiger CAPTCHAs oder Sperren aus. Rotieren Sie die IP erst, wenn eine Blockade auftritt, nicht bei jeder Anfrage.

Wie oft sollte ich Kursdaten abrufen?

Für Kurse höchstens einmal pro Minute während der Handelszeiten, für Pflichtveröffentlichungen genügt meist ein täglicher Abruf. Häufigeres Scraping löst schneller CAPTCHAs aus und erhöht das Sperr-Risiko.


Verwandte Leitfäden

  • Rotierende Residential-Proxys für die CAPTCHA-Lösung

Finanzdaten ohne CAPTCHA-Unterbrechungen erfassen – Holen Sie sich Ihren CaptchaAI-Schlüssel.

Kommentare sind für diesen Artikel deaktiviert.