Accessing ESPN Fantasy Football for Private Leagues

In my previous post, I explained how to access the ESPN Fantasy Football’s new (V3) public API. But what if your league manager does not make the league public? Are you out of luck?

In this post I’ll explain how to pass ‘cookies’ to ESPN and access private league data.

To accomplish this, we’ll use…

  • Python
  • retriculate
  • purrr

Python?!

Yes - Believe it or not, though I’m an avid R user, I believe in using the best tool for the job. I code in Python about as well as I speak Spanish. I have to think really hard as I translate vocabulary, I don’t always know what I’m saying, and I often have to repeat something verbatim that I read somewhere else.

Despite my best efforts to accomplish this in R, I simply have not been able to make it work yet. (Please let me know if you or someone else figures out how to do this!) Again, major thanks to Steve Morse for this post. Follow him on twitter @thestevemo.

So, my motto here, to butcher a quote from “Tropic Thunder” is to, “never go full Python….” In light of that, it gave me a great opportunity to learn retriculate.

Access Private League Data

Take the steps below to access private leagues. Again, if your league is public, you can access the API like so.

Build URL

To begin, we build the URL to access the API. This is the same as if we access a public league. Restating from my previous post - replace the leagueID with your private league ID. In the ‘tail’ of the URL, we request different chunks of data from the API. These are all the elements of which I’m aware. Feel free to shorten if you do not need to request all the data.

base = "http://fantasy.espn.com/apis/v3/games/ffl/seasons/"
year = "2019"
mid = "/segments/0/leagues/"
leagueID = "12345678"
tail = "?view=mDraftDetail&view=mLiveScoring&view=mMatchupScore&view=mPendingTransactions&view=mPositionalRatings&view=mSettings&view=mTeam&view=modular&view=mNav&view=mMatchupScore"
url = paste0(base,year,mid,leagueID,tail)

Find Cookies

The ESPN fantasy football API requires two cookies; swid and espn_s2.

You can find both of these cookies in Chrome in the following path:

Settings >> Privacy and Security >> Site Settings >> Cookies >> See All Cookies and Site Data

They’ll be listed among the ‘ESPN’ cookies. You can find these two cookies similarly in other browsers.

swid = "{cookie-with-curly-brackets-included}"
espn_s2 =  "very%long%cookie%296%ish%characters%long%with%no%brackets%"

What Currently Does Not Work (R)

ESPNGet <- httr::GET(url = url,
                     httr::set_cookies(
                       `swid` = "{cookie-with-curly-brackets-included}",
                       `espn_s2` =  "very%long%cookie%296%characters%long%with%no%brackets%"
                     ))
ESPNGet$status_code
## [1] 401

The status code is 401. This means the request has been blocked and we cannot access the data through the API using this technique.

What does work: Python in R

Since we cannot use R to access the API, lets replicate Steve’s work in Python. Since we want to manipulate and eventually visualize the data in R, lets access the data using Python code in R.

First, ensure Python is installed on your computer. The latest release of Python should be located for Windows or MAC users.

Next, install the reticulate package with install.packages("reticulate") and call the library. Thanks to Kevin Ushey for the great package that helps Python illiterate people like me execute a few lines of Python code in R.

library(reticulate)

Should future Python code not work, it could be because R is unable to find where Python is installed. You can point R in the right direction with this call: use_Python("/path/to/Python_folder")

The reticulate packages creates an R/Python marriage.

Note The code below is Python. According to the reticulate documentation, R and Python will pass variables back and forth between each other. Pass R objects to Python by preceding the r variable with r.. Python objects pass to R by preceding the object with py$.

In order to access the URL built from r code we write execute the following:

r.url
## 'http://fantasy.espn.com/apis/v3/games/ffl/seasons/2019/segments/0/leagues/89417258?view=mDraftDetail&view=mLiveScoring&view=mMatchupScore&view=mPendingTransactions&view=mPositionalRatings&view=mSettings&view=mTeam&view=modular&view=mNav&view=mMatchupScore'

Request Data Using Python

In order to access the API, execute the code below with your cookies as explained above.

import requests

r = requests.get(r.url,
                 cookies={"SWID": "{cookie-with-curly-brackets-included}",
                          "espn_s2": "very%long%token%296%characters%long%with%no%brackets%" })
d = r.json()
r.status_code
## 200

If you’ve provided the correct URL and cookies, you should receive status 200. This means you have successfully accessed your league’s private data.

Manipulate the Data

Now that we have accessed private league ESPN Fantasy Football data, we need to get it in a usable format. In my previous post, because we were able to access our public league data using R code, our data was in the shape of a dataframe. Using this technique, the data is in JSON format.

This is taylor made for purrr. We are able to parse the JSON and extract the data of interest.

Team Records

The first basic piece of information to extract is team records.

library(tidyverse)
library(purrr)
TeamRecords =
  py$d$teams %>%
  map_df(magrittr::extract, c("location", "nickname", "id")) %>%
  bind_cols(
    py$d$teams %>%
    map("record") %>%
    map("overall") %>%
    map_df(magrittr::extract, c("losses", "wins"))
  ) %>%
  unite(Team, c(location, nickname), sep = " ")
Team id losses wins
’R’m Chair Quarterback 1 9 4
Philly Chapmaniacs 2 4 9
The Plainsmen 3 7 6
The OBJective Functions 4 7 6
Analysis Paralysis 5 9 4
Team Ward 6 9 4
Compute This! 7 2 11
The Chief 8 5 8
Dallas The boys 9 8 5
Palindrome Tikkit 10 5 8

Next, we’ll extract the schedule.

Schedule =
py$d$schedule %>%
  map_df(magrittr::extract, c("winner","matchupPeriodId")) %>%
  bind_cols(
    py$d$schedule %>%
    map("away") %>%
    map_df(magrittr::extract,c("teamId","totalPoints"))
  ) %>%
  bind_cols(
    py$d$schedule %>%
    map("home") %>%
    map_df(magrittr::extract,c("teamId","totalPoints"))
  )

Future Posts

In subsequent posts, I’ll analyze our league and share the code so you can do the same. In particular, I hope to explore…

  • Measures of luck: Does a team’s record really indicate their quality?
  • The accuracy of ESPN’s player projections.
  • How well player’s perform vs their draft position.