Trường Hợp Sử Dụng

Xử lý CAPTCHA để thu thập dữ liệu thống kê thể thao

Cổng thống kê thể thao bảo vệ cơ sở dữ liệu người chơi, nhật ký trò chơi và hồ sơ lịch sử bằng Cloudflare Turnstile và reCAPTCHA. CAPTCHA kích hoạt trong các truy vấn nhanh trên nhiều người chơi, mùa hoặc trò chơi — đặc biệt là trên các trang web kiếm tiền từ dữ liệu của họ sau tường phí. Dưới đây là cách thu thập dữ liệu thể thao một cách đáng tin cậy.

Các mẫu CAPTCHA trên cổng thông tin thể thao

Kiểu dữ liệu Loại cổng thông tin CAPTCHA Trình kích hoạt
Thống kê cầu thủ Trang web tham khảo Cloudflare Turnstile Tải trang trình phát nhanh
Điểm số hộp trò chơi Cổng tính điểm Cloudflare Challenge Tra cứu trò chơi hàng loạt
Bảng xếp hạng mùa giải Trang web giải đấu reCAPTCHA v2 Điều hướng tự động
Phép chiếu tưởng tượng Nền tảng giả tưởng reCAPTCHA v3 Truy cập giống như API thường xuyên
Tỷ lệ cá cược/lines Cổng tỷ lệ cược Cloudflare Turnstile Làm mới tần số cao
Ghi chép lịch sử Lưu trữ trang web CAPTCHA hình ảnh Yêu cầu xuất dữ liệu

Người thu thập dữ liệu thể thao

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

Trình tổng hợp dữ liệu theo mùa (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']
);

Chiến lược thu thập theo thể thao

thể thao Khối lượng dữ liệu cao điểm Độ nhạy CAPTCHA Cách tiếp cận được đề xuất
Bóng chày Nhật ký trò chơi hàng ngày Trung bình Thu thập sau khi trò chơi kết thúc
Bóng rổ Đêm trò chơi Cao trong khi chơi game Sử dụng giờ thấp điểm
Bóng đá Trò chơi hàng tuần Thấp giữa các trò chơi Bộ sưu tập số lượng lớn hàng tuần
Bóng đá Hàng ngày trên khắp các giải đấu Trung bình Phiên mỗi giải đấu
khúc côn cầu Hàng đêm Trung bình Bộ sưu tập sau trận đấu

Khắc phục sự cố

Vấn đề Nguyên nhân Cách xử lý
Cloudflare Turnstile trên mỗi trang Không có sự tồn tại của cookie Duy trì <staging-session-cookie> trong phiên
Trang trình phát hiển thị các số liệu thống kê khác nhau Chuyển đổi Season/career Bao gồm tham số phần trong URL
Trang điểm trống Trò chơi chưa chơi Kiểm tra lịch thi đấu trước khi truy vấn
Tỷ lệ giới hạn sau 50 yêu cầu Giới hạn hàng ngày của cổng thông tin Phân phối theo thời gian, sử dụng proxy

Câu hỏi thường gặp

Tôi nên thu thập dữ liệu trò chơi trực tiếp bao lâu một lần?

Trong các trận đấu đang diễn ra, điểm số sẽ được cập nhật vài phút một lần. Sử dụng khoảng thời gian 2–5 phút với thời gian duy trì phiên để giảm thiểu CAPTCHA. CaptchaAI xử lý Turnstile với tỷ lệ thành công 100%.

Tôi có thể thu thập số liệu thống kê lịch sử của nhiều mùa không?

Vâng. Dữ liệu lịch sử hiếm khi thay đổi, vì vậy hãy thu thập dữ liệu đó một lần và lưu vào bộ nhớ đệm cục bộ. CAPTCHA có thể kích hoạt trong quá trình thu thập hàng loạt lần đầu nhưng sẽ không ảnh hưởng đến dữ liệu được lưu trữ.

API thể thao có loại bỏ nhu cầu giải CAPTCHA không?

Một số giải đấu cung cấp API chính thức nhưng chúng thường đắt tiền, có tỷ lệ giới hạn hoặc thiếu số liệu thống kê nâng cao có sẵn trên các trang web tham khảo. CaptchaAI cho phép bạn truy cập các nguồn dữ liệu bổ sung được bảo vệ bởi CAPTCHA.

bài viết liên quan

Các bước tiếp theo

Thu thập dữ liệu thể thao một cách đáng tin cậy —lấy khóa API CaptchaAI của bạnvà tự động xử lý CAPTCHA của cổng thông tin thể thao.

Os comentários estão desativados para este artigo.