Skip to content

Instantly share code, notes, and snippets.

@eatoncw
Created March 7, 2018 20:15
Show Gist options
  • Save eatoncw/ac5703825e55f2acde048e1006c02902 to your computer and use it in GitHub Desktop.
Save eatoncw/ac5703825e55f2acde048e1006c02902 to your computer and use it in GitHub Desktop.
Sample api request - find certain Best Buy products that are on sale and save to database
#utilize a background job - using sidekiq with redis queue
class ApisSalesFinders::BestbuySalesFinderWorker < ApisSalesFinders::SalesFinders
include Sidekiq::Worker
require './app/models/scrape_constants/collections' #ebay product categories
def perform
current_items = Product.where(retailer: "best buy")
discount = 10
minimum_price = 75 #used to filter out some accessory products
current_products_urls_arr = [] #used to purge database of old records
bestbuy = Apis::BestbuyApi.new
begin
ProductCollections.bestbuy_search_queries.each_with_index do |collection, i|
puts "fetching #{collection[:name]} collection from bestbuy"
# first iteration to get total pages
page_number = 1
puts "fetching page #{page_number}"
response = bestbuy.get_all_listings(URI.encode(collection[:query_string]),page_number) #api request
products = filter_bestbuy_products(bestbuy,response,discount,minimum_price)
products.each {|product| current_products_urls_arr << product.url }
save_or_update_items(products, current_items) # add to database
# loop through additional pages
total_pages = response['totalPages'].to_i ||= 1
puts "#{total_pages} pages in the #{collection[:name]} collection"
additional_pages = total_pages - 1
additional_pages.times do |i|
page_number = i + 2
puts "fetching page #{page_number}"
response = bestbuy.get_all_listings(URI.encode(collection[:query_string]),page_number)
products = filter_bestbuy_products(bestbuy,response,discount,minimum_price)
products.each {|product| current_products_urls_arr << product.url }
save_or_update_items(products, current_items) # add to database
end
end
# purge old records
old_products = current_items.where.not(url: current_products_urls_arr)
puts "destroying #{old_products.size} old products"
old_products.delete_all
puts "#{Product.where(retailer:"best buy").count} Bestbuy products in our database"
rescue StandardError => e
Honeybadger.notify(e)
end
end
def filter_bestbuy_products(bestbuy,response,discount,minimum_price)
products = discount_filter(bestbuy.to_products(response), discount) #json to ruby objects and filter
products.reject!{|product| product.price < minimum_price}
products
end
end
class Apis::BestbuyApi < Apis::ApiConnector
base_uri "https://api.bestbuy.com/v1"
default_params apiKey: ENV["BESTBUY_APIKEY"], LID: ENV["BESTBUY_AFFILIATE_TRACKING_ID"], format: "json", pageSize: 100, show: 'name,salePrice,onSale,regularPrice,image,linkShareAffiliateUrl,shortDescription,depth,height,width'
# max is 100 products per page
def initialize
end
def get_all_listings(query_string,page_number)
self.class.get("/products(#{query_string})", param: {page: page_number})
end
def to_products(response) #only commit to ruby objects if response contains sales criteria
arr = response['products']
arr = arr ||= []
arr.reject! {|item| item['onSale'] == false }
Dish(arr, Apis::BestbuyProduct)
end
end
class Apis::BestbuyProduct < Apis::ApiProduct
def initialize(items_array)
@retailer = "best buy"
@name = items_array.andand['name']
@description = items_array.andand['shortDescription']
@image_url = items_array.andand['image']
@url = items_array.andand['linkShareAffiliateUrl']
@unit_dimensions = "inches"
@item_length = return_measurement(items_array.andand['width'])
@item_width = return_measurement(items_array.andand['depth'])
@item_height = return_measurement(items_array.andand['height'])
current_price = items_array['salePrice']
original_price = items_array['regularPrice']
check_sale(original_price, current_price) #returns prices, onsale boolean, and discount
@category = "appliances" #currently we are only looking for appliances
end
def return_measurement(measurement)
(measurement == nil) ? 0 : measurement.split(" ")[0]
end
end
#to run
rails runner ApisSalesFinders::BestbuySalesFinderWorker.perform_async
def ProductCollections.bestbuy_search_queries #these filter out unwanted products using bestbuy defined classes and departments etc
[
{name: "dishwashers", query_string: "search=dishwasher&department=appliance&subclass=dishwash*"},
{name: "washing machines", query_string: "search=washing machine&class=laundry&department=appliance"},
{name: "dryers", query_string: "search=dryer&class=laundry&department=appliance"},
{name: "ovens", query_string: "search=oven&department=appliance&subclass=walloven*|search=oven&department=appliance&subclass=range*" },
{name: "ranges", query_string: "search=range&class=cooking&department=appliance|search=range&class=built-ins&department=appliance"},
{name: "refrigerators and freezer", query_string: "class=refri*&department=appliance"}
]
end
class ApisSalesFinders::SalesFinders
def discount_filter(array, discount)
array.select{ |item| ((item.price - item.onsale_price) / item.price) >= (discount.to_f / 100) }
end
def save_or_update_items(products, current_items)
products.each do |product|
existing_product = current_items.find_by(url: product.url) #check if product exists
if existing_product.nil?
new_sales_item = Product.new(product.instance_values) #instance_values method convert product object into hash for database save
new_sales_item.save
puts "saved new item #{new_sales_item.name} with a category of #{new_sales_item.category}"
elsif (existing_product.onsale_price != product.onsale_price) #check if prices are the same
existing_product.update(product.instance_values)
puts "the price has changed"
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment