Last active
September 18, 2015 22:45
-
-
Save wking/cf87063bd5f76be73db0 to your computer and use it in GitHub Desktop.
Upload product ↔ category associations
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# | |
# https://gist.github.com/wking/cf87063bd5f76be73db0 | |
import csv | |
import json | |
import logging | |
import sys | |
import time | |
import urllib.request | |
logging.basicConfig(level=logging.INFO) | |
def read_categories(stream=sys.stdin): | |
categories = {'children': {}} | |
logging.info('loading categories') | |
for row in csv.DictReader(stream): | |
parent = categories | |
for field in ['Department', 'Aisle', 'Section', 'Shelf', 'Space']: | |
category = { | |
'name': row[field], | |
'short-name': row['{} URL'.format(field)], | |
'children': {}, | |
} | |
if (category['name'] in ['', '*'] or | |
category['short-name'] in ['', '*']): | |
break | |
if category['name'] not in parent['children']: | |
parent['children'][category['name']] = category | |
parent = parent['children'][category['name']] | |
return categories | |
def upload_categories(base_url, headers, categories, parent_id=None): | |
logging.info('uploading categories') | |
if categories['children']: | |
current_categories = download_categories( | |
base_url=base_url, headers=headers, parent_id=parent_id) | |
for name, category in categories['children'].items(): | |
category['parent'] = parent_id | |
if category['name'] not in current_categories: | |
category_id = upload_category( | |
base_url=base_url, headers=headers, category=category) | |
else: | |
category_id = current_categories[category['name']] | |
upload_categories( | |
base_url=base_url, headers=headers, categories=category, | |
parent_id=category_id) | |
def download_categories(base_url, headers, parent_id=None): | |
logging.info('download categories with parent {}'.format(parent_id)) | |
if parent_id is None: | |
pid = 'null' | |
else: | |
pid = parent_id | |
request = urllib.request.Request( | |
url='{base}/categories?parent={pid}&limit=250'.format( | |
base=base_url, pid=pid), | |
headers=headers, | |
method='GET', | |
) | |
with urllib.request.urlopen(request) as response: | |
categories_bytes = response.read() | |
charset = response.headers.get_content_charset() | |
categories_json = categories_bytes.decode(charset) | |
categories = json.loads(categories_json) | |
name_ids = {cat['name']: cat['id'] for cat in categories} | |
logging.info('downloaded categories with parent {}: {}'.format( | |
parent_id, name_ids)) | |
time.sleep(1) | |
return name_ids | |
def upload_category(base_url, headers, category): | |
data = category.copy() | |
data.pop('children') | |
logging.info('upload category {}'.format(data)) | |
request = urllib.request.Request( | |
url='{base}/categories'.format(base=base_url), | |
data=json.dumps(data).encode('UTF-8'), | |
headers=headers, | |
method='POST', | |
) | |
with urllib.request.urlopen(request) as response: | |
new_category_bytes = response.read() | |
charset = response.headers.get_content_charset() | |
new_category_json = new_category_bytes.decode(charset) | |
new_category = json.loads(new_category_json) | |
logging.info('uploaded category {} with id {}'.format( | |
new_category['name'], new_category['id'])) | |
time.sleep(1) | |
return new_category['id'] | |
if __name__ == '__main__': | |
import argparse | |
parser = argparse.ArgumentParser() | |
parser.add_argument('categories') | |
parser.add_argument('--base-url', default='https://api.azurestandard.com') | |
parser.add_argument( | |
'-H', '--header', action='append', default=[], dest='headers') | |
args = parser.parse_args() | |
headers = {} | |
for header in args.headers: | |
key, value = [x.strip() for x in header.split(':', 1)] | |
headers[key] = value | |
with open(args.categories, 'r') as stream: | |
categories = read_categories(stream=stream) | |
upload_categories( | |
base_url=args.base_url, headers=headers, categories=categories) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# | |
# https://gist.github.com/wking/cf87063bd5f76be73db0 | |
import csv | |
import json | |
import logging | |
import sys | |
import time | |
import urllib.error | |
import urllib.parse | |
import urllib.request | |
logging.basicConfig(level=logging.INFO) | |
def get_categories(base_url, cache='/tmp/categories.json'): | |
try: | |
with open(cache, 'r') as stream: | |
categories_json = stream.read() | |
except FileNotFoundError: | |
logging.info('requesting categories') | |
categories = [] | |
count = None | |
start = 0 | |
limit = 250 | |
while True: | |
logging.info('requesting categories ({} from {} of {})'.format( | |
limit, start, count)) | |
with urllib.request.urlopen( | |
'{}/categories?{}'.format( | |
base_url, | |
urllib.parse.urlencode({ | |
'start': start, | |
'limit': limit, | |
})) | |
) as response: | |
categories_bytes = response.read() | |
charset = response.headers.get_content_charset() | |
if count is None: | |
count = int(response.headers['Count']) | |
new_json = categories_bytes.decode(charset) | |
categories.extend(json.loads(new_json)) | |
if len(categories) >= count: | |
break | |
start += limit | |
categories_json = json.dumps(categories) | |
with open(cache, 'w') as stream: | |
stream.write(categories_json) | |
categories = {} | |
cats = json.loads(categories_json) | |
for category in cats: | |
key = (category['name'], category.get('parent')) | |
categories[key] = category['id'] | |
logging.debug('category {} -> {}'.format(key, category['id'])) | |
return categories | |
def get_subheaders(base_url, headers, categories, stream=sys.stdin): | |
subheaders = {} | |
logging.info('loading subheaders') | |
for row in csv.DictReader(stream): | |
subheader = row['SubHeaders'].strip().lower() | |
if not subheader: | |
continue | |
parent_id = None | |
for field in ['Department', 'Aisle', 'Section', 'Shelf', 'Space']: | |
name = row[field] | |
if name in ['', '*']: | |
break | |
short_name = row['{} URL'.format(field)] | |
key = (name, parent_id) | |
try: | |
id = categories[key] | |
except KeyError as error: | |
logging.info('missing category {} ({})'.format(key, row)) | |
try: | |
id = upload_category( | |
base_url=base_url, headers=headers, category={ | |
'name': name, | |
'short-name': name, | |
'parent': parent_id, | |
}) | |
except urllib.error.HTTPError: | |
with urllib.request.urlopen( | |
'{}/categories?{}'.format( | |
base_url, | |
urllib.parse.urlencode({'parent': parent_id})) | |
) as response: | |
categories_bytes = response.read() | |
charset = response.headers.get_content_charset() | |
categories_json = categories_bytes.decode(charset) | |
cats = json.loads(categories_json) | |
category = [cat for cat in cats if cat['name'] == name][0] | |
id = category['id'] | |
categories[key] = id | |
parent_id = id | |
if subheader not in subheaders: | |
subheaders[subheader] = [] | |
subheaders[subheader].append(id) | |
logging.debug('subheaders {} -> {}'.format( | |
subheader, subheaders[subheader])) | |
return subheaders | |
def upload_category(base_url, headers, category): | |
logging.info('upload category {}'.format(category)) | |
request = urllib.request.Request( | |
url='{base}/categories'.format(base=base_url), | |
data=json.dumps(category).encode('UTF-8'), | |
headers=headers, | |
method='POST', | |
) | |
with urllib.request.urlopen(request) as response: | |
new_category_bytes = response.read() | |
charset = response.headers.get_content_charset() | |
new_category_json = new_category_bytes.decode(charset) | |
new_category = json.loads(new_category_json) | |
logging.info('uploaded category {} with id {}'.format( | |
new_category['name'], new_category['id'])) | |
time.sleep(1) | |
return new_category['id'] | |
def associate_products(base_url, headers, subheaders, stream=sys.stdin): | |
logging.info('associating products') | |
missing = set() | |
for row in csv.DictReader(stream): | |
subheader = row['SubHeaders'].strip().lower() | |
if subheader in [ | |
'soon to be discontinued', | |
]: | |
continue | |
code = row['Item code'].strip() | |
try: | |
category_ids = subheaders[subheader] | |
except KeyError as error: | |
if subheader not in missing: | |
logging.error(str(error)) | |
missing.add(subheader) | |
continue | |
for category_id in category_ids: | |
associate_product( | |
base_url=base_url, headers=headers, | |
code=code, category_id=category_id) | |
def associate_product(base_url, headers, code, category_id): | |
logging.info('associate {} with {}'.format(code, category_id)) | |
request = urllib.request.Request( | |
url='{base}/packaged-product/{code}/category/{id}'.format( | |
base=base_url, code=code, id=category_id), | |
headers=headers, | |
method='POST', | |
) | |
with urllib.request.urlopen(request) as response: | |
logging.info('associated {} with {}'.format(code, category_id)) | |
time.sleep(1) | |
if __name__ == '__main__': | |
import argparse | |
parser = argparse.ArgumentParser() | |
parser.add_argument('categories') | |
parser.add_argument('products') | |
parser.add_argument('--base-url', default='https://api.azurestandard.com') | |
parser.add_argument( | |
'-H', '--header', action='append', default=[], dest='headers') | |
args = parser.parse_args() | |
headers = {} | |
for header in args.headers: | |
key, value = [x.strip() for x in header.split(':', 1)] | |
headers[key] = value | |
categories = get_categories(base_url=args.base_url) | |
with open(args.categories, 'r') as stream: | |
subheaders = get_subheaders( | |
base_url=args.base_url, headers=headers, | |
categories=categories, stream=stream) | |
with open(args.products, 'r') as stream: | |
associate_products( | |
base_url=args.base_url, headers=headers, | |
subheaders=subheaders, stream=stream) |
For the live upload, I dropped fix_subheaders
. I converted the Excel files to CSV and ran:
$ ./category-upload.py -H 'Authorization: Basic ....' Category\ UI\ and\ URL\ FINAL.csv
$ ./product-category-upload.py -H 'Authorization: Basic ...' Category\ UI\ and\ URL\ FINAL.csv Catalog\ Data\ Source\ File\ FINAL.csv
The existing-categories download in product-category-upload.py
doesn't work with the new limited queries from azurestandard/beehive@2728652 (Merge branch 'api-limit', 2015-02-20), but I worked around that by seeding /tmp/categories.json
with data dumped from a Django shell. Now that we support start
(azurestandard/api-spec@86d4d29, public.json: Add 'start=...' for offsetting list results, 2015-02-24), you could go that way too.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I spent a day trying to connect the missing subheaders with fix_subheaders, but eventually gave up (about 60% of the way through the product file). These were the remaining subheaders that weren't listed in the category file:
ERROR:root:'bath tissue, toilet paper'
ERROR:root:'bedding-pillows'
ERROR:root:'beeswax, bulk'
ERROR:root:'bleach'
ERROR:root:'bleach, chlorine free'
ERROR:root:'candle, beeswax'
ERROR:root:'candles, soy'
ERROR:root:'canning supplies'
ERROR:root:'cheesecloth'
ERROR:root:'cleaner, all purpose'
ERROR:root:'cleaner, bathroom'
ERROR:root:'cleaner, carpet shampoo'
ERROR:root:'cleaner, degreaser'
ERROR:root:'cleaner, drain & septic system treatment'
ERROR:root:'cleaner, glass'
ERROR:root:'cleaner, soy'
ERROR:root:'cleaner, toilet'
ERROR:root:'cleaners'
ERROR:root:'cleaners, indoor/outdoor'
ERROR:root:'cleaners, the home collection'
ERROR:root:'cleaning, cloths'
ERROR:root:'clothes, socks'
ERROR:root:'clothes, t-shirts'
ERROR:root:'coffee filters'
ERROR:root:'container, tote for multiple uses'
ERROR:root:'cooking twine'
ERROR:root:'cutlery'
ERROR:root:'dish soap, automatic'
ERROR:root:'dishwasher detergent'
ERROR:root:'disinfecting wipes'
ERROR:root:'fabric softener'
ERROR:root:'facial tissue'
ERROR:root:'feminine hygiene products'
ERROR:root:'firelighters'
ERROR:root:'food covers'
ERROR:root:'laundry booster & hard water treatment'
ERROR:root:'laundry care products'
ERROR:root:'laundry detergent'
ERROR:root:'laundry soap'
ERROR:root:'laundry soap, cold water'
ERROR:root:'laundry, soap nuts'
ERROR:root:'laundry, stain & odor remover'
ERROR:root:'lid for plastic pail'
ERROR:root:'lid remover'
ERROR:root:'lid, spout for pouring'
ERROR:root:'lids'
ERROR:root:'light bulbs'
ERROR:root:'light bulbs, 100 watt'
ERROR:root:'light bulbs, 13 watt'
ERROR:root:'light bulbs, 60 watt'
ERROR:root:'light bulbs, bug lights'
ERROR:root:'light bulbs, night lights'
ERROR:root:'light bulbs, twist'
ERROR:root:'mug'
ERROR:root:'mylar bags with oxygen absorbers'
ERROR:root:'napkins'
ERROR:root:'odor eliminator'
ERROR:root:'odor eliminator for refrigerator'
ERROR:root:'oxygen absorbers'
ERROR:root:'paper bowls'
ERROR:root:'paper plates'
ERROR:root:'paper towels'
ERROR:root:'parchment paper'
ERROR:root:'personal hygiene products'
ERROR:root:'plastic wrap'
ERROR:root:'produce wash'
ERROR:root:'pump for containers'
ERROR:root:'soap berries'
ERROR:root:'soap, coconut oil'
ERROR:root:'soap, dishwashing'
ERROR:root:'soaps & cleaners chemical sensitive'
ERROR:root:'sponges'
ERROR:root:'storage, container'
ERROR:root:'storage, glass jar, no lid'
ERROR:root:'storage, lid, for pail'
ERROR:root:'storage, lids metal'
ERROR:root:'storage, pail/bucket'
ERROR:root:'trash bags'
ERROR:root:'trash bags, biodigradable'
ERROR:root:'wax paper'
ERROR:root:'alkalizing support'
ERROR:root:'alkalizing support, tea'
ERROR:root:'aloe vera crystals'
ERROR:root:'bee pollen'
ERROR:root:'beverages nutritional , mineral drink'
ERROR:root:'beverages, super food drink mix'
ERROR:root:'coral calcuim, products'
ERROR:root:'cordyceps power'
ERROR:root:'feminine, female enhancments'
ERROR:root:'fulvic acid'
ERROR:root:'grapefruit seed extract products'
ERROR:root:'hemp drink mixes'
ERROR:root:'human growth hormone'
ERROR:root:'juice beverages, nutritional'
ERROR:root:'juice powders, supplements, nutritional, greens'
ERROR:root:'lecithin'
ERROR:root:'maca products & smoothie blends'
ERROR:root:'maca products, extracts'
ERROR:root:'nutritional supplements'
ERROR:root:'probiotics'
ERROR:root:'probiotics, yogurt starter'
ERROR:root:'protein powder'
ERROR:root:'protein, hemp powder'
ERROR:root:'protien powder'
ERROR:root:'royal jelly'
ERROR:root:'salve, silver colloidal'
ERROR:root:'silver biotics'
ERROR:root:'silver, colloidal'
ERROR:root:'spirit reishi'
ERROR:root:'super food'
ERROR:root:'supplement, sprays'
ERROR:root:'supplements & minerals'
ERROR:root:'supplements, acidophilus'
ERROR:root:'supplements, amino acids'
ERROR:root:'supplements, children'
ERROR:root:'supplements, chlorophyll'
ERROR:root:'supplements, clay products'
ERROR:root:'supplements, dietary'
ERROR:root:'supplements, enzymes'
ERROR:root:'supplements, fiber'
ERROR:root:'supplements, for pain relief & stress management'
ERROR:root:'supplements, liquid'
ERROR:root:'supplements, minerals'
ERROR:root:'supplements, nutritional'
ERROR:root:'supplements, nutritional minerals'
ERROR:root:'supplements, nutritional powders, drink mix'
ERROR:root:'supplements, nutritional powders, weight loss'
ERROR:root:'supplements, nutritional, cod liver oil products'
ERROR:root:'supplements, nutritional, enzymes'
ERROR:root:'supplements, nutritional, greens'
ERROR:root:'supplements, nutritional, omega boost'
ERROR:root:'supplements, nutritional, vitamins'
ERROR:root:'supplements, probiotics'
ERROR:root:'supplements, special formulas'
ERROR:root:'supplements, vitamins'
ERROR:root:'supplements, weightloss support'
ERROR:root:'supplements. sleeping aid'
ERROR:root:'symptom relief, feminine'
ERROR:root:'symptom relief, for cold sore'
ERROR:root:'vitamins'
ERROR:root:'water, alkaline'
ERROR:root:'weight gain'
ERROR:root:'wellness drink'
ERROR:root:'yeast, nutritional'
ERROR:root:'chocolate covered nuts'
ERROR:root:'nuts, almonds'
ERROR:root:'nuts, brazil nuts'
ERROR:root:'nuts, cashews'
ERROR:root:'nuts, hazelnuts'
ERROR:root:'nuts, macadamia nuts'
ERROR:root:'nuts, peanuts'
ERROR:root:'nuts, pecans'
ERROR:root:'nuts, pinenuts'
ERROR:root:'nuts, pistachios'
ERROR:root:'nuts, walnuts'
ERROR:root:'trail mix'
ERROR:root:'chia seed oil, artisan cold-pressed'
ERROR:root:'oil, almond'
ERROR:root:'oil, apricot kernel'
ERROR:root:'oil, avocado'
ERROR:root:'oil, canola'
ERROR:root:'oil, coconut'
ERROR:root:'oil, coconut mana'
ERROR:root:'oil, flax'
ERROR:root:'oil, flax seed'
ERROR:root:'oil, garlic'
ERROR:root:'oil, grapeseed'
ERROR:root:'oil, grapeseed oil'
ERROR:root:'oil, hemp'
ERROR:root:'oil, hemp seed'
ERROR:root:'oil, olive'
ERROR:root:'oil, palm'
ERROR:root:'oil, peanut'
ERROR:root:'oil, red palm'
ERROR:root:'oil, rice'
ERROR:root:'oil, rice bran'
ERROR:root:'oil, safflower'
ERROR:root:'oil, sesame'
ERROR:root:'oil, shortening'
ERROR:root:'oil, sprays'
ERROR:root:'oil, sunflower'
ERROR:root:'oil, walnut'
ERROR:root:'shortening, palmfruit'
ERROR:root:'mac-n-cheese'
ERROR:root:'noodes'
ERROR:root:'pasta, 3 color'
ERROR:root:'pasta, brown rice'
ERROR:root:'pasta, corn'
ERROR:root:'pasta, dinner mixes & seasonings'
ERROR:root:'pasta, egg noodles'
ERROR:root:'pasta, for kids'
ERROR:root:'pasta, gluten free'
ERROR:root:'pasta, jerusalem artichoke'
ERROR:root:'pasta, kamut'
ERROR:root:'pasta, kelp noodles'
ERROR:root:'pasta, quinoa'
ERROR:root:'pasta, rainbow'
ERROR:root:'pasta, salad mixes & seasonings'
ERROR:root:'pasta, semolina'
ERROR:root:'pasta, soba'
ERROR:root:'pasta, spelt'
ERROR:root:'pasta, spelt white'
ERROR:root:'pasta, spelt whole grain'
ERROR:root:'pasta, spinach'
ERROR:root:'pasta, sprouted grain'
ERROR:root:'pasta, udon'
ERROR:root:'pasta, vegetable'
ERROR:root:'pasta, white einkorn'
ERROR:root:'pasta, whole wheat'
ERROR:root:'pasta, whole wheat, einkorn'
ERROR:root:'bouillon cubes'
ERROR:root:'broth'
ERROR:root:'brown gravy'
ERROR:root:'dip mixes'
ERROR:root:'dip mixes'
ERROR:root:'gravy mixes'
ERROR:root:'gravy, mixes'
ERROR:root:'miso paste'
ERROR:root:'nutritional yeast extract'
ERROR:root:'sauce, mixes'
ERROR:root:'seasoning, & spice mixes'
ERROR:root:'seasoning, mixes'
ERROR:root:'seasonings, bean soup'
ERROR:root:'seasonings, popcorn'
ERROR:root:'seasonings, salsa'
ERROR:root:'seasonings, spaghetti'
ERROR:root:'soup, mixes'
ERROR:root:'candy, drops'
ERROR:root:'candy, ginger'
ERROR:root:'candy, lollipops'
ERROR:root:'candy, maple treats'
ERROR:root:'candy, pops'
ERROR:root:'candy, sweet tarts'
ERROR:root:'carob coated almonds'
ERROR:root:'carob coated nuts'
ERROR:root:'carob coated treats'
ERROR:root:'chocolate bar'
ERROR:root:'chocolate bar, mini'
ERROR:root:'chocolate bars'
ERROR:root:'chocolate covered cacao nibs'
ERROR:root:'chocolate covered fruit'
ERROR:root:'chocolate covered goji berries'
ERROR:root:'chocolate rainbow drops'
ERROR:root:'chocolate treats'
ERROR:root:'chocolate, raw'
ERROR:root:'cookies'
ERROR:root:'cookies, cream filled'
ERROR:root:'cookies, crunchy'
ERROR:root:'cookies, einkorn'
ERROR:root:'cookies, fig filled'
ERROR:root:'cookies, ginger shortbread'
ERROR:root:'cookies, maple waffle'
ERROR:root:'cookies, soft'
ERROR:root:'dessert topping'
ERROR:root:'dessert toppings'
ERROR:root:'energy bars'
ERROR:root:'fig bars'
ERROR:root:'fruit bars'
ERROR:root:'fruit leather'
ERROR:root:'fruit, nut & seed bars/ energy bars'
ERROR:root:'licorice'
ERROR:root:'mints'
ERROR:root:'snacks, sea vegtable crunch'
ERROR:root:'sweets'
ERROR:root:'yogurt covered fruit & nuts'
ERROR:root:'chia seeds, milled'
ERROR:root:'seeds, alfalfa'
ERROR:root:'seeds, broccoli'
ERROR:root:'seeds, caraway'
ERROR:root:'seeds, chia'
ERROR:root:'seeds, clover'
ERROR:root:'seeds, flax'
ERROR:root:'seeds, flaxseed cold milled'
ERROR:root:'seeds, hemp'
ERROR:root:'seeds, poppy'
ERROR:root:'seeds, pumpkin'
ERROR:root:'seeds, radish'
ERROR:root:'seeds, sesame'
ERROR:root:'seeds, sesame black'
ERROR:root:'seeds, sprouted snack packs'
ERROR:root:'seeds, sprouting mix'
ERROR:root:'seeds, sunflower'
ERROR:root:'sprouting seeds'
ERROR:root:'beef sticks'
ERROR:root:'buffalo jerky snacks'
ERROR:root:'builder bars'
ERROR:root:'bulk, trail mix'
ERROR:root:'cereal bars'
ERROR:root:'cheese puffs'
ERROR:root:'chips, bean'
ERROR:root:'chips, cassava'
ERROR:root:'chips, corn'
ERROR:root:'chips, multigrain'
ERROR:root:'chips, plentils'
ERROR:root:'chips, potato'
ERROR:root:'chips, rice'
ERROR:root:'chips, sea vegetable'
ERROR:root:'chips, tortilla'
ERROR:root:'chips, tortillia'
ERROR:root:'clif kid z bar, cookie'
ERROR:root:'coconut bars'
ERROR:root:'crackers'
ERROR:root:'crackers, animal'
ERROR:root:'crackers, classic'
ERROR:root:'crackers, flax snacks'
ERROR:root:'crackers, japanese rice'
ERROR:root:'crackers, lunch packs'
ERROR:root:'crackers, nut thins'
ERROR:root:'crackers, rice'
ERROR:root:'crackers, rice snaps'
ERROR:root:'crackers, rice toast'
ERROR:root:'crackers, sprouted'
ERROR:root:'fiber bars'
ERROR:root:'flatbread'
ERROR:root:'fruit snacks'
ERROR:root:'graham, crackers'
ERROR:root:'granola bars'
ERROR:root:'granola bars, chewy'
ERROR:root:'granola bars, crunchy'
ERROR:root:'granola, superfoods, raw'
ERROR:root:'granola, trail bars'
ERROR:root:'jerky, beef'
ERROR:root:'junobar'
ERROR:root:"kit's fruit & nut bars"
ERROR:root:'nuts & seeds, trail packs'
ERROR:root:'popcorn, microwavable'
ERROR:root:'popcorn, vegan'
ERROR:root:'popcorn, white cheddar'
ERROR:root:'pretzel snacks'
ERROR:root:'protein bars'
ERROR:root:'protein bars, gluten free'
ERROR:root:'pumpkin seed snack packs'
ERROR:root:'rice cakes'
ERROR:root:'savory bar'
ERROR:root:'seaweed crumbles, flavored'
ERROR:root:'seaweed snack packs'
ERROR:root:'sesame sticks'
ERROR:root:'snack mix'
ERROR:root:'super food kale chips'
ERROR:root:'superfoods, raw'
ERROR:root:'toaster pastries'
ERROR:root:'toaster pastries, frosted'
ERROR:root:'trail mix bars, mojo'
ERROR:root:'trail mix, bulk'
ERROR:root:'z bars nutritional, kids'
ERROR:root:'z fruit, fruit & veggie snacks for kids'
ERROR:root:'z fruit, fruit snacks for kids'
ERROR:root:''
ERROR:root:'butter, substitutes'
ERROR:root:'cheese alternatives'
ERROR:root:'cheese alternatives, shredded'
ERROR:root:'cream cheese alternative'
ERROR:root:'dips & spreads'
ERROR:root:'meat alternative, deli slices'
ERROR:root:'meat alternative, ground beef'
ERROR:root:'meat alternative, hot dogs'
ERROR:root:'meat alternative, sausage'
ERROR:root:'meat substitute'
ERROR:root:'meat substitutes, soy curls'
ERROR:root:'meat substitutes, vegetable protein'
ERROR:root:'milk, soy'
ERROR:root:'milk, substitute'
ERROR:root:'milk, substitute powders'
ERROR:root:'milk, substitutes'
ERROR:root:'sour cream, alternative'
ERROR:root:'tempeh'
ERROR:root:'tofu'
ERROR:root:'sweeteners, agave syrup'
ERROR:root:'sweeteners, agave, syrup'
ERROR:root:'sweeteners, barley malt'
ERROR:root:'sweeteners, brown sugar'
ERROR:root:'sweeteners, cane juice crystals'
ERROR:root:'sweeteners, cane sugar crystals'
ERROR:root:'sweeteners, coconut crystals'
ERROR:root:'sweeteners, coconut nectar'
ERROR:root:'sweeteners, coconut palm sugar'
ERROR:root:'sweeteners, coconut sugar'
ERROR:root:'sweeteners, corn syrup'
ERROR:root:'sweeteners, date sugar'
ERROR:root:'sweeteners, erythritol'
ERROR:root:'sweeteners, fructose'
ERROR:root:'sweeteners, fruit sweeteners'
ERROR:root:'sweeteners, honey'
ERROR:root:'sweeteners, honey crystals'
ERROR:root:'sweeteners, honey infused'
ERROR:root:'sweeteners, manuka honey'
ERROR:root:'sweeteners, maple butter'
ERROR:root:'sweeteners, maple sugar'
ERROR:root:'sweeteners, maple syrup'
ERROR:root:'sweeteners, molasses'
ERROR:root:'sweeteners, powdered sugar'
ERROR:root:'sweeteners, rice syrup'
ERROR:root:'sweeteners, sorghum'
ERROR:root:'sweeteners, stevia'
ERROR:root:'sweeteners, stevia, squeeze bottle'
ERROR:root:'sweeteners, sunroot'
ERROR:root:'sweeteners, turbinado'
ERROR:root:'sweeteners, xylitol'
ERROR:root:'sweeteners, yacon'
ERROR:root:'slim tea'