# Imports first # This is the sdk found at https://github.com/timotheus/ebaysdk-python from ebaysdk.trading import Connection as Trading from ebaysdk.exception import ConnectionError, ConnectionResponseError # Parallel requires Gevent and Grequests modules to be available on the server (requires at least 2.7.5) from ebaysdk.parallel import Parallel import datetime import csv # Application Settings app_id = '' dev_id = '' crt_id = '' domain = 'api.sandbox.ebay.com' # User Identification usr_token = '' # CSV file data csv_path = '' csv_delimiter = ',' csv_quote = '\'' # We can probably put the DateRange functions into a class # Creates a dateRange list for use with glue def setDateRange(days=None, start=None, stop=None, rangeType=None): # Default to searching for the current 24 hours, minus 1ms # Formats the date presented to it according to what we need, defaults to today def checkDate(theDate=None): if theDate != None: if type(theDate) == type(datetime.datetime.today()): return theDate elif type(theDate) == type(''): try: return datetime.datetime.strptime(theDate, '%Y-%m-%d') except ValueError: return datetime.datetime.today() else: return datetime.datetime.today() else: return datetime.datetime.today() # Set our days to an int, defaults to 0 try: days = int(days) except (TypeError, ValueError): days = 0 # Set our rangeType to str, defaults to 'end' if rangeType != None: rangeType = str(rangeType) else: rangeType = 'end' # Set the days argument to search forward/backward more than one day delta = datetime.timedelta(days) start_time = checkDate(start) if stop != None: end_time = checkDate(stop) else: end_time = start_time + delta # Convert our dates into a format that the api can recognize (ISO 8601) start_time = start_time.strftime("%Y-%m-%dT00:00:00.000Z") # Force the future to be the absolute end of the day end_time = end_time.strftime("%Y-%m-%dT23:59:59.999Z") # if the end_time is in the past, reverse order (EG, the delta is a negative number) if end_time < start_time: start_time_old = start_time start_time = end_time end_time = start_time_old return {'from': start_time, 'to': end_time, 'type': rangeType} # We call this a couple times, so it gets its own function def switchDateRange(list=None, range=None): # Switch statement to check which type of dateRange to search by # Will always return a valid dateRange for use as various itemArgs # Defaults to a dateRange ending today if (range == None) | (isinstance(range, dict) != True): # Sets the default range (items that are ending today) range = setDateRange() if (list == None) | (isinstance(list, dict) != True): list = {} # This can be start/mod/end def rangeType(condition=None): # No condition to check, defaults to End if condition == None: return 'End' # Cast the condition to a string condition = str(condition) switch = { 'start': 'Start', # Covers StartTimeFrom and StartTimeTo - Items that started in this date range 'mod': 'Mod', # Covers ModTimeFrom and ModTimeTo - Items modified in this date range 'end': 'End' # Covers EndTimeFrom and EndTimeTo - Items that end within this date range } # Return the approiate text or End if no ID matches return switch.get(condition, 'End') # Override the dates in sellerList with values from dateRange, if provided if ('from' in range) & ('type' in range): # Remove the list of keys from sellerList - We can only have one search range for o in ['EndTimeFrom','ModTimeFrom','StartTimeFrom']: try: del list[o] except KeyError: continue list[rangeType(range['type'])+'TimeFrom'] = range['from'] if ('to' in range) & ('type' in range): # Remove the list of keys from sellerList - We can only have one search range for o in ['EndTimeTo','ModTimeTo','StartTimeTo']: try: del list[o] except KeyError: continue list[rangeType(range['type'])+'TimeTo'] = range['to'] return list # Here are the error codes that we currently have # None - No events have happened yet # 0 - Everything is fine # 1 - The API variable is either unset, or is not a valid connection to the ebay API # 2 - The required list is either unset, or is not the required type - Usually dict, but can be a list of dicts # 3 - EndTimeFrom or EndTimeTo were not set in the list - getSeller only # 4 - Relates to 3, but specifically refers to a dateRange not being available for glue # 5 - No data (useful) returned by API - A field of data we need is None # minor logic checking - We don't want to get trackbacks for silly reasons # getSeller gets a list of our itemIDs def getSeller(api=None, list=None): # api is the connection to the Trading API # list is a dict containing required information for ebay to search for (http://developer.ebay.com/DevZone/XML/docs/Reference/ebay/GetSellerEvents.html) # - The datapoints that MUST be defined are EndTimeFrom and EndTimeTo - These select the dateRange that items end on ebay # Set our error conditions res = { 'error': { 'code': None, 'msg': None, 'fnc': 'getSeller' }, 'apiResponse': {} } if api == None: res['error']['code'] = '1' res['error']['msg'] = 'api is not set' return res if (list == None) | (isinstance(list, dict) != True): res['error']['code'] = '2' res['error']['msg'] = 'list doesn\'t exist or is of wrong type, must be dict' return res if (('EndTimeFrom' not in list) | ('EndTimeTo' not in list)) & (('StartTimeFrom' not in list) | ('StartTimeTo' not in list)): res['error']['code'] = '3' res['error']['msg'] = 'either "StartTime" or "EndTime" is not set in list' return res res['apiResponse'] = api.execute('GetSellerEvents', list).dict() # Verify that the search returned information if res['apiResponse']['ItemArray'] != None: # Check if the itemArray is setup the way we want it (list containing one or more dicts) try: for k in res['apiResponse']['ItemArray']['Item']: itemid = k['ItemID'] except TypeError: # The itemArray is not, force it to be then res['apiResponse'] = [ res['apiResponse']['ItemArray']['Item'] ] else: # No need to encase the list in another list res['apiResponse'] = res['apiResponse']['ItemArray']['Item'] # Yay no errors if res['error']['code'] == None: res['error']['code'] = '0' else: # drop the response as it contains no useful information anymore res['apiResponse'] = {} res['error']['code'] = '5' res['error']['msg'] = 'no items found - maybe the dateRange is too narrow?' return res # getItems uses the list of ItemIDs provided by getSeller to get specific information about each ItemID def getItems(api=None, itemList=None, itemArgs=None): # api is the connection to the Trading API # itemList is a dict containing ItemIDs and other info as a result of getSeller # itemArgs is an optional dict that contains extra details to refine the search returned by ebay (http://developer.ebay.com/DevZone/XML/docs/Reference/ebay/GetItem.html) # - The two required datapoints in itemArgs are IncludeItemSpecifics and ItemID, both are defined below in the loop # Set our error conditions res = { 'error': { 'code': None, 'msg': None, 'fnc': 'getItems' }, 'apiResponse': {} } if api == None: res['error']['code'] = '1' res['error']['msg'] = 'api is not set' return res if (itemList == None) | (type(itemList) != type([])): res['error']['code'] = '2' res['error']['msg'] = 'itemList doesn\'t exist or is of wrong type, must be list containing one or more dicts' return res if isinstance(itemArgs, dict) != True: res['error']['code'] = '2' res['error']['msg'] = 'itemArgs doesn\'t exist or is of wrong type, must be dict' return res # For each ItemID for k in itemList: # Extra arguments that should be applied anyways itemArgs['IncludeItemSpecifics'] = 'True' # Search for specific ItemID itemArgs['ItemID'] = k['ItemID'] # If the Item is not active, we don't want it - Prevents sending extra API resquests for data we don't want if(k['SellingStatus']['ListingStatus'] != 'Active'): continue res['apiResponse'][k['ItemID']] = api.execute('GetItem', itemArgs).dict() # We want the error code to only be changed on the first successful iteration if res['error']['code'] == None: res['error']['code'] = '0' # After the loop is complete, return the whole res return res # Use the modTime range and only return data for items that have been modified in the timerange # Args we want are the modTime and NewItemFilter=True to get only items that have changed in this timerange def checkRevisedItems(api=None, itemArgs=None, dateRange=None): # api is the connection to the Trading API # itemList is a list containing itemIDs to check for updates # itemArgs is an optional dict that contains extra details to refine the search returned by ebay res = { 'error': { 'code': None, 'msg': None, 'fnc': 'checkRevisedItems' }, 'apiResponse': { 'itemIDs': [] } } if api == None: res['error']['code'] = '1' res['error']['msg'] = 'api is not set' return res if (itemArgs == None) | (isinstance(itemArgs, dict) != True): res['error']['code'] = '2' res['error']['msg'] = 'itemArgs doesn\'t exist or is of wrong type, must be dict' return res if (dateRange == None) | (isinstance(dateRange, dict) != True): dateRange = setDateRange() itemArgs['NewItemFilter'] = 'True' # Switch statement to select the proper dateRange itemArgs = switchDateRange(itemArgs, dateRange) response = api.execute('GetSellerEvents', itemArgs).dict() try: items = response['ItemArray']['Item'] # Store the ItemIDs that need to be updated again with getItem for i in items: res['apiResponse']['itemIDs'].append(i['ItemID']) except TypeError: res['apiResponse'] = {'code': '1', 'msg': 'No items were revised in the selected dateRange'} response = None # No Errors found if res['error']['code'] == None: res['error']['code'] = '0' return res # storeItems uses the dict of Items provided by getItems and stores the information we want def storeItems(itemList=None): # itemList is the apiRequest value presented as a result of getItems # - This contains every item that matches getItems criteria with the ItemID as the key of further dicts # Set our error conditions res = { 'error': { 'code': None, 'msg': None, 'fnc': 'storeItems' }, 'apiResponse': {} } if (itemList == None) | (isinstance(itemList, dict) != True): res['error']['code'] = '2' res['error']['msg'] = 'itemList doesn\'t exist or is of wrong type, must be dict' return res # Now that we've stored all the data in a really big dictionary, lets pull only the information we want out of it for k in itemList: res['apiResponse'][k] = { 'price': {}, 'condition': {}, 'quantity': {} } condition = '0' # Switch statement - Sets condition.msg based on conditionID - based on table from http://developer.ebay.com/devzone/finding/callref/Enums/conditionIdList.html def conCheck(condition): # Cast the condition to a string condition = str(condition) switch = { '1000': 'New', '1500': 'New Other', '1750': 'New with defects', '2000': 'Manufacturer Refurbished', '2500': 'Seller Refurbished', '3000': 'Used', '4000': 'Used/Very Good Condition', '5000': 'Used/Good Condition', '6000': 'Used/Acceptable Condition', '7000': 'For Parts/Not Working' } # Return the approiate text or N/A if no ID matches return switch.get(condition, 'N/A') res['apiResponse'][k]['title'] = itemList[k]['Item']['Title'] if 'ConditionID' in itemList[k]['Item']: # Override the condition defined at the start of the loop condition = itemList[k]['Item']['ConditionID'] if 'SellingStatus' in itemList[k]['Item']: # Store current price + currency the price is in res['apiResponse'][k]['price']['cur'] = itemList[k]['Item']['SellingStatus']['CurrentPrice']['_currencyID'] res['apiResponse'][k]['price']['val'] = itemList[k]['Item']['SellingStatus']['CurrentPrice']['value'] # Store the quantity - Subtract sold from total to get current value res['apiResponse'][k]['quantity']['sold'] = itemList[k]['Item']['SellingStatus']['QuantitySold'] res['apiResponse'][k]['quantity']['total'] = itemList[k]['Item']['Quantity'] else: res['apiRequest'][k]['price']['cur'] = None res['apiRequest'][k]['price']['val'] = None res['apiRequest'][k]['quantity']['sold'] = None res['apiRequest'][k]['quantity']['total'] = None # Get the item specifics - Special fields pertaining to the item, such as manufacturer and part number if 'ItemSpecifics' in itemList[k]['Item']: # Try to for loop our data, if that fails, assume it's a dict with only one ItemSpecific try: for i in itemList[k]['Item']['ItemSpecifics']['NameValueList']: name = i['Name'] if(name == 'Brand'): res['apiResponse'][k]['mfg'] = i['Value'] if(name == 'MPN'): res['apiResponse'][k]['mpn'] = i['Value'] except TypeError: if itemList[k]['Item']['ItemSpecifics']['NameValueList']['Name'] == 'Brand': res['apiResponse'][k]['mfg'] = itemList[k]['Item']['ItemSpecifics']['NameValueList']['Value'] res['apiResponse'][k]['mpn'] = None elif itemList[k]['Item']['ItemSpecifics']['NameValueList']['Name'] == 'MPN': res['apiResponse'][k]['mpn'] = itemList[k]['Item']['ItemSpecifics']['NameValueList']['Value'] res['apiResponse'][k]['mfg'] = None else: res['apiResponse'][k]['mpn'] = None res['apiResponse'][k]['mfg'] = None # Store the condition res['apiResponse'][k]['condition']['code'] = condition res['apiResponse'][k]['condition']['msg'] = conCheck(condition) # We want the error code to only be changed on the first successful iteration if res['error']['code'] == None: res['error']['code'] = '0' # After the loop is complete, return the whole res return res # Glue logic to run all the functions above properly def glue(api=None, sellerList=None, dateRange=None): # api is the connection to the api, this is passed to the functions as called # sellerList is the options that you can present ebay for searching (http://developer.ebay.com/DevZone/XML/docs/Reference/ebay/GetSellerEvents.html) # dateRange overrides EndTimeFrom and EndTimeTo from sellerList for ease of passing these required data with nothing else # Set our error conditions res = { 'error': { 'code': '0', 'msg': None, 'fnc': 'glue' }, 'apiResponse': {} } if api == None: res['error']['code'] = '1' res['error']['msg'] = 'api is not set' return res if (sellerList == None) | (isinstance(sellerList, dict) != True): res['error']['code'] = '2' res['error']['msg'] = 'itemList doesn\'t exist or is of wrong type, must be dict' return res if (dateRange == None) | (isinstance(dateRange, dict) != True): dateRange = setDateRange() # Switch statement to select the proper dateRange sellerList = switchDateRange(sellerList, dateRange) seller = getSeller(api, sellerList) if seller['error']['code'] == '0': sellerList = seller['apiResponse'] seller = None # Unset any data we no-longer need - Saves on memory items = getItems(api, sellerList, {}) if items['error']['code'] == '0': sellerList = items['apiResponse'] items = storeItems(sellerList) storedItems = items['apiResponse'] items = None return storedItems else: res['error'] = items['error'] return res else: res['error'] = seller['error'] return res try: p = Parallel() api = Trading(domain=domain, appid=app_id, devid=dev_id, certid=crt_id, token=usr_token, config_file=None, debug=False, parellel=p) # Example usage, returns a dict containing all items of interst (based on the functions above) # To import a whole lot of data from ebay, we need to pull in dateRange increments of ~20 days # Easiest way would be to have a starting date and then look forward 20 days with the delta itemData = glue(api=api, sellerList={}, dateRange={'from':'2016-03-01T00:00:00.000Z', 'to': '2016-03-28T23:59:59.999Z', 'type':'start'}) itemlist = [] # Write a CSV file containing some of the data we're interested in with open(csv_path, 'wb') as csvfile: fieldnames = ['Part Number', 'Manufacturer', 'Condition', 'Price', 'Quantity', 'Description'] writer = csv.DictWriter(csvfile, fieldnames=fieldnames, delimiter=csv_delimiter, quotechar=csv_quote, quoting=csv.QUOTE_MINIMAL) writer.writerow({'Part Number':'Part Number', 'Manufacturer':'Manufacturer', 'Condition':'Condition', 'Price':'Price', 'Quantity':'Quantity', 'Description':'Description'}) for i in itemData: def keyCheck(key): try: data = itemData[i][key] return data except KeyError: data = 'N/a' return data try: price = itemData[i]['price']['val'] except KeyError: price = 'N/a' try: quantity = str(int(itemData[i]['quantity']['total'])-int(itemData[i]['quantity']['sold'])) except KeyError: quantity = 'N/a' data = { 'Part Number': keyCheck('mpn'), 'Manufacturer': keyCheck('mfg'), 'Condition': itemData[i]['condition']['msg'], 'Price': price, 'Quantity': quantity, 'Description': keyCheck('title').encode('utf-8') } writer.writerow(data) # Store the itemIDs so that we can use them to check which ones were modified itemlist.append(i) except ConnectionError as e: print(e)