Preisvergleichsdienste für Medikamente stoßen früh auf dieselbe Hürde: Sobald das Suchformular abgesendet wird, erscheint ein reCAPTCHA v2. CaptchaAI löst diese Abfragen per API, sodass Ihre Pipeline die Preisdaten aus mehreren Apothekenportalen zusammenführen kann. Dieser Leitfaden zeigt den kompletten Workflow – vom Auslesen des Sitekeys über den parallelen Abruf bis zum Rate-Limiting – und ordnet ihn in den rechtlichen Rahmen der DACH-Region ein.
Preisvergleich bei Medikamenten in der DACH-Region
Bevor Sie Code schreiben, lohnt sich ein Blick auf den Kontext. In Deutschland sind die Abgabepreise verschreibungspflichtiger (Rx-)Arzneimittel durch die Arzneimittelpreisverordnung (AMPreisV) bundesweit festgelegt – hier gibt es schlicht nichts zu vergleichen. Interessant wird ein Preisvergleich vor allem bei rezeptfreien (OTC-)Produkten, bei Versandapotheken und bei Kostenzuzahlungen, wo die Preise tatsächlich variieren.
Zwei Punkte gehören in jede Planung: Erstens gelten IP-Adressen unter der DSGVO als personenbezogene Daten – prüfen Sie Ihre Rechtsgrundlage und Ihre Datenflüsse, bevor Sie Portale systematisch abfragen. Zweitens greifen Sie ausschließlich auf öffentlich zugängliche Preisdaten in eigenen oder autorisierten Umgebungen zu. CaptchaAI übernimmt dabei nur einen Teil: das automatische Lösen der CAPTCHA-Abfrage, die dem eigentlichen Datenabruf im Weg steht.
Wo Apothekenportale CAPTCHAs einsetzen
Nicht jede Seite schützt sich gleich. Diese Übersicht zeigt, welche CAPTCHA-Typen Ihnen typischerweise begegnen und was sie auslöst:
| Seitentyp | Typisches CAPTCHA | Auslöser |
|---|---|---|
| Medikamenten-Suchformular | reCAPTCHA v2 | bei jeder Suchanfrage |
| Preisergebnisseite | Cloudflare Turnstile | bei Verdacht auf automatisierten Zugriff |
| Apotheken-Umkreissuche | reCAPTCHA v2 | bei standortbezogenen Abfragen |
| Gutschein-/Rabattsuche | Bild-CAPTCHA | vor Anzeige der Rabattcodes |
| Erstattungsformular der Krankenkasse | reCAPTCHA v2 | an Login- und Such-Gates |
reCAPTCHA v2, Cloudflare Turnstile und Bild-CAPTCHAs decken damit den Großteil der Fälle ab – alle drei werden von CaptchaAI unterstützt.
reCAPTCHA v2 lösen: der Basis-Workflow
Der Ablauf ist immer gleich: Suchseite laden, den sitekey aus dem HTML auslesen, die Abfrage über CaptchaAI lösen und das zurückgegebene Token als g-recaptcha-response mit dem Formular absenden.
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)
Die for-Schleife fragt den Status im Abstand von drei Sekunden ab und bricht nach einem Timeout ab – so bleibt ein hängendes Portal nicht unbemerkt.
Preise über mehrere Apotheken hinweg vergleichen
Ein echter Vergleich zieht mehrere Quellen heran. Legen Sie pro Apotheke Portal-URL und CAPTCHA-Typ als Konfiguration ab und sammeln Sie die Ergebnisse ein – die günstigste Quelle steht am Ende oben:
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")))
Schlägt eine Quelle fehl, landet der Fehler im Ergebnis statt den ganzen Lauf abzubrechen – bei einem Vergleich über viele Portale ist Robustheit wichtiger als ein sauberer Abbruch.
Verschiedene CAPTCHA-Typen in einer Pipeline
Da Portale unterschiedliche Anbieter einsetzen, wählt eine kleine Verzweigung je nach captcha_type die passende Lösungsfunktion und das korrekte Formularfeld – g-recaptcha-response für reCAPTCHA v2, cf-turnstile-response für Turnstile:
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
})
Neue Quellen fügen Sie hinzu, ohne die Aufruflogik anzufassen – nur der Konfigurationseintrag und gegebenenfalls ein weiterer Zweig kommen hinzu.
Sitzungen wiederverwenden und CAPTCHAs reduzieren
Jede gelöste Abfrage kostet einen Thread und Zeit. Halten Sie die requests.Session() über mehrere Suchen offen, lösen viele Portale erst nach einigen Anfragen wieder eine Abfrage aus. Diese Klasse löst nur dann, wenn die Antwort tatsächlich eine CAPTCHA-Seite ist:
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)
Der Zähler searches_since_captcha verrät Ihnen zudem, nach wie vielen Abfragen eine Quelle im Schnitt erneut prüft – eine nützliche Kennzahl für die Kostenschätzung.
Parallele Abfrage in Node.js
Wer den Vergleich lieber im JavaScript-Stack betreibt, löst alle Quellen mit Promise.all parallel und sortiert die erfolgreichen Antworten nach Preis:
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());
}
Rate-Limiting und Zugriffsmuster
Apothekenportale beobachten Zugriffsmuster genau. Wer zu schnell und zu gleichförmig abfragt, provoziert mehr CAPTCHAs und riskiert eine Sperre. Diese vier Stellschrauben halten die Last unauffällig:
| Strategie | Umsetzung |
|---|---|
| Anfragen zeitlich strecken | 5–10 Sekunden zwischen den Suchvorgängen |
| Sitzungen rotieren | neue Sitzung alle 20–30 Abfragen |
| Suchmuster variieren | nicht wiederholt nach demselben Medikament suchen |
| Residential-Proxys einsetzen | Rechenzentrums-IPs lösen mehr CAPTCHAs aus |
Häufige Probleme und Lösungen
| Problem | Ursache | Lösung |
|---|---|---|
| Bei jeder Suche erscheint ein CAPTCHA | Cookies der Sitzung werden nicht übernommen | requests.Session() verwenden und Cookies über alle Anfragen halten |
| Preise laden trotz gelöster Abfrage nicht | Der Parser liest eine alte oder unvollständige Ansicht aus | Daten erst nach erfolgreicher Token-Anwendung in derselben Sitzung extrahieren |
| Sperre nach vielen Suchvorgängen | Abrufrate oder Parallelität ist für die Quelle zu aggressiv | Intervalle drosseln, Sitzungen stabil halten und Proxy-Qualität prüfen |
| Andere Preise als im Browser | fehlende Session-Cookies oder Header | Browser-Header exakt nachbilden |
FAQ
Ist das Scrapen von Apothekenpreisen in Deutschland zulässig?
Das hängt vom Portal und von Ihrer Datenverarbeitung ab. Öffentlich einsehbare Preise abzurufen ist grundsätzlich etwas anderes, als sich über geschützte Bereiche hinwegzusetzen – prüfen Sie AGB und Nutzungsbedingungen der Quelle. Weil IP-Adressen unter der DSGVO personenbezogen sind, gehören Rechtsgrundlage und Datenflüsse in jede seriöse Planung. CaptchaAI löst nur die CAPTCHA-Abfrage; die rechtliche Bewertung des Zugriffs bleibt bei Ihnen.
Welche CAPTCHA-Typen kommen auf Apothekenportalen am häufigsten vor?
In der Praxis dominiert reCAPTCHA v2 auf Suchformularen, gefolgt von Cloudflare Turnstile auf Ergebnisseiten und vereinzelt Bild-CAPTCHAs vor Rabattcodes. Alle drei unterstützt CaptchaAI, sodass Sie für einen Anbieterwechsel nur den captcha_type in Ihrer Konfiguration anpassen, nicht die gesamte Logik.
Was kostet das Lösen der CAPTCHAs bei einem Vergleich über viele Apotheken?
CaptchaAI rechnet pro gleichzeitigem Thread ab – nicht pro gelöstem CAPTCHA. Der Einstiegstarif BASIC (15 $/Monat, 5 Threads) erlaubt bereits unbegrenzte Lösungen und fünf parallele Abfragen; wer viele Portale gleichzeitig prüft, greift zu STANDARD (30 $/Monat, 15 Threads) oder ADVANCE (90 $/Monat, 50 Threads). Ihre Kosten hängen also von der gewünschten Parallelität ab, nicht von der Zahl der Suchvorgänge.
Wie vermeide ich, bei jeder Suche ein neues CAPTCHA zu erhalten?
Halten Sie eine Sitzung offen und übernehmen Sie deren Cookies über alle Anfragen hinweg, wie in der Klasse PharmacySession gezeigt. Viele Portale prüfen dann erst nach mehreren Abfragen erneut. Ergänzend helfen realistische Browser-Header, ruhige Abfrageintervalle und Residential-Proxys – Rechenzentrums-IPs lösen deutlich mehr Abfragen aus.