Tutorial: A Tiny, Zero Dependency Price Tracker

Build a tiny personal price tracker with Diffbot Extract and Python (⏲️ 10 Minutes)

Overkill? Surely. But this Premium Polished Tee from Abercrombie is really nice. It's mid-weight, oversized, cropped, and the XS fits my medium set 5'7" frame like a glove.

I just don't want to pay $40 for it.

So let's build a tiny plug and play price tracker to tell me when it drops.

How it'll work

The simplest model of this system would look something like this:

  1. I want a deal on X
  2. The deal tracker checks the price of X on a regular schedule
  3. I am notified when the price drops

Requirements

Python 3.8+. If you're following this guide exactly, you'll need Mac OS X Mavericks+ as well.

Step 1: Check the Price

Scraping the price off a product page is relatively straight forward with a little CSS-fu. In practice, you'd have to define a new set of CSS selectors for every product you want to track. This is annoying for everyone, including front-end engineers.

Instead, I'll use Diffbot Extract , which can extract the price off any product page without any rules. The free plan is plenty to work with for our tiny price tracker and the implementation cannot be easier.

import urllib.parse
import urllib.request

TOKEN = "YOUR-DIFFBOT-TOKEN"

product = "https://www.abercrombie.com/shop/us/p/camp-collar-cropped-summer-linen-blend-shirt-56703828?categoryId=12204&faceout=model&seq=01"

url = f"https://api.diffbot.com/v3/product?token={TOKEN}&url={urllib.parse.quote(product)}"

with urllib.request.urlopen(url) as response:
	data = json.load(response)

We could've used the requests library here, but in the spirit of keeping things tiny, we'll use the standard urllib.request library to make a GET call.

Diffbot Extract follows a consistent ontology for product pages. Price is extracted in offerPriceDetails, so let's grab that along with a few other details.

import urllib.parse
import urllib.request
import json

TOKEN = "YOUR-DIFFBOT-TOKEN"

product = "https://www.abercrombie.com/shop/us/p/camp-collar-cropped-summer-linen-blend-shirt-56703828?categoryId=12204&faceout=model&seq=01"
target_price = 25

url = f"https://api.diffbot.com/v3/product?token={TOKEN}&url={urllib.parse.quote(product)}"

with urllib.request.urlopen(url) as response:
	data = json.load(response)
	extracted_product = data['objects'][0]
	title = extracted_product.get('title')
	offer_price = extracted_product.get('offerPriceDetails', {}).get('amount')
	offer_symbol = extracted_product.get('offerPriceDetails', {}).get('symbol')
	if offer_price < target_price:
		print(f"SALE: {offer_symbol}{offer_price} — {title}")
	else:
		print(f"No Deal: {offer_symbol}{offer_price} — {title}")

Finally, we'll use argparse, another built-in library, to create a little CLI.

import urllib.parse
import urllib.request
import json
import argparse

# Parse CLI Arguments
parser = argparse.ArgumentParser("deal")
parser.add_argument("--url", help="Product page URL", required=True)
parser.add_argument("--price", help="Price at or below which you'd like to target", type=int)
args = parser.parse_args()

TOKEN = "YOUR-DIFFBOT-TOKEN"

product = args.url
target_price = args.price or 99999

# Build Diffbot Extract Call
url = f"https://api.diffbot.com/v3/product?token={TOKEN}&url={urllib.parse.quote(product)}"

# Extract Product Pricing
with urllib.request.urlopen(url) as response:
	data = json.load(response)
	extracted_product = data['objects'][0]
	title = extracted_product.get('title')
	offer_price = extracted_product.get('offerPriceDetails', {}).get('amount')
	offer_symbol = extracted_product.get('offerPriceDetails', {}).get('symbol')
	if offer_price < target_price:
		print(f"SALE: {offer_symbol}{offer_price} — {title}")
	else:
		print(f"{offer_symbol}{offer_price} — {title}")

So far zero non-standard dependencies, runs on a Python 3.8 environment, and 30 lines.

Step 2: On a schedule

This should've been the simplest thing. But Amazon Lambda is such a huge pain to work with. There's also something really compelling about being able to run the tracker completely locally.

I can't reliably run a cron job on my mac, so I experimented a little with launchd, mac's answer to crontab, that also supports delaying scheduled script executions until the system is awake. But documentation is sparse and I could never get it to work.

In the throngs of my research, I discovered that Automator (Apple's OG Shortcuts) has a feature that runs bash scripts as a calendar alarm. Calendar alarms work even when the Macbook is asleep (delayed until wake). We have something here!

As a bonus, it's super easy to adjust the schedule of our price tracker on the Calendar app. On save, the Calendar app opens, allowing you to modify the start date/time and repeat settings. I have mine set to repeat every day at 12pm.

Of course, this is all Mac specific. Feel free to bring your own scheduler.

Step 3: I am notified when the price drops

We have a few options here. I assumed I'd be working with email early on when I was still fumbling about with Lambda. Now that the tracker runs locally, why not try a system notification?

Did you know you can display a Mac native system notification with one line?

$ osascript -e 'display notification "World" with title "Hello"'

With the built-in os library, we can run this little osascript in our Python tracker.

import urllib.parse
import urllib.request
import json
import argparse
import os

# Parse CLI Arguments
parser = argparse.ArgumentParser("deal")
parser.add_argument("--url", help="Product page URL", required=True)
parser.add_argument("--price", help="Price at or below which you'd like to target", type=int)
args = parser.parse_args()

TOKEN = "YOUR-DIFFBOT-TOKEN"

product = args.url
target_price = args.price or 99999

# Notifier
def notify(title, text):
	os.system("""
            osascript -e 'display notification "{}" with title "{}"'
            """.format(text, title))

# Build Diffbot Extract Call
url = f"https://api.diffbot.com/v3/product?token={TOKEN}&url={urllib.parse.quote(product)}"

# Extract Product Pricing
with urllib.request.urlopen(url) as response:
	data = json.load(response)
	extracted_product = data['objects'][0]
	title = extracted_product.get('title')
	offer_price = extracted_product.get('offerPriceDetails', {}).get('amount')
	offer_symbol = extracted_product.get('offerPriceDetails', {}).get('symbol')
	if offer_price < target_price:
		notify(f"🔥 {offer_symbol}{offer_price} — {title}", f"BUY NOW: {offer_symbol}{target_price}")
	else:
		notify(f"☹️ {offer_symbol}{offer_price} — {title}", f"HODL: {offer_symbol}{target_price}")

To run the tracker manually —

$ python3 dealer.py --url 'https://www.abercrombie.com/shop/us/p/camp-collar-cropped-summer-linen-blend-shirt-56703828?categoryId=12204&faceout=model&seq=01' --price 50

Et voila.

I'll let you know when the shirt goes on sale.


June 7, 2024 Update: Not yet at $25, but the tracker did grab a lower price today.