Ultimate Fighting Championship публикует обширный архив публичных данных о боях на ufcstats.com: каждое завершённое событие, каждый поединок и подробную разбивку ударов, тейкдаунов и результатов по каждому бойцу. Для всех, кто создаёт модель предсказания боёв, исследовательский датасет для ставок или простой справочник «кто кого победил», эта публичная страница статистики, настоящая сокровищница, и копировать цифры вручную перестаёт работать, как только вас интересует больше, чем один кард.
В этом руководстве показан лучший способ скрейпить статистику UFC с помощью Python: вы будете получать отрендеренные страницы статистики через Crawling API, парсить таблицы с BeautifulSoup и собирать чистую запись для каждого бойца и поединка. Всё руководство ограничено публичной спортивной статистикой (имена бойцов, рекорды, результаты и показатели боёв). Оно не касается социальных сетей, персональных данных или чего-либо за логином, а раздел о легальности ближе к концу не является формальностью, поэтому прочитайте его перед тем, как направить скрейпер на реальные объёмы.
Что вы создадите
Скрипт на Python, который принимает публичный URL бойца или события с ufcstats.com, получает отрендеренный HTML через Crawling API и извлекает структурированную запись. В качестве примера используется публичная страница с данными бойца, из которой извлекаются следующие поля:
- Имя бойца, боец, которому принадлежит страница, например «Jon Jones».
- Рекорд, строка карьерных побед-поражений-ничьих, например «27-1-0».
- Событие и дата, кард, в котором проходил каждый перечисленный поединок, с его датой.
- Соперник, другой боец в каждом поединке.
- Результат, исход с точки зрения данного бойца: победа, поражение или ничья.
- Ключевая статистика, значимые удары и тейкдауны, зафиксированные в поединке.
Почему обычный запрос испытывает трудности на ufcstats.com
Иногда статическую разметку с ufcstats.com можно получить обычным HTTP-клиентом, но у сырого скрейпера возникают две периодические проблемы на любом публичном спортивном сайте. Во-первых, вёрстка опирается на таблицы, строки которых загружаются вместе со скриптами и стилями, поэтому наивный запрос может вернуть неполную страницу или урезанную оболочку без нужных столбцов статистики. Во-вторых, сайты следят за автоматизированным трафиком: поток запросов с одного датацентрового IP, постоянно обращающихся к одному пути в плотном цикле, попадает под throttle или вызов задолго до окончания сезона событий.
Таким образом, работающий скрейпер статистики UFC требует двух вещей в одном запросе: запроса, возвращающего полностью сформированную страницу, и IP, который сайт воспримет как обычного посетителя. Это можно построить самостоятельно с помощью headless-браузера плюс пула ротирующих резидентных прокси, но соединение этих компонентов и поддержание их работоспособности, это большая часть всей работы. Crawling API объединяет оба в одном вызове: вы отправляете ему URL, он получает страницу за доверенным IP и возвращает готовый HTML для парсинга.
Crawlbase предлагает два типа токенов. Обычный токен получает статический HTML; JavaScript (JS) токен сначала рендерит страницу в настоящем браузере. Многие страницы ufcstats.com возвращают таблицы в статическом HTML, поэтому начните с обычного токена. Если столбец статистики возвращается пустым, переключитесь на JS-токен, и отрендеренная разметка будет содержать контент с поздней загрузкой.
Предварительные требования
Перед написанием кода необходимо подготовить несколько вещей. Ни одна из них не займёт много времени.
Базовые знания Python. Вы должны уметь писать и запускать Python-скрипты и устанавливать пакеты с помощью pip. Если вы новичок в парсинге HTML, руководство по BeautifulSoup в Python охватывает работу с селекторами, которую предполагает этот туториал.
Python 3.8 или новее. Проверьте версию командой python --version. Если Python не установлен, установите его с python.org или через дистрибутив, например Anaconda.
Аккаунт Crawlbase и токен. Зарегистрируйтесь, откройте панель управления и скопируйте токен со страницы аккаунта. Относитесь к токену как к паролю: он аутентифицирует ваши запросы, поэтому не добавляйте его в систему контроля версий.
Настройка проекта
Создайте виртуальную среду, чтобы зависимости проекта были изолированы, затем установите две библиотеки, которые нужны скрейперу.
python --version python -m venv ufc_env source ufc_env/bin/activate pip install crawlbase beautifulsoup4
На Windows активируйте среду командой ufc_env\Scripts\activate вместо строки с source. Две зависимости выполняют всю работу: crawlbase, официальный клиент для Crawling API, а beautifulsoup4 парсит возвращённый HTML, позволяя извлекать отдельные поля по CSS-селектору.
Шаг 1: Получение отрендеренной страницы статистики
Начните с получения готовой страницы. Импортируйте класс CrawlingAPI, инициализируйте его токеном и запросите URL бойца. Проверка кода статуса перед парсингом делает ошибки заметными, а не скрытыми.
from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) def crawl(page_url): response = api.get(page_url) if response["status_code"] == 200: return response["body"].decode("utf-8") print(f"Request failed: {response['status_code']}") return None if __name__ == "__main__": page_url = "http://www.ufcstats.com/statistics/events/completed" html = crawl(page_url) print(html[:500] if html else "No HTML returned")
Это повторяет минимальный фрагмент из исходного руководства, обновлённый под официальный клиент: вместо ручного построения URL запроса и URL-кодирования цели вы передаёте URL страницы в api.get и позволяете клиенту обработать кодирование и токен. Запустите скрипт командой python scraper.py и вы должны увидеть настоящую разметку статистики, таблицу завершённых событий, а не пустую оболочку. Это подтверждает, что запрос работает, прежде чем вы напишете хоть один селектор. Замените пример URL на URL страницы деталей бойца, например http://www.ufcstats.com/fighter-details/<id>, чтобы обратиться к странице конкретного бойца.
Этот единственный вызов api.get делает больше, чем кажется. За ним Crawling API получает страницу ufcstats.com через доверенный, ротирующий резидентный IP и передаёт вам готовый HTML, избавляя от необходимости запускать флот headless-браузеров и поддерживать пул прокси только для чтения публичной таблицы статистики. Начните с бесплатного уровня на странице бойца.
Шаг 2: Парсинг полей бойца с помощью BeautifulSoup
Имея HTML, загрузите его в BeautifulSoup и извлеките каждое поле. Страница бойца на ufcstats.com размещает имя и карьерный рекорд вверху, а затем перечисляет все поединки в таблице результатов, где каждая строка содержит соперника, событие, дату, исход и показатели конкретного поединка. Оберните извлечение в небольшие вспомогательные функции, чтобы одно отсутствующее поле не ломало весь запуск.
from bs4 import BeautifulSoup def text_of(node): return node.get_text(strip=True) if node else None def scrape_fighter(html): soup = BeautifulSoup(html, "html.parser") name = text_of(soup.select_one(".b-content__title-record")) record = text_of(soup.select_one(".b-content__title-record")) bouts = [] rows = soup.select("tr.b-fight-details__table-row") for row in rows: cells = row.select("td.b-fight-details__table-col") if len(cells) < 10: continue fighters = [text_of(p) for p in cells[1].select("p")] opponent = fighters[1] if len(fighters) > 1 else None strikes = [text_of(p) for p in cells[4].select("p")] takedowns = [text_of(p) for p in cells[5].select("p")] event = [text_of(p) for p in cells[6].select("p")] bouts.append({ "result": text_of(cells[0]), "opponent": opponent, "sig_strikes": strikes[0] if strikes else None, "takedowns": takedowns[0] if takedowns else None, "event": event[0] if event else None, "date": event[1] if len(event) > 1 else None, }) return {"name": name, "record": record, "bouts": bouts}
Вспомогательная функция text_of делает одно полезное: возвращает обрезанный текст узла или None, когда узел отсутствует, чтобы вызов .get_text() никогда не бросал исключение при пустом результате. Страница бойца упаковывает имя и рекорд побед-поражений-ничьих в один блок заголовка, именно поэтому оба поля читаются из .b-content__title-record, а имя отделяется от строки рекорда в дальнейшей обработке. Каждая строка результатов находится в tr.b-fight-details__table-row, а её ячейки следуют в стабильном порядке: исход, два бойца, нокдауны, значимые удары, тейкдауны, затем событие и дата. Индексирование ячеек по позиции сохраняет читаемость парсера, извлекая результат, соперника, ключевую статистику и событие для каждого поединка. Если нужно освежить знания о выборе надёжных селекторов, руководство по XPath и CSS-селекторам станет хорошим дополнением.
Имена классов b-content__title-record и b-fight-details__table отражают текущую разметку ufcstats.com. Публичные сайты меняют дизайн без предупреждения, поэтому воспринимайте селекторы выше как начальный шаблон, а не контракт. Когда поле возвращается как None, повторно просмотрите живую страницу в инструментах разработчика браузера и обновите селектор. Периодическое обслуживание селекторов, норма для любого боевого скрейпера, а не признак поломки.
Шаг 3: Сборка скрипта
Теперь соедините запрос и парсинг в один запускаемый скрипт. Получите HTML, передайте его парсеру и выведите структурированную запись.
import json from crawlbase import CrawlingAPI from bs4 import BeautifulSoup api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) def crawl(page_url): response = api.get(page_url) if response["status_code"] == 200: return response["body"].decode("utf-8") print(f"Request failed: {response['status_code']}") return None def text_of(node): return node.get_text(strip=True) if node else None def scrape_fighter(html): soup = BeautifulSoup(html, "html.parser") name = text_of(soup.select_one(".b-content__title-record")) record = text_of(soup.select_one(".b-content__title-record")) bouts = [] for row in soup.select("tr.b-fight-details__table-row"): cells = row.select("td.b-fight-details__table-col") if len(cells) < 10: continue fighters = [text_of(p) for p in cells[1].select("p")] strikes = [text_of(p) for p in cells[4].select("p")] takedowns = [text_of(p) for p in cells[5].select("p")] event = [text_of(p) for p in cells[6].select("p")] bouts.append({ "result": text_of(cells[0]), "opponent": fighters[1] if len(fighters) > 1 else None, "sig_strikes": strikes[0] if strikes else None, "takedowns": takedowns[0] if takedowns else None, "event": event[0] if event else None, "date": event[1] if len(event) > 1 else None, }) return {"name": name, "record": record, "bouts": bouts} def main(): page_url = "http://www.ufcstats.com/fighter-details/f4c49976c75c5ab2" html = crawl(page_url) if not html: return data = scrape_fighter(html) print(json.dumps(data, indent=2)) if __name__ == "__main__": main()
Как выглядит вывод
Запустите полный скрипт командой python scraper.py и получите чистую структурированную запись о бойце, готовую для записи в JSON, CSV или базу данных. Точные числа зависят от бойца и поединка; форма остаётся неизменной.
{ "name": "Jon Jones", "record": "Record: 27-1-0", "bouts": [ { "result": "win", "opponent": "Ciryl Gane", "sig_strikes": "9", "takedowns": "1", "event": "UFC 285: Jones vs. Gane", "date": "Mar. 04, 2023" } ] }
Масштабирование на многих бойцов и события
Одна страница бойца, это демо; настоящий датасет охватывает многих из них. Форма остаётся той же: держите список URL бойцов, запрашивайте каждый через Crawling API, парсите той же функцией и собирайте строки. Поскольку каждая страница бойца имеет одинаковую структуру таблицы, уже написанный парсер работает для всех без изменений. Распространённый паттерн, начать с индекса завершённых событий, перейти к списку поединков каждого события и перейти по ссылкам бойцов оттуда, а затем в конце записать всё сразу.
import time fighters = [ "http://www.ufcstats.com/fighter-details/f4c49976c75c5ab2", "http://www.ufcstats.com/fighter-details/07f72a2a7591b409", ] results = [] for url in fighters: html = crawl(url) if html: results.append(scrape_fighter(html)) time.sleep(2) with open("ufc_stats.json", "w") as f: json.dump(results, f, indent=2)
Вызов time.sleep между запросами сделан намеренно. Даже несмотря на то, что Crawling API ротирует IP за вас, контроль темпа поддерживает запуск корректным и предсказуемым и даёт каждой странице время вернуться без накопления запросов. Чтобы преобразовать записи о бойцах в плоскую таблицу, пройдите в цикле список bouts и запишите одну строку CSV на поединок с прикреплённым именем бойца; тот же паттерн «запрос-парсинг» распространяется на страницы событий, если вы предпочитаете индексировать по карду, а не по бойцу.
Как оставаться незаблокированным
Даже на публичном сайте статистики поток автоматизированных запросов может вызвать throttle. Несколько привычек сохраняют работоспособность запуска, и они применимы к любому сайту, который вы скрейпите в масштабе.
- Контролируйте темп запросов. Долбить страницы в плотном цикле, быстрейший способ получить throttle. Распределяйте запросы и варьируйте цели вместо краулинга одного пути на полной скорости.
- Опирайтесь на ротацию. Пул резидентных IP распределяет запросы по множеству адресов реальных пользователей, чтобы ни один не превысил rate-limit. Crawling API делает это за вас; если вы создаёте собственный стек, именно эту часть нужно сделать правильно.
- Читайте коды статуса. Запуск, начавший возвращать вызовы или ошибки, сигнализирует о том, что текущий темп или IP-уровень больше недостаточен. Воспринимайте это как сигнал к отступлению, а не как шум для игнорирования.
Для более широкого плана действий смотрите подробное руководство по скрейпингу без блокировок и общее руководство по скрейпингу сайтов с Python. Если вы предпочитаете маршрутизировать собственный трафик через ротирующий пул вместо использования управляемого API, Smart AI Proxy (также называемый AI Proxy) даёт ту же ротацию резидентных IP как drop-in прокси-эндпойнт.
Законен ли скрейпинг статистики UFC?
Допустимость скрейпинга статистики UFC зависит от условий использования сайта статистики, вашей юрисдикции и того, что вы делаете с данными. Скрейпинг публичной информации, доступной без аккаунта, в целом находится на более безопасной почве, чем сбор закрытых или персональных данных, но «публичность», это не универсальное разрешение. Прочитайте условия использования и robots.txt любого сайта, на который вы направляете скрейпер, включая ufcstats.com, и относитесь к обоим как к границе того, что вы собираете и с какой скоростью. Ни один из кодов здесь это не меняет; он просто делает работоспособной техническую часть.
Держите работу в узких рамках. Данное руководство намеренно ограничено публичной статистикой боёв и бойцов: имена, рекорды побед-поражений-ничьих, события и даты, результаты и показатели поединков, такие как значимые удары и тейкдауны. Оно не охватывает данные из социальных сетей, персональные данные о бойцах или болельщиках, контент за аутентификацией, попытки обойти аутентификацию, и не распространяет защищённые авторским правом медиаматериалы, такие как записи событий или фотографии. Это намеренно выходит за рамки, потому что публичная статистика позволяет работе оставаться защищаемой. Если возникает соблазн расширить охват на публикации в социальных сетях или личные профили, именно здесь данный туториал заканчивается.
Для чего-либо коммерческого предпочитайте официальный или лицензированный источник. UFC и его партнёры по данным лицензируют статистику боёв через официальные фиды, а более широкие поставщики спортивных данных предлагают платные API с чёткими правами использования и стабильной схемой, которую не нужно заново открывать каждый раз при перерисовке страницы. Скрейпинг публичной страницы статистики, правильный инструмент для исследований, личного проекта или разового датасета, где ни один лицензированный фид не покрывает вашу потребность; когда вы создаёте продукт на основе этих данных, лицензированный API спортивных данных является более чистым и безопасным путём.
Ключевые выводы
- Запрашивайте через доверенный IP. Обычный запрос к ufcstats.com может вернуть неполную страницу или быть throttled; Crawling API запрашивает её через ротирующий резидентный IP в одном вызове.
-
Парсите таблицы с BeautifulSoup. Страница бойца показывает имя и рекорд в блоке заголовка, а каждый поединок в
b-fight-details__table-row, поэтому индексируйте ячейки для извлечения результата, соперника, ударов, тейкдаунов и события. -
Ожидайте дрейфа селекторов. Имена классов
b-content__title-recordи строк таблицы отражают текущую разметку; повторно проверяйте и обновляйте их, когда поле возвращаетNone. - Масштабируйтесь, перебирая URL с паузами. Тот же парсер работает на каждой странице бойца, поэтому реальный датасет, это список ссылок плюс короткий sleep между запросами.
-
Оставайтесь на публичной статистике, лицензируйте для коммерческого использования. Придерживайтесь публичной статистики боёв и бойцов, соблюдайте ToS и
robots.txt, и отдавайте предпочтение официальному или лицензированному фиду спортивных данных при создании продукта.
Часто задаваемые вопросы
Каков лучший способ скрейпить статистику UFC с помощью Python?
Запрашивайте публичную страницу статистики через Crawling API, чтобы получить полный HTML за доверенным, ротирующим IP, затем парсите его с BeautifulSoup. Это сочетание решает две сложные части: получение полной страницы и IP, который сайт воспринимает как обычного посетителя, пока вы сосредоточены на выборе нужных полей из таблиц.
Какую статистику UFC можно извлечь?
С публичной страницы бойца или события можно получить имя бойца, рекорд побед-поражений-ничьих, соперника в каждом поединке, событие и дату, результат и показатели поединка, такие как значимые удары и тейкдауны. Данное руководство ограничено только публичной спортивной статистикой; оно не охватывает социальные сети, персональные данные или что-либо за логином.
Нужен ли обычный токен или JS-токен для ufcstats.com?
Начните с обычного токена. Многие страницы ufcstats.com возвращают таблицы в статическом HTML, поэтому обычного токена обычно достаточно. Если столбец статистики возвращается пустым, переключитесь на JavaScript (JS) токен, который рендерит страницу в настоящем браузере перед возвратом HTML, и контент с поздней загрузкой будет присутствовать при парсинге.
Мои селекторы возвращают None. Что изменилось?
Почти наверняка разметка сайта. Имена классов, такие как b-content__title-record, и строки b-fight-details__table меняются при перерисовке страницы, поэтому селекторы, работавшие в прошлом месяце, могут сломаться. Повторно просмотрите живую страницу в инструментах разработчика браузера и обновите селекторы. Периодическое обслуживание селекторов, норма для любого боевого скрейпера.
Предлагает ли UFC официальный API статистики?
UFC и его партнёры по данным лицензируют статистику боёв через официальные фиды, а более широкие поставщики спортивных данных продают платные API с чёткими правами использования и стабильной схемой. Для чего-либо коммерческого предпочитайте один из этих лицензированных источников. Скрейпинг публичной страницы статистики лучше всего подходит для исследований, личного проекта или разового датасета, где ни один лицензированный фид не покрывает вашу потребность.
Как избежать блокировки при скрейпинге статистики UFC?
Держите темп запросов с одного IP низким, варьируйте цели вместо цикличного обхода одного пути, и маршрутизируйте через ротирующие резидентные IP, чтобы ни один адрес не превысил rate-limit. Crawling API управляет ротацией и доверенным пулом IP; если вы создаёте собственный стек, именно в эту часть стоит вложиться. Следите за кодами статуса и отступайте, когда начинаете получать вызовы.
Обходите любой сайт в масштабе, без борьбы с инфраструктурой.
Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.
