构建药房价格比较工具意味着从多个药房门户、药品定价数据库和保险处方集收集定价数据。其中许多来源都使用验证码保护其定价页面 - 特别是搜索表单和结果页面上的 reCAPTCHA v2。以下是处理它们的方法。
验证码出现在药房门户中的位置
| 页面类型 | 常见验证码 | 扳机 |
|---|---|---|
| 药品检索表 | reCAPTCHA v2 | 每个搜索查询 |
| 价格结果页面 | Cloudflare Turnstile | 疑似自动访问 |
| 药房定位器 | reCAPTCHA v2 | 基于位置的查询 |
| 优惠券查询 | 图片验证码 | 显示折扣代码之前 |
| 保险处方集 | reCAPTCHA v2 | 登录/search门 |
基本工作流程
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)
多药房比较
比较多个来源的价格:
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")))
处理不同的验证码类型
药房门户使用各种验证码提供商:
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
})
重复搜索的会话管理
药品价格比较需要多次搜索。管理会话以最大程度地减少验证码遭遇:
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)
JavaScript 实现
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());
}
速率限制注意事项
药房门户密切监控访问模式:
| 战略 | 执行 |
|---|---|
| 空间请求 | 搜索间隔 5 至 10 秒 |
| 轮换会议 | 每 20-30 个查询就有一个新会话 |
| 改变搜索模式 | 不要重复搜索同一种药物 |
| 使用自有服务器基础设施 | 数据中心 IP 触发更多验证码 |
故障排除
| 问题 | 原因 | 处理方式 |
|---|---|---|
| 每次搜索时都会显示验证码 | 会话不保留 cookie | 使用requests.Session()并维护cookie |
| 验证码后未加载价格 | 缺少 CSRF 令牌 | 提取并包含隐藏的表单字段 |
| 多次搜索后网站被屏蔽 | 速率限制 | 在请求之间添加延迟 |
| 价格与浏览器不同 | 缺少会话 cookie 或标头 | 完全匹配浏览器标头 |
常问问题
我可以同时比较多少个药房来源?
CaptchaAI 处理并行求解。您可以同时解决多个药房门户的验证码。实际限制取决于您的 CaptchaAI 余额和每个门户的速率限制。
药房门户是否经常更改其验证码类型?
一些门户网站定期更新其验证码实施。通过检查提取是否仍能找到站点密钥来监视更改。 CaptchaAI 支持所有主要的验证码类型,因此切换提供商不需要改变您的解决方法。
品牌药品定价门户和仿制药定价门户之间有区别吗?
验证码处理类似。仿制药数据库可能使用更简单的图像验证码,而品牌药房门户网站倾向于使用 reCAPTCHA v2 或 Turnstile。
相关文章
- 如何使用Api解决Recaptcha V2回调
- Recaptcha V2 Turnstile同一站点处理
- Recaptcha V2回调机制
下一步
构建您的药房价格比较工具 –”获取您的 CaptchaAI API 密钥并处理所有药房来源的验证码。