Skip to content

Instantly share code, notes, and snippets.

@ozzieliu
Last active September 9, 2023 16:56
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save ozzieliu/9fbbc83b354c568709dc4e6a30fea54f to your computer and use it in GitHub Desktop.
Save ozzieliu/9fbbc83b354c568709dc4e6a30fea54f to your computer and use it in GitHub Desktop.
Proof of Concept task to get ticket prices and event info using StubHub's API with Python
# Quick intro to accessing Stubhub API with Python
# Ozzie Liu (ozzie@ozzieliu.com)
# Related blog post: http://ozzieliu.com/2016/06/21/scraping-ticket-data-with-stubhub-api/
# Updated 3/5/2017 for Python 3 and Stubhub's InventorySearchAPI - v2
import requests
import base64
import json
import pprint
import pandas as pd
import datetime
#### Step 1: # Obtaining StubHub User Access Token ####
## Enter user's API key, secret, and Stubhub login
app_token = input('Enter app token: ')
consumer_key = input('Enter consumer key: ')
consumer_secret = input('Enter consumer secret: ')
stubhub_username = input('Enter Stubhub username (email): ')
stubhub_password = input('Enter Stubhub password: ')
## Generating basic authorization token
combo = consumer_key + ':' + consumer_secret
basic_authorization_token = base64.b64encode(combo.encode('utf-8'))
# print(basic_authorization_token)
## POST parameters for API call
headers = {
'Content-Type':'application/x-www-form-urlencoded',
'Authorization':'Basic '+basic_authorization_token.decode('utf-8'),}
body = {
'grant_type':'password',
'username':stubhub_username,
'password':stubhub_password,
'scope':'PRODUCTION'}
## Making the call
url = 'https://api.stubhub.com/login'
r = requests.post(url, headers=headers, data=body)
token_respoonse = r.json()
access_token = token_respoonse['access_token']
user_GUID = r.headers['X-StubHub-User-GUID']
#### Step 2 - Searching inventory for an event ####
inventory_url = 'https://api.stubhub.com/search/inventory/v2'
headers['Authorization'] = 'Bearer ' + access_token
headers['Accept'] = 'application/json'
headers['Accept-Encoding'] = 'application/json'
## Enter event ID
eventid = '9837052'
data = {'eventid':eventid}
## GET request and change to Pandas dataframe
inventory = requests.get(inventory_url, headers=headers, params=data)
inv = inventory.json()
listing_df = pd.DataFrame(inv['listing'])
## Getting ticket prices out of the currentPice column of dicts - assuming they're all in USD
listing_df['amount'] = listing_df.apply(lambda x: x['currentPrice']['amount'], axis=1)
## Export to a csv file
listing_df.to_csv('tickets_listing.csv', index=False)
#### Step 3 - Adding Event and Venue Info ####
## Calling the eventsearch api
info_url = 'https://api.stubhub.com/catalog/events/v2/' + eventid
info = requests.get(info_url, headers=headers)
# pprint.pprint(info.json())
info_dict = info.json()
event_date = datetime.datetime.strptime(info_dict['eventDateLocal'][:10], '%Y-%m-%d')
event_name = info_dict['title']
event_date = info_dict['eventDateLocal'][:10]
venue = info_dict['venue']['name']
snapshot_date = datetime.datetime.today().strftime('%m/%d/%Y')
listing_df['snapshotDate'] = snapshot_date
listing_df['eventName'] = event_name
listing_df['eventDate'] = event_date
listing_df['venue'] = venue
my_col = ['snapshotDate','eventName','eventDate', 'venue', 'sectionName', 'row',
'seatNumbers', 'quantity', 'deliveryTypeList', 'amount']
final_df = listing_df[my_col]
## Exporting final report
final_df.to_csv('tickets_listing_with_info.csv', index=False)
@fgscivittaro
Copy link

Yes, I had the same issue. It appears to max out at 250 individual listings. Is there a way to get around this and retrieve all tickets at once?

@ozzieliu
Copy link
Author

@cliftonbaker81 @fgscivittaro From what I've seen, their API does have a maximum number of 250 listings imposed per query. One workaround is to make several queries by paging through all the listings. You can look at totalListings and then vary the start, end or rows parameters to get all the listings, 250 at a time.

I've also found that making the same GET query with a browser actually returns all the results. So might be able to explore changing the user-agent in requests. Try it at your own risk of course.

@chrislkeller
Copy link

@ozzieliu @fgscivittaro: I added some crude while looping to increment the starting point of the listings based on the total number of listings. This worked for me, retrieving all 1955 listings into a single spreadsheet. Your mileage may vary.

import requests
import base64
import json
import pprint
import pandas as pd
import datetime
import logging

logger = logging.getLogger("root")
logging.basicConfig(
    format = "\033[1;36m%(levelname)s: %(filename)s (def %(funcName)s %(lineno)s): \033[1;37m %(message)s",
    level=logging.DEBUG
)

APP_TOKEN = ''

CONSUMER_KEY = ''

CONSUMER_SECRET = ''

STUBHUB_USERNAME = ''

STUBHUB_PASSWORD = ''

# generating basic authorization token
COMBO = CONSUMER_KEY + ':' + CONSUMER_SECRET

BASIC_AUTHORIZATION_TOKEN = base64.b64encode(COMBO.encode('utf-8'))

HEADERS = {
    'Content-Type':'application/x-www-form-urlencoded',
    'Authorization':'Basic '+BASIC_AUTHORIZATION_TOKEN.decode('utf-8'),
}

BODY = {
    'grant_type': 'password',
    'username': STUBHUB_USERNAME,
    'password': STUBHUB_PASSWORD,
    'scope': 'PRODUCTION'
}

DATA = {
    'eventid': '',
    'sort': 'quantity desc',
    'start': 0,
    'rows': 100
}


class StuHubEventSearch(object):

    ## making the call
    url = 'https://api.stubhub.com/login'

    inventory_url = 'https://api.stubhub.com/search/inventory/v2'

    def _init(self, *args, **kwargs):
        r = requests.post(self.url, headers=HEADERS, data=BODY)
        token_response = r.json()
        user_GUID = r.headers['X-StubHub-User-GUID']
        HEADERS['Authorization'] = 'Bearer ' + token_response['access_token']
        HEADERS['Accept'] = 'application/json'
        HEADERS['Accept-Encoding'] = 'application/json'
        HEADERS['User-agent'] = "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/1.0.154.53 Safari/525.19"
        inventory = requests.get(self.inventory_url, headers=HEADERS, params=DATA)
        inv = inventory.json()
        rows = inv['rows']
        start = inv['start']
        total_listings = inv['totalListings']
        list_of_tickets = self.make_request_to_url(self.inventory_url, rows, start, total_listings)
        final_df = self.create_and_return_data_frame(list_of_tickets)
        csv_file_name = "_%s_%s_tickets_listing.csv" % (datetime.datetime.today().strftime('%Y_%m_%d'), total_listings)
        final_df.to_csv(csv_file_name, index=False)


    def make_request_to_url(self, url, rows, start, total):
        all_listings = []
        DATA = {
            'eventid': '103128027',
            'sort': 'quantity desc',
            'start': start,
            'rows': 100
        }
        while DATA['start'] < total:
            inventory = requests.get(self.inventory_url, headers=HEADERS, params=DATA)
            inv = inventory.json()
            all_listings = all_listings + inv['listing']
            logger.debug("Retrieving listings starting at %s" % (DATA['start']))
            DATA['start'] = DATA['start'] + inv['rows']
        return all_listings


    def create_and_return_data_frame(self, list_of_tickets):
        listing_df = pd.DataFrame(list_of_tickets)
        listing_df['current_price'] = listing_df.apply(lambda x: x['currentPrice']['amount'], axis=1)
        listing_df['listed_price'] = listing_df.apply(lambda x: x['listingPrice']['amount'], axis=1)
        listing_df['snapshotDate'] = datetime.datetime.today().strftime('%Y-%m-%d %H:%m')
        listing_df['eventName'] = 'World Series'
        listing_df['eventDate'] = '2017-10-24'
        listing_df['venue'] = 'Dodgers Stadium'
        my_col = [
            'snapshotDate',
            'eventName',
            'eventDate',
            'venue',
            'sectionName',
            'row',
            'seatNumbers',
            'quantity',
            'sellerOwnInd',
            'current_price',
            'listed_price',
            'listingId',
        ]
        final_df = listing_df[my_col]
        return final_df


if __name__ == '__main__':
    task_run = StuHubEventSearch()
    task_run._init()
    print "\nTask finished at %s\n" % str(datetime.datetime.now())

@ozzieliu
Copy link
Author

ozzieliu commented Nov 8, 2017

@chrislkeller that's great! Thanks!

@sasiegel
Copy link

sasiegel commented Dec 9, 2017

This has been very helpful, thank you.

@gitkwr
Copy link

gitkwr commented May 25, 2018

I can't seem to find how to get the API keys, any guidance?

@ozzieliu
Copy link
Author

@Rohit-Patel, you'll have to sign up on Stubhub's developer portal and request for a key: https://developer.stubhub.com/

@eaglesphan088
Copy link

Hi - has anyone tried to build code to download sold ticket data ( versus only the listed data)? They display this data when you are listing tickets and click "get more pricing info" on the "set your price" page. Not sure if that data would have to be scraped or if there's a way to use the API.

Thanks!

@tsofoon
Copy link

tsofoon commented Jul 15, 2019

interesting project! I can't get past this line even after getting the access token. any thoughts?

KeyError Traceback (most recent call last)
in
26 r = requests.post(url, headers=headers, data=body)
27 token_respoonse = r.json()
---> 28 access_token = token_respoonse['access_token']
29 user_GUID = r.headers['X-StubHub-User-GUID']

KeyError: 'access_token'

@iherma02
Copy link

@tsofoon I have the same error, still unsure of how to proceed.

To see what the actual HTTP response is, it could be helpful to print(r) after you generate the response in line 26. When I do that, I simply get <Response [401]> in my terminal, indicating some sort of unauthorized access. I wonder if you'd get the same response.

Can anyone help with this?

@iherma02
Copy link

@tsofoon have you actually gotten the access token yet? Fixed my issue by just using https://cryptii.com/pipes/text-to-base64 instead of the Python function to encode the concatenated keys, the outputs were wildly different (no clue why), and then just using the GUI at https://developer.stubhub.com/oauth/apis/post/accesstoken to get the token, worked like a charm, no 401 response. Also, make sure you're sending StubHub.com credentials and not credentials for you StubHub developer account.

If you've already done this and have your access token, then just disregard this, figured it might be useful.

@jakepschwartz
Copy link

@tsofoon have you actually gotten the access token yet? Fixed my issue by just using https://cryptii.com/pipes/text-to-base64 instead of the Python function to encode the concatenated keys, the outputs were wildly different (no clue why), and then just using the GUI at https://developer.stubhub.com/oauth/apis/post/accesstoken to get the token, worked like a charm, no 401 response. Also, make sure you're sending StubHub.com credentials and not credentials for you StubHub developer account.

If you've already done this and have your access token, then just disregard this, figured it might be useful.

Hi, I still have issues connecting. I have consumer key, consumer secret, using user stubhub account (not dev). Convert to base64. I'm able to generate access_token manually using the webpage https://developer.stubhub.com/oauth/apis/post/accesstoken, but not from python. Already tried switching grant_type client_credentials or password, url, everything. I even tried by-passing step 1 and manually entered access_token in step 2.

Here's the original code:

POST parameters for API call

headers = {
'Content-Type':'application/x-www-form-urlencoded',
'Authorization':'Basic '+basic_authorization_token.decode('utf-8'),}
body = {
'grant_type':'password',
'username':stubhub_username,
'password':stubhub_password,
'scope':'PRODUCTION'}

Making the call

url = 'https://api.stubhub.com/login'
r = requests.post(url, headers=headers, data=body)
token_respoonse = r.json()

Many thanks, warm regards

@rcarmody09
Copy link

@tsofoon have you actually gotten the access token yet? Fixed my issue by just using https://cryptii.com/pipes/text-to-base64 instead of the Python function to encode the concatenated keys, the outputs were wildly different (no clue why), and then just using the GUI at https://developer.stubhub.com/oauth/apis/post/accesstoken to get the token, worked like a charm, no 401 response. Also, make sure you're sending StubHub.com credentials and not credentials for you StubHub developer account.
If you've already done this and have your access token, then just disregard this, figured it might be useful.

Hi, I still have issues connecting. I have consumer key, consumer secret, using user stubhub account (not dev). Convert to base64. I'm able to generate access_token manually using the webpage https://developer.stubhub.com/oauth/apis/post/accesstoken, but not from python. Already tried switching grant_type client_credentials or password, url, everything. I even tried by-passing step 1 and manually entered access_token in step 2.

Here's the original code:

POST parameters for API call

headers = {
'Content-Type':'application/x-www-form-urlencoded',
'Authorization':'Basic '+basic_authorization_token.decode('utf-8'),}
body = {
'grant_type':'password',
'username':stubhub_username,
'password':stubhub_password,
'scope':'PRODUCTION'}

Making the call

url = 'https://api.stubhub.com/login'
r = requests.post(url, headers=headers, data=body)
token_respoonse = r.json()

Many thanks, warm regards

I am in the same boat here... Any help appreciated

@jakepschwartz
Copy link

jakepschwartz commented Apr 10, 2021 via email

@Chris123xc
Copy link

What are you using for app token input?

@ozzieliu
Copy link
Author

@Chris123xc that's what you'll have to provide with your stubhub API credentials (if the API is still available, it's been a few years since i wrote this up)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment