Spor istatistikleri portalları oyuncu veritabanlarını, oyun günlüklerini ve geçmiş kayıtlarını Cloudflare Turnstile ve reCAPTCHA ile korur. CAPTCHA'lar birden fazla oyuncu, sezon veya oyun genelinde (özellikle de verilerinden ödeme duvarları arkasında para kazanan sitelerde) yapılan hızlı sorgular sırasında tetiklenir. Spor verilerini güvenilir bir şekilde nasıl toplayacağınız burada açıklanmıştır.
Spor Portallarındaki CAPTCHA Desenleri
| Veri türü | Portal türü | CAPTCHA | Tetikleyici |
|---|---|---|---|
| Oyuncu istatistikleri | Referans siteleri | Cloudflare Turnstile | Hızlı oynatıcı sayfası yüklemeleri |
| Oyun kutusu puanları | Puan portalları | Cloudflare doğrulama akışı | Toplu oyun aramaları |
| Sezon sıralamaları | Lig siteleri | reCAPTCHA v2 | Otomatik gezinme |
| Fantezi projeksiyonlar | Fantezi platformlar | reCAPTCHA v3 | Sık API benzeri erişim |
| Bahis oranları/lines | Oran portalları | Cloudflare Turnstile | Yüksek frekanslı yenilemeler |
| Tarihsel kayıtlar | Arşiv siteleri | Resim CAPTCHA'sı | Veri dışa aktarma istekleri |
Spor Veri Toplayıcı
import requests
import time
import re
from dataclasses import dataclass, field
@dataclass
class PlayerStats:
name: str
team: str
position: str
stats: dict = field(default_factory=dict)
season: str = ""
source: str = ""
class SportsDataCollector:
def __init__(self, api_key):
self.api_key = api_key
self.session = requests.Session()
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
})
def get_player_stats(self, portal_url, player_slug, season=None):
"""Fetch player statistics, solving CAPTCHAs as needed."""
url = f"{portal_url}/players/{player_slug}"
if season:
url += f"/{season}"
response = self.session.get(url)
if self._is_captcha_page(response):
response = self._solve_turnstile_and_retry(response, url)
return self._parse_player_stats(response.text)
def get_game_scores(self, portal_url, date):
"""Fetch all game scores for a specific date."""
url = f"{portal_url}/scores/{date}"
response = self.session.get(url)
if self._is_captcha_page(response):
response = self._solve_turnstile_and_retry(response, url)
return self._parse_scores(response.text)
def collect_team_roster(self, portal_url, team_slug, season):
"""Collect stats for all players on a team roster."""
roster_url = f"{portal_url}/teams/{team_slug}/{season}/roster"
response = self.session.get(roster_url)
if self._is_captcha_page(response):
response = self._solve_turnstile_and_retry(response, roster_url)
player_slugs = self._extract_player_links(response.text)
all_stats = []
for slug in player_slugs:
try:
stats = self.get_player_stats(portal_url, slug, season)
all_stats.append(stats)
time.sleep(2) # Respectful delay
except Exception as e:
print(f"Failed for {slug}: {e}")
return all_stats
def _is_captcha_page(self, response):
return (
response.status_code == 403 or
"cf-turnstile" in response.text or
"challenges.cloudflare.com" in response.text
)
def _solve_turnstile_and_retry(self, response, url):
match = re.search(r'data-sitekey="(0x[^"]+)"', response.text)
if not match:
raise ValueError("Turnstile sitekey not found")
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": self.api_key,
"method": "turnstile",
"sitekey": match.group(1),
"pageurl": 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": self.api_key,
"action": "get",
"id": task_id,
"json": 1
})
data = result.json()
if data["status"] == 1:
return self.session.post(url, data={
"cf-turnstile-response": data["request"]
})
raise TimeoutError("Turnstile solve timed out")
def _parse_player_stats(self, html):
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "html.parser")
# Extract stat rows from tables
stats = {}
stat_table = soup.select_one("table.stats, #stats-table")
if stat_table:
headers = [th.text.strip() for th in stat_table.select("thead th")]
for row in stat_table.select("tbody tr"):
cells = [td.text.strip() for td in row.select("td")]
if len(cells) == len(headers):
for header, value in zip(headers, cells):
stats[header] = value
def text_or_empty(node):
return node.text.strip() if node and node.text else ""
return PlayerStats(
name=text_or_empty(soup.select_one("h1, .player-name")),
team=text_or_empty(soup.select_one(".team-name, .team")),
position=text_or_empty(soup.select_one(".position, .pos")),
stats=stats
)
def _parse_scores(self, html):
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "html.parser")
games = []
def text_or_none(node):
return node.text.strip() if node and node.text else None
for game in soup.select(".game-card, .scoreboard-item"):
games.append({
"away": text_or_none(game.select_one(".away-team")),
"home": text_or_none(game.select_one(".home-team")),
"away_score": text_or_none(game.select_one(".away-score")),
"home_score": text_or_none(game.select_one(".home-score")),
"status": text_or_none(game.select_one(".game-status"))
})
return games
def _extract_player_links(self, html):
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "html.parser")
links = []
for a in soup.select("a[href*='/players/']"):
slug = a["href"].rstrip("/").split("/")[-1]
if slug and slug not in links:
links.append(slug)
return links
# Usage
collector = SportsDataCollector("YOUR_API_KEY")
# Get player stats
stats = collector.get_player_stats(
"https://sports.example.com", "lebron-james", "2024"
)
print(f"{stats.name} ({stats.team}): {stats.stats}")
# Get all scores for a date
scores = collector.get_game_scores("https://sports.example.com", "2024-12-25")
for game in scores:
print(f"{game['away']} {game['away_score']} @ {game['home']} {game['home_score']}")
Sezon Veri Toplayıcı (JavaScript)
class SportsAggregator {
constructor(apiKey) {
this.apiKey = apiKey;
}
async collectSeasonData(portalUrl, sport, season, teams) {
const allData = {};
for (const team of teams) {
try {
const roster = await this.getTeamStats(portalUrl, team, season);
allData[team] = roster;
} catch (error) {
allData[team] = { error: error.message };
}
// Rate limit between teams
await new Promise(r => setTimeout(r, 3000));
}
return allData;
}
async getTeamStats(portalUrl, teamSlug, season) {
const url = `${portalUrl}/teams/${teamSlug}/${season}`;
const response = await fetch(url);
const html = await response.text();
if (html.includes('cf-turnstile') || response.status === 403) {
return this.solveAndFetch(url, html);
}
return this.parseTeamPage(html);
}
async solveAndFetch(url, html) {
const match = html.match(/data-sitekey="(0x[^"]+)"/);
if (!match) throw new Error('Turnstile sitekey not found');
const submitResp = await fetch('https://ocr.captchaai.com/in.php', {
method: 'POST',
body: new URLSearchParams({
key: this.apiKey,
method: 'turnstile',
sitekey: match[1],
pageurl: url,
json: '1'
})
});
const { request: taskId } = await submitResp.json();
for (let i = 0; i < 60; i++) {
await new Promise(r => setTimeout(r, 3000));
const result = await fetch(
`https://ocr.captchaai.com/res.php?key=${this.apiKey}&action=get&id=${taskId}&json=1`
);
const data = await result.json();
if (data.status === 1) {
const response = await fetch(url, {
method: 'POST',
body: new URLSearchParams({ 'cf-turnstile-response': data.request })
});
return this.parseTeamPage(await response.text());
}
}
throw new Error('Turnstile solve timed out');
}
parseTeamPage(html) {
const players = [];
const rowMatches = html.matchAll(/<tr[^>]*class="[^"]*player[^"]*"[^>]*>([\s\S]*?)<\/tr>/gi);
for (const row of rowMatches) {
const cells = [...row[1].matchAll(/<td[^>]*>([\s\S]*?)<\/td>/gi)]
.map(m => m[1].replace(/<[^>]+>/g, '').trim());
if (cells.length >= 3) {
players.push({
name: cells[0],
position: cells[1],
stats: cells.slice(2)
});
}
}
return { players, count: players.length };
}
}
// Usage
const aggregator = new SportsAggregator('YOUR_API_KEY');
const seasonData = await aggregator.collectSeasonData(
'https://sports.example.com', 'basketball', '2024',
['lakers', 'celtics', 'warriors']
);
Spora Göre Toplama Stratejisi
| Spor | En yüksek veri hacmi | CAPTCHA duyarlılığı | Önerilen yaklaşım |
|---|---|---|---|
| Beyzbol | Günlük oyun kayıtları | Orta | Oyunlar bittikten sonra topla |
| Basketbol | Oyun geceleri | Oyunlar sırasında yüksek | Yoğun olmayan saatleri kullanın |
| Futbol | Haftalık oyunlar | Oyunlar arasında düşük | Haftalık toplu tahsilat |
| Futbol | Ligler arasında günlük | Orta | Lig başına oturumlar |
| Hokey | her gece | Orta | Oyun sonrası koleksiyon |
Sorun giderme
| Sorun | Sebep | Düzeltme |
|---|---|---|
| Her sayfada Turnstile | Çerez kalıcılığı yok | Oturumda qa_session_cookie'yi koruyun |
| Oyuncu sayfası farklı istatistikleri gösteriyor | Sezon/career geçiş | Sezon parametresini URL'ye ekle |
| Puanlar sayfası boş | Henüz oynanmamış oyunlar | Sorgulamadan önce oyun programını kontrol edin |
| 50 istekten sonra oran sınırlı | Portalın günlük sınırı | Zamana dağıtın, proxy kullanın |
SSS
Canlı oyun verilerini ne sıklıkla toplamalıyım?
Aktif oyunlar sırasında puanlar birkaç dakikada bir güncellenir. CAPTCHA'ları en aza indirmek için oturum sürekliliği ile 2-5 dakikalık aralıklar kullanın.CaptchaAI, Turnstileyi %100 başarı oranıyla yönetir.
Birden fazla sezona giden geçmiş istatistikleri toplayabilir miyim?
Evet. Geçmiş veriler nadiren değişir, bu nedenle bunları bir kez toplayın ve yerel olarak önbelleğe alın. CAPTCHA'lar ilk toplu toplama sırasında tetiklenebilir ancak depolanan verileri etkilemez.
Spor API'leri CAPTCHA çözme ihtiyacını ortadan kaldırıyor mu?
Bazı ligler resmi API'ler sunar, ancak bunlar genellikle pahalıdır, hız sınırlıdır veya referans sitelerde mevcut olan gelişmiş istatistikler eksiktir. CaptchaAI, CAPTCHA'lar tarafından korunan ek veri kaynaklarına erişmenizi sağlar.
İlgili Makaleler
- Pazar Araştırması Veri Toplama
- Geetest ve Cloudflare Turnstile Karşılaştırması
- Cloudflare Turnstile 403 Token Düzeltmesinden Sonra
Sonraki Adımlar
Spor verilerini güvenilir bir şekilde toplayın —CaptchaAI API anahtarınızı alınve spor portalı CAPTCHA'larını otomatik olarak yönetin.