Các trang web đặt vé du lịch tích cực chặn việc kiểm tra giá vé tự động bằng CAPTCHA và phát hiện bot. CaptchaAI cho phép bạn theo dõi giá chuyến bay và khách sạn một cách đáng tin cậy thông qua các biện pháp phòng vệ này.
Cảnh quan CAPTCHA trên các trang web du lịch
| Danh mục trang web | Loại CAPTCHA | độ khó |
|---|---|---|
| Hãng hàng không (trực tiếp) | reCAPTCHA v3, Đám mây bùng phát | Trung bình |
| OTA (Expedia, Đặt chỗ) | reCAPTCHA v2, Cloudflare Turnstile | Trung bình-Cao |
| Tìm kiếm tổng hợp (Google Chuyến bay, thuyền Kayak) | reCAPTCHA v3 | Trung bình |
| Hãng hàng không giá rẻ | CAPTCHA hình ảnh, reCAPTCHA | Thấp-Trung bình |
| Tổng hợp khách sạn | Cloudflare Challenge | Cao |
Triển khai giám sát giá vé
import requests
import time
import re
import json
import os
from datetime import datetime, timedelta
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
def solve_captcha(params):
params["key"] = API_KEY
resp = requests.get("https://ocr.captchaai.com/in.php", params=params)
if not resp.text.startswith("OK|"):
raise Exception(f"Submit: {resp.text}")
task_id = resp.text.split("|")[1]
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id,
})
if result.text == "CAPCHA_NOT_READY":
continue
if result.text.startswith("OK|"):
return result.text.split("|", 1)[1]
raise Exception(f"Solve: {result.text}")
raise TimeoutError()
class FareMonitor:
def __init__(self):
self.session = requests.Session()
self.session.headers["User-Agent"] = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/120.0.0.0"
)
self.history = []
def fetch_with_captcha(self, url):
"""Fetch a travel page, solving CAPTCHAs if encountered."""
resp = self.session.get(url)
# reCAPTCHA v2/v3
match = re.search(
r'data-sitekey=["\']([A-Za-z0-9_-]+)["\']', resp.text
)
if match:
site_key = match.group(1)
# Detect v3 vs v2
if "recaptcha/api.js?render=" in resp.text:
token = solve_captcha({
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": url,
"version": "v3",
"action": "search",
})
else:
token = solve_captcha({
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": url,
})
resp = self.session.post(url, data={
"g-recaptcha-response": token,
})
# Cloudflare Turnstile
if "cf-turnstile" in resp.text:
match = re.search(
r'data-sitekey=["\']([^"\']+)', resp.text
)
if match:
token = solve_captcha({
"method": "turnstile",
"sitekey": match.group(1),
"pageurl": url,
})
resp = self.session.post(url, data={
"cf-turnstile-response": token,
})
return resp.text
def check_fares(self, routes):
"""Check fares for a list of routes."""
results = []
for route in routes:
try:
html = self.fetch_with_captcha(route["url"])
prices = self._extract_prices(html)
result = {
"route": f"{route['origin']}-{route['destination']}",
"date": route["date"],
"prices": prices,
"min_price": min(prices) if prices else None,
"timestamp": datetime.utcnow().isoformat(),
}
results.append(result)
self.history.append(result)
if prices:
print(f" {result['route']} ({route['date']}): "
f"${min(prices)}-${max(prices)}")
else:
print(f" {result['route']}: No prices found")
time.sleep(3) # Respectful delay
except Exception as e:
print(f" {route.get('origin', '?')}-"
f"{route.get('destination', '?')}: ERROR - {e}")
return results
def _extract_prices(self, html):
"""Extract prices from travel page HTML."""
prices = []
# Common price patterns
for match in re.finditer(
r'\$\s*([\d,]+(?:\.\d{2})?)', html
):
price = float(match.group(1).replace(",", ""))
if 20 < price < 10000: # Filter noise
prices.append(price)
return sorted(set(prices))
def detect_price_drops(self, threshold_pct=5):
"""Detect significant price drops in history."""
route_prices = {}
for entry in self.history:
key = f"{entry['route']}_{entry['date']}"
if key not in route_prices:
route_prices[key] = []
if entry["min_price"]:
route_prices[key].append(entry["min_price"])
alerts = []
for key, prices in route_prices.items():
if len(prices) >= 2:
prev = prices[-2]
current = prices[-1]
change_pct = ((current - prev) / prev) * 100
if change_pct < -threshold_pct:
alerts.append({
"route": key,
"previous": prev,
"current": current,
"change": f"{change_pct:.1f}%",
})
return alerts
def export_report(self, filename="fare_report.json"):
"""Export fare history to JSON."""
with open(filename, "w") as f:
json.dump(self.history, f, indent=2)
print(f"Exported {len(self.history)} fare checks to {filename}")
# Define routes to monitor
routes = [
{
"origin": "JFK",
"destination": "LAX",
"date": "2025-03-15",
"url": "https://example-airline.com/flights?from=JFK&to=LAX&date=2025-03-15",
},
{
"origin": "SFO",
"destination": "ORD",
"date": "2025-03-20",
"url": "https://example-airline.com/flights?from=SFO&to=ORD&date=2025-03-20",
},
]
monitor = FareMonitor()
results = monitor.check_fares(routes)
monitor.export_report()
Lên lịch
# Check fares every 4 hours
0 */4 * * * cd /opt/fare-monitor && python fare_monitor.py
Phân tích chi phí
| Cấp độ giám sát | Tuyến đường | Kiểm tra/Day | CAPTCHA/Day | Ước tính. Chi phí |
|---|---|---|---|---|
| cá nhân | 5 | 6/route | ~30 | 0,5 USD |
| Đại lý nhỏ | 50 | 4/route | ~200 | $2-5 |
| Doanh nghiệp | 500 | 6/route | ~3.000 | $20-40 |
Câu hỏi thường gặp
Tôi nên kiểm tra giá vé bao lâu một lần?
Cứ sau 4 - 6 giờ cho mục đích sử dụng cá nhân. Cứ sau 1-2 giờ để sử dụng cho công việc. Các hãng hàng không cập nhật giá theo đợt nên việc kiểm tra thường xuyên hơn sẽ mang lại lợi nhuận giảm dần.
Tôi có thể theo dõi giá khách sạn không?
Vâng. Cách tiếp cận tương tự cũng áp dụng cho các trang web Booking.com, Expedia và khách sạn trực tiếp. Điều chỉnh mẫu trích xuất giá cho các định dạng trang khách sạn.
Làm cách nào để xử lý các trang định giá động?
Một số trang web du lịch yêu cầu hiển thị JavaScript. Sử dụng Selenium hoặc Playwright để tìm nạp trang, sau đó sử dụng CaptchaAI để giải CAPTCHA.