Skip to content

Instantly share code, notes, and snippets.

@justengel
Created October 5, 2023 00:14
Show Gist options
  • Save justengel/290e54645bc77431785b79f85b1a3193 to your computer and use it in GitHub Desktop.
Save justengel/290e54645bc77431785b79f85b1a3193 to your computer and use it in GitHub Desktop.
Check prices and report on the cheapest price found
"""
Check and store the prices of an item.
Create a find_prices.json file and run daily to see if prices changed.
..code-block:: json
{
"Coffee Maker": {
"Vendor": {
"url": "https://vendor.url",
},
"Amazon": {
"url": "https://www.amazon.com/...",
}
}
On windows use taskschd.msc as a cron job.
"""
import sys
try:
import local
except (ImportError, Exception):
print("Could not import local env!", file=sys.stderr)
import json
import requests
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from script_utils import send_mail
FILENAME = "find_prices.json"
def find_price(url):
r = requests.get(url)
if r.status_code != 200:
chrome_options = Options()
chrome_options.add_argument("--headless")
chrome_options.add_experimental_option("excludeSwitches", ["enable-logging"])
browser = webdriver.Chrome(options=chrome_options)
browser.get(url)
page = browser.page_source
browser.close()
else:
page = r.text
# Stock
in_stock = "out of stock" not in page.lower()
if "amazon" in url:
soup = BeautifulSoup(page, "html.parser")
try:
a_price = soup.find("span", class_="a-offscreen").text
except (AttributeError, Exception):
a_price = (
soup.find("span", class_="a-price-whole").text
+ soup.find("span", class_="a-price-fraction").text
)
price = float(a_price.replace("$", "").replace(",", ""))
else:
page_lower = page.lower()
price = None
try:
button_idx = page_lower.find("add to cart")
price_idx = page_lower[:button_idx].rfind("$")
s_price = page[price_idx:button_idx].split(" ", 1)[0].split("<", 1)[0]
price = float(s_price.replace("$", "").replace(",", ""))
except (TypeError, ValueError):
for i in range(100):
try:
price_idx = page_lower.rfind("$")
page_lower = page_lower[price_idx + 1 :]
s_price = page_lower.split(" ", 1)[0].split("<", 1)[0]
price = float(s_price.replace("$", "").replace(",", ""))
break
except (TypeError, ValueError):
pass
return price, in_stock
def main():
with open(FILENAME, "r") as f:
prices = json.loads(f.read())
cheaper = {}
for product, stores in prices.items():
print(f"{product}: ")
for store, store_prices in stores.items():
price, stock = find_price(store_prices["url"])
if price is None:
continue
try:
old_price = store_prices["cheapest_price"]
except KeyError:
# Initialize cheapest price
old_price = store_prices["cheapest_price"] = price
cheap = old_price > price
print(
f"\t{store}\tprice: ${price:.02f}\tstock: {stock}\tcheaper: {cheap}\tdiff: ${old_price - price:.02f}",
flush=True,
)
store_prices[f"last_price"] = price
if cheap:
if not cheaper.get(product) or cheaper[product][1] > price:
cheaper[product] = (store, price, store_prices["url"])
store_prices[f"cheapest_price"] = price
with open(FILENAME, "w") as f:
json.dump(prices, f, indent=4)
if cheaper:
body = []
for product, (store, price, url) in cheaper.items():
body.append(f"{product} found a cheaper price at {store} for ${price:.02f}\n\t{url}\n")
send_mail(
"Cheaper price found!",
"\n".join(body),
"[email protected]",
["[email protected]"],
)
print("\nCheaper price found!\n\n" + '\n'.join(body))
else:
print("\tNo cheaper prices found!", file=sys.stderr)
if __name__ == "__main__":
with local.env():
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment