Extract structured data from espn.com — espn.com for MMA fights, fighters, rankings and events at $0.50 per 1,000 rows — the lowest price of any comparable UFC scraper. 42 measured stats per fighter per bout · reach, stance, gym & referee · career defence & closing sportsbook odds.
UFC & MMA Stats Scraper for espn.com on Apify →
No credit card required. No commitment. Cancel anytime.
- Click sign up — pick GitHub, Google, or email; takes ~30 seconds
- Open this actor — input is pre-filled with a working example
- Click Start — export results as JSON, CSV, or Excel
Your $5 monthly platform credit is enough to run this actor right away — and again every month — scraping typically several hundred to several thousand results per run, depending on your input.
Search with filters — Search by keyword. Filter by 🚻 gender, 🥋 method, 🏁 result, and more.
Multiple input modes — 🥊 fights (per-fighter statlines) or 👤 fighters (profiles + career rates) or 🏆 rankings or 📅 events. Switch modes without re-scraping.
Detail enrichment — Fetch structured metadata for each MMA.
Incremental mode — Only get new or changed MMA records since your last run. Content hash per MMA — no duplicates, no re-processing.
Change classification — Track unchanged, expired, cross-run duplicate detection across runs. Build audit trails of how MMA records evolve over time.
Compact output — Emit core fields only (AI-agent / MCP-friendly). Keeps response size small for LLM workflows.
Result cap — Stop after N MMA records (up to 10.000). Set to 0 for the full catalog.
Export anywhere — Download as JSON, CSV, or Excel. Stream via Apify API, webhooks, or integrations with Make, Zapier, Airbyte, Keboola.
Structured data — Every MMA returns the same schema with consistent field naming. All fields always present — null when unavailable, never omitted.
Data pipeline automation Integrate with your ETL pipeline to collect structured MMA records from espn.com on a schedule. Export to CSV, JSON, or directly to your database. Use compact mode to control output size.
Market research Monitor MMA records, track trends, and analyze market dynamics with structured, deduplicated data from espn.com.
Change monitoring Run daily or hourly in incremental mode to capture only new, updated, or expired ${NOUNS}. Perfect for price-tracking, churn analysis, and alerting pipelines.
AI / LLM training data Structured JSON per MMA is ready for RAG pipelines, embeddings, and agent workflows. Compact mode trims tokens for LLM context windows.
{
"query": "ufc",
"season": "2024",
"mode": "fights",
"maxResults": 50,
"includeDetails": true
}| Parameter | Type | Default | Description |
|---|---|---|---|
query |
string | — | Promotion slug to scrape: "ufc", "pfl", "bellator", and 45 more that ESPN carries. Use a JSON array for several, e.g. ["ufc","pfl"]. This is NOT a free-text search — to find a fighter or an event by name, leave this as the promotion and use Name Search, or paste an ESPN URL into Start URLs. |
mode |
enum | "fights" |
fights = one row per fighter per bout with the full 42-stat line. fighters = roster profiles with career rates and a W-L-D breakdown. rankings = divisional + pound-for-pound boards. events = one row per card. |
season |
string | — | Scope to one or more seasons, e.g. "2024". Accepts a JSON array like ["2023","2024"]. Applies to fights and events only — a roster and a ranking board are current, not historical, so fighters and rankings mode ignore it and say so in the log. Leave empty for ESPN’s current season. |
startUrls |
array | [] |
Paste espn.com URLs. What each one returns depends on the Record type: in fights mode a fighter page gives that fighter’s whole bout log and a fightcenter page gives the card, while in fighters mode a fighter page gives that one profile and in events mode a fightcenter page gives that one event. A fightcenter link that names a bout (…/fightId/…) gives exactly that bout. Each URL becomes its own search and results are merged and deduplicated. A URL that does not match the selected mode is skipped with a message. Rankings are selected by promotion and cannot be addressed by URL. |
maxResults |
integer | 50 |
Maximum rows to deliver (0 = unlimited). A row is whatever the selected mode produces: one fighter’s statline in a bout, a fighter, a ranking entry, or an event. In fights mode each bout yields two rows, one per corner. |
fromDate |
string | — | Only events on or after this date (YYYY-MM-DD). Narrowing by date marks the run coverage incomplete, so incremental mode will not mark unseen records as expired. |
toDate |
string | — | Only events on or before this date (YYYY-MM-DD). |
weightClass |
string | — | Substring match, e.g. "Lightweight", "Women's Strawweight". In rankings mode it matches the board name. |
gender |
enum | "" |
Restrict to one gender. Applies to fighters and rankings. |
fighterCountry |
string | — | Substring match on the fighter's citizenship, e.g. "Brazil". |
stance |
string | — | Orthodox, Southpaw or Switch. |
gym |
string | — | Substring match on the fighter’s gym, e.g. "American Top Team", "Pitbull". |
minWins |
integer | — | Drop fighters whose career record shows fewer wins than this. A fighter with no record on file is dropped, not kept. |
minLosses |
integer | — | Drop fighters with fewer career losses than this. |
titleFightsOnly |
boolean | false |
Keep only championship-length (five-round) bouts. ESPN ships no title flag, so a five-round main event without a belt is included too. |
methodFilter |
enum | "any" |
Keep only bouts that ended this way. |
resultFilter |
enum | "any" |
Keep only rows where this fighter won, lost, or drew. |
searchQuery |
string | — | Substring match on fighter name (or event name in events mode). |
activeOnly |
boolean | false |
Fighters mode only: drop fighters ESPN marks inactive. |
includeCareerDefense |
boolean | false |
Fighters mode only. Adds strikes absorbed per minute, striking defence and takedown defence — computed from the opponent side of every bout in the fighter’s log, because the source publishes no defensive career stat. Several times slower per fighter, so it is off by default. Each row reports measuredBouts and measuredMinutes, the basis the figures rest on. |
includeOdds |
boolean | false |
Adds the money line for each fighter (open, close and current), the implied win probability with and without the bookmaker margin, line movement, the best price across the books, and the scheduled-rounds over/under. This is a HISTORICAL archive, not a live feed, and it is not complete: sampled 2026-09-01, 2020-2025 priced 20-24 of every 21-24 bouts checked, 2019 about half, 2018 and 2026 none at all. Closing lines exist only for 2024-2025. Bouts the books did not price, or that fall outside that window, come back with the odds columns empty. |
includeDetails |
boolean | true |
Adds each fighter’s career averages to their fight rows: strikes landed per minute, striking and takedown accuracy, KO/TKO and decision shares. Slower per row. The 42-stat line for the bout itself is always included and is not affected by this. |
compact |
boolean | false |
Core fields only (for AI-agent/MCP workflows). |
excludeEmptyFields |
boolean | false |
Drop null, empty-string, and empty-array fields from each record before push. Smaller payloads for AI agents and dashboards. |
incrementalMode |
boolean | false |
Compare against the previous run and label each row NEW, UPDATED, REAPPEARED, UNCHANGED or EXPIRED. By default you get NEW, UPDATED and REAPPEARED; the other two are opt-in below. stateKey is optional — leave it empty and the actor derives a stable key from the search inputs, so different filter sets never share state. |
stateKey |
string | — | Optional. Stable identifier for the tracked universe. Leave empty to auto-generate from search inputs. |
emitUnchanged |
boolean | false |
Incremental mode only. Off by default: rows that have not changed since the previous run are skipped, so you are not billed for them again. Turn on to receive the full universe every run. |
emitExpired |
boolean | false |
Incremental mode only. When a record a previous run delivered is gone from the source, emit a row for it marked EXPIRED, carrying its listingId so you can reconcile. Knowing something is gone requires seeing the whole set, so turning this on makes the run read the full universe for your search even when Max Results is small — the extra reading is not charged, and expired rows still count against Max Results. Suppressed automatically when the run used a date window or when part of the source failed to respond, because neither run saw the whole universe. |
skipReposts |
boolean | false |
When incremental, skip records whose content matches an expired record from a prior run (cross-run duplicate detection). |
telegramToken |
string | — | Telegram bot token (from @BotFather). Required for Telegram notifications. |
telegramChatId |
string | — | Telegram chat or channel ID (e.g. "-100123456789"). Required when telegramToken is set. |
discordWebhookUrl |
string | — | Discord incoming webhook URL. Server Settings → Integrations → Webhooks → New Webhook. |
slackWebhookUrl |
string | — | Slack incoming webhook URL. api.slack.com/messaging/webhooks. |
notificationLimit |
integer | 5 |
Maximum number of records included in each notification message (1–20). |
notifyOnlyChanges |
boolean | false |
When Incremental Mode is on, only send notifications for records marked NEW, UPDATED or REAPPEARED. Has no effect outside incremental mode. |
whatsappAccessToken |
string | — | WhatsApp Cloud API permanent access token (System User token from Meta Business). Recipient must have messaged the business number within the last 24h (service-conversation window — free since Nov 2024). |
whatsappPhoneNumberId |
string | — | Your WhatsApp Business phone-number ID (numeric, from Meta dashboard). Required when whatsappAccessToken is set. |
whatsappTo |
string | — | Recipient phone in E.164 format without + (e.g. "436641234567"). Recipient must have messaged your business number within last 24h. |
webhookUrl |
string | — | Receives a JSON POST with {metadata, items} after each run. Universal escape hatch for n8n / Make / Zapier / custom backends. |
webhookHeaders |
object | — | Optional JSON object of custom headers (e.g. {"Authorization":"Bearer ..."}). |
appConnector |
string | — | Optional. Pick a connected app under Settings → API & Integrations to receive your results. The app receives up to the first 500 rows of the run and only rows that carry data — records marked EXPIRED are not sent, so an app-side copy will keep a record the source has dropped. The full run is always in the dataset. Best-effort across MCP connectors as Apify expands its catalog. |
mcpIssueTeam |
string | — | Only when the connected app is an issue tracker: the team (name or ID) the summary issue is created under, if that app requires one. |
Every MMA returns the same 174-field schema. Missing values are null — never omitted.
recordTypenamefightIdeventIdeventNamedatepromotionweightClasscardSegmentmatchNumberscheduledRoundsroundLengthSecondsvenuevenueNamevenueCityvenueStatevenueCountrylocationrefereeisCompletedboutCountfighter1Idfighter1Namefighter2Idfighter2NamewinnerIdwinnerNameisDrawisNoContestisTitleFightmethodmethodCategorymethodDetailmethodTargetroundtimefighterIdfighterNamenicknamecornerisWinnerresultopponentNameopponentIdopponentcountrygenderagedateOfBirthisActivestancereachInchesheightInchesweightLbsgymstylesaccoladescareerRecordfighterUrlheadshotUrlwinslossesdrawsnoConteststkoWinstkoLossessubmissionWinssubmissionLossestitleWinstitleLossestitleDrawstotalFightscareerStrikeLPMcareerStrikeAccuracycareerTakedownAvgcareerTakedownAccuracycareerSubmissionAvgcareerKoTkoRatecareerDecisionRatemeasuredStrikesLandedPerMinutemeasuredStrikesAbsorbedPerMinutemeasuredStrikeDifferentialPerMinutemeasuredStrikeDefensemeasuredTakedownDefensemeasuredBoutsmeasuredMinutesoddsProvideroddsProviderCountmoneyLinemoneyLineOpenmoneyLineCloseclosingLineProvidermoneyLineDecimalmoneyLineMovementimpliedProbabilityMovementbestMoneyLinebestMoneyLineProviderimpliedProbabilitySpreadisFavoriteisPickEmimpliedWinProbabilityimpliedWinProbabilityNoVigopponentMoneyLineopponentImpliedWinProbabilityroundsOverUnderoverOddsunderOddsrankingBoardrankingTyperankrankTrendtitleDefensesisChampionknockDownstotalStrikesAttemptedtotalStrikesLandedsigStrikesAttemptedsigStrikesLandedsigDistanceHeadStrikesAttemptedsigDistanceHeadStrikesLandedsigDistanceBodyStrikesAttemptedsigDistanceBodyStrikesLandedsigDistanceLegStrikesAttemptedsigDistanceLegStrikesLandedsigClinchBodyStrikesAttemptedsigClinchBodyStrikesLandedsigClinchHeadStrikesAttemptedsigClinchHeadStrikesLandedsigClinchLegStrikesAttemptedsigClinchLegStrikesLandedsigGroundHeadStrikesAttemptedsigGroundHeadStrikesLandedsigGroundBodyStrikesAttemptedsigGroundBodyStrikesLandedsigGroundLegStrikesAttemptedsigGroundLegStrikesLandedtakedownsAttemptedtakedownsLandedtakedownsSlamstakedownAccuracyadvancesreversalssubmissionstimeInControlsigDistanceStrikesAttemptedsigDistanceStrikesLandedsigClinchStrikesAttemptedsigClinchStrikesLandedsigGroundStrikesAttemptedsigGroundStrikesLandedsigStrikesAbsorbedsigStrikeDefensetakedownsDefendedtakedownDefensesigStrikeAccuracyadvanceToHalfGuardadvanceToSideadvanceToMountadvanceToBackslamRateurlportalUrllistingIdsearchQuerycontentQualityreadIncompletedetailFetchedscrapedAtsourcecontentHashchangeTypeisRepostrepostOfIdrepostDetectedAt
One object per MMA. Here is a real example from a production run:
{
"recordType": "fight",
"name": "Felipe Bunes",
"fightId": "401623977",
"eventId": "600039893",
"eventName": "UFC Fight Night: Ankalaev vs. Walker 2",
"date": "2024-01-13T21:00Z",
"promotion": "ufc",
"weightClass": "Flyweight",
"cardSegment": "Prelims",
"matchNumber": 11,
"scheduledRounds": 3,
"roundLengthSeconds": 300
}Truncated — full records contain 174 fields. See Output fields for the complete schema.
Try UFC & MMA Stats Scraper for espn.com now — $5 free credit, no credit card →
Pay only for what you extract. No subscription required — Apify's free $5 credit covers thousands of results.
| Event | Price (USD) |
|---|---|
| Actor Start | $0.001 |
| Result | $0.0005 |
See the actor on Apify for current pricing.
How do I scrape espn.com? Use this actor on Apify to extract structured data from espn.com. Configure your search query and filters in the input, then click Start — no coding required.
How do I get espn.com data as JSON, CSV, or Excel? The actor writes each MMA to Apify's dataset. Download as JSON, CSV, or Excel from the Console, stream via the API, or push to Make, Zapier, Airbyte, or Keboola.
Is it legal to scrape espn.com? Web scraping of publicly available data is generally legal. This actor only accesses publicly visible information. Always check espn.com's terms of service for your specific use case.
How much does it cost? Pay-per-event pricing — you only pay for ${NOUNS} extracted. Apify's free $5 credit is enough to run thousands of results before you pay anything.
How does incremental mode work? Each MMA gets a content hash. On subsequent runs, only new or changed MMA records are emitted — saving time, compute, and storage. Expired MMA records can be tracked separately.
Do I need an API key or credentials? No. Just sign up for Apify, paste your input, and click Start. No credit card required.
Browse all Black Falcon Data actors →
New to Apify? Create a free account with $5 credit — no credit card required.
- Sign up — $5 platform credit included
- Open UFC & MMA Stats Scraper for espn.com and configure your input
- Click Start — export results as JSON, CSV, or Excel
Need more later? See Apify pricing.
Black Falcon Data builds production-grade web scrapers for job boards and marketplace data. Browse our full actor catalog at www.blackfalcondata.com.
Last updated: 2026 09