Kasus Penggunaan

Pengumpulan Data Statistik Olahraga dengan Penanganan CAPTCHA

Portal statistik olahraga melindungi database pemain, log permainan, dan catatan sejarah dengan Cloudflare Turnstile dan reCAPTCHA. CAPTCHA terpicu selama cookieri cepat di beberapa pemain, musim, atau pertandingan – terutama di situs yang memonetisasi data mereka di balik paywall. Berikut cara mengumpulkan data olahraga dengan andal.

Pola CAPTCHA di Portal Olahraga

Tipe data Jenis portal CAPTCHA Pemicu
Statistik pemain Situs referensi Cloudflare Turnstile Pemuatan halaman pemutar cepat
Skor kotak permainan Portal skor Cloudflare Challenge Pencarian game massal
Klasemen musim Situs liga reCAPTCHA v2 Navigasi otomatis
Proyeksi fantasi Platform fantasi reCAPTCHA v3 Akses seperti API yang sering
Peluang taruhan/lines Portal peluang Cloudflare Turnstile Penyegaran frecookiensi tinggi
Catatan sejarah Situs arsip Gambar CAPTCHA Request ekspor data

Pengumpul Data Olahraga

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

Agregator Data Musim (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']
);

Strategi Pengumpulan berdasarkan Olahraga

Olahraga Volume data puncak Sensitivitas CAPTCHA Pendekatan yang disarankan
Bisbol Log permainan harian Sedang Kumpulkan setelah pertandingan berakhir
Bola Basket Malam permainan Tinggi selama pertandingan Gunakan jam di luar jam sibuk
Sepak bola Pertandingan mingguan Rendah di antara pertandingan Pengumpulan massal mingguan
Sepak Bola Setiap hari di seluruh liga Sedang Sesi per liga
Hoki Setiap malam Sedang Koleksi pasca pertandingan

Pemecahan Masalah

Masalah Penyebab Solusi
Turnstile di setiap halaman Tidak ada kegigihan cookie Pertahankan qa_validation_cookie dalam sesi
Halaman pemain menunjukkan statistik yang berbeda Tombol Musim/career Sertakan parameter musim di URL
Halaman skor kosong Permainan belum dimainkan Periksa jadwal permainan sebelum bertanya
Tarif dibatasi setelah 50 request Batas harian Portal Distribusikan ke seluruh waktu, gunakan proxy

Pertanyaan Umum

Seberapa sering saya harus mengumpulkan data permainan langsung?

Selama permainan aktif, skor diperbarui setiap beberapa menit. Gunakan interval 2–5 menit dengan persistensi sesi untuk meminimalkan CAPTCHA. CaptchaAI menangani Turnstile dengan tingkat keberhasilan 100%.

Bisakah saya mengumpulkan statistik historis selama beberapa musim?

Ya. Data historis jarang berubah, jadi kumpulkan sekali dan simpan dalam cache secara lokal. CAPTCHA dapat terpicu selama pengumpulan massal awal namun tidak akan memengaruhi data yang disimpan.

Apakah API olahraga menghilangkan kebutuhan akan solve CAPTCHA?

Beberapa liga menawarkan API resmi, tetapi sering kali harganya mahal, tarifnya terbatas, atau tidak ada statistik lanjutan yang tersedia di situs referensi. CaptchaAI memungkinkan Anda mengakses sumber data tambahan yang dilindungi oleh CAPTCHA.

Artikel Terkait

Langkah Selanjutnya

Kumpulkan data olahraga dengan andal –dapatkan kunci API CaptchaAI Andadan menangani CAPTCHA portal olahraga secara otomatis.

Komentar dinonaktifkan untuk artikel ini.