mirror of
https://github.com/c0de-archive/ebay-testing.git
synced 2024-09-08 01:47:37 +00:00
This function wasn't working with different arguments such as days and start It would always return x days off of today instead of the date to start at It now always returns x days off of the date supplied to start or today if start is invalid This also works for stop (they use the same checkDate function)
461 lines
19 KiB
Python
461 lines
19 KiB
Python
# 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) |