<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/rss-style.xsl" type="text/xsl"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	     xmlns:dc="http://purl.org/dc/elements/1.1/"
	   xmlns:atom="http://www.w3.org/2005/Atom"
	     xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	  xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>
<channel>
	<title>wishlist &#8211; Terence Eden’s Blog</title>
	<atom:link href="https://shkspr.mobi/blog/tag/wishlist/feed/" rel="self" type="application/rss+xml" />
	<link>https://shkspr.mobi/blog</link>
	<description>Regular nonsense about tech and its effects 🙃</description>
	<lastBuildDate>Wed, 22 Jan 2025 07:58:03 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://shkspr.mobi/blog/wp-content/uploads/2023/07/cropped-avatar-32x32.jpeg</url>
	<title>wishlist &#8211; Terence Eden’s Blog</title>
	<link>https://shkspr.mobi/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title><![CDATA[Use Python to get alerted when an Amazon wishlist item drops in price]]></title>
		<link>https://shkspr.mobi/blog/2022/01/use-python-to-get-alerted-when-an-amazon-wishlist-item-drops-in-price/</link>
					<comments>https://shkspr.mobi/blog/2022/01/use-python-to-get-alerted-when-an-amazon-wishlist-item-drops-in-price/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sat, 22 Jan 2022 12:34:54 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[Amazon]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[wishlist]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=41673</guid>

					<description><![CDATA[Scratching my own itch. I want an alert when there&#039;s been a price drop on an item on my Amazon wishlist.  I couldn&#039;t find an easy way to get an email directly from Amazon (customer-focused my shiny metal arse) so I knocked something up in Python. This is heavily inspired by Leigh Dodds&#039; Wishlist Monitor.  Amazon don&#039;t offer an API for wishlists (innovative my shiny metal arse). So this uses…]]></description>
										<content:encoded><![CDATA[<p>Scratching my own itch. I want an alert when there's been a price drop on an item on my Amazon wishlist.  I couldn't find an easy way to get an email directly from Amazon (customer-focused my shiny metal arse) so I knocked something up in Python. This is heavily inspired by <a href="https://github.com/ldodds/wishlist-monitor">Leigh Dodds' Wishlist Monitor</a>.</p>

<p>Amazon don't offer an API for wishlists (innovative my shiny metal arse). So this uses Beautiful Soup to grab the data from the HTML. To be fair, there's also <em>some</em> microdata on the page, which makes things slightly easier.</p>

<p>The code is available on <a href="https://github.com/edent/Amazon-Wishlist-Pricedrop-Alert">github.com/edent/Amazon-Wishlist-Pricedrop-Alert</a> - but here's a brief walkthrough of parsing and dealing with pagination:</p>

<p>First up, the standard boilerplate for getting and parsing HTML</p>

<pre><code class="language-python">from bs4 import BeautifulSoup
import requests
import re
import json
</code></pre>

<p>I start by passing this function my wishlist URL <a href="https://www.amazon.co.uk/gp/registry/wishlist/1A1NYHTAZ3N6V/"></a><a href="https://www.amazon.co.uk/gp/registry/wishlist/1A1NYHTAZ3N6V/">https://www.amazon.co.uk/gp/registry/wishlist/1A1NYHTAZ3N6V/</a>. 
Amazon only returns 10 items per page of HTML, but it includes a "pagination" URL at the end of each page. Later on, we'll recursively call this function based on the "next page" URL.</p>

<pre><code class="language-python">def get_wishlist(url):
    #   Load the wishlist
    response = requests.get(url)
    page_html = response.text
    #   Parse the page
    soup = BeautifulSoup(page_html, 'html.parser')
    return soup
</code></pre>

<p>The item name - for example "Fly Fishing by JR Hartley" - needs to be scraped out of the HTML. It looks something like this:</p>

<pre><code class="language-html">&lt;a id="itemName_IS7IZUKCMTBZG" 
   class="a-link-normal" 
   title="Fly Fishing by JR Hartley"
   href="..."&gt;
   Fly Fishing by JR Hartley
&lt;/a&gt;
</code></pre>

<p>Use BeautifulSoup to grab the string inside the anchor. Use a regex to match anything starting with <code>itemName_</code>.</p>

<pre><code class="language-python">def get_items(soup):
    #   Get the item names
    for match in soup.find_all('a', id=re.compile("itemName_")):
        item = match.string.strip()
        item_list.append(item)
</code></pre>

<p>Prices and product IDs are slightly easier to scrape. They have microdata and JSON. For example:</p>

<pre><code class="language-html">&lt;li data-id="1A1NYHTAZ3N6V"
    data-itemId="I3VQB497DB6HJF"
    data-price="8.07"
    data-reposition-action-params="{"hasComparisonTable":false,"itemExternalId":"ASIN:B0962VD2G8|A1F83G8C2ARO7P","listType":"WishList","sid":"260-3142506-2992056"}"
    class="a-spacing-none g-item-sortable"&gt;...
</code></pre>

<p>The JSON in <code>data-reposition-action-params</code> parses out to:</p>

<pre><code class="language-json">{
  "hasComparisonTable": false,
  "itemExternalId": "ASIN:B0962VD2G8|A1F83G8C2ARO7P",
  "listType": "WishList",
  "sid": "260-3142506-2992056"
}
</code></pre>

<p>Beautiful soup can grab the <code>data-price</code> and then the Amazon ID:</p>

<pre><code class="language-python">def get_prices_and_ids(soup):
    #   Get the price and ID from data attributes
    for match in soup.find_all("li", class_="g-item-sortable"):
        price = match.attrs["data-price"]
        price_list.append(price)
        json_data = json.loads(match.attrs["data-reposition-action-params"])
        # Will be something like "ASIN:B095PV5G87|A1F83G8C2ARO7P"
        id = json_data["itemExternalId"].split(":")[1].split("|")[0]
        id_list.append(id)
</code></pre>

<p>That will get us all the names, IDs, and prices on the first page. What about subsequent pages?</p>

<p>At the bottom of the page is this HTML:</p>

<pre><code class="language-html">&lt;input
   type="hidden"
   name="showMoreUrl"
   value="/hz/wishlist/slv/items?filter=unpurchased&amp;paginationToken=eyJGcm9tVV...MDl9&amp;itemsLayout=LIST&amp;sort=default&amp;type=wishlist&amp;lid=1A1NYHTAZ3N6V"
   class="showMoreUrl"/&gt;
</code></pre>

<p>Retrieving the URL gives another HTML page with the next set of wishlist items on it.  The last page of results has <code>&lt;div id="endOfListMarker"&gt;</code> which we can use to detect when to stop getting pages.</p>

<pre><code class="language-python">def get_paginator(soup):
    paginator = None
    ##  Find the paginator
    if soup.find("div", {"id": "endOfListMarker"}) is None:
        #   If the end tag doesn't exist, continue
        for match in soup.find_all('input', class_="showMoreUrl"):
            paginator = "https://www.amazon.co.uk" + match.attrs["value"]
    else:
        paginator = None
    return paginator
</code></pre>

<p>Putting it all together looks like this:</p>

<pre><code class="language-python">#   Set up the lists
item_list  = []
price_list = []
id_list    = []

#   Message text
message = "Here are the recent price drops:\n"

def get_all(url):
    global counter
    counter = counter + 1
    print( "Getting page " + str(counter))
    soup = get_wishlist(url)
    get_items(soup)
    get_prices_and_ids(soup)
    paginator = get_paginator(soup)
    if paginator is not None:
        get_all(paginator)

#   Get all the items on the wishlist
#   Which page are we on?
counter = 0
get_all("https://www.amazon.co.uk/gp/registry/wishlist/1A1NYHTAZ3N6V/")
</code></pre>

<p>That stores all the data in some lists. Next up, put them in a Pandas DataFrame for ease of access:</p>

<pre><code class="language-python">#   Place into a DataFrame
all_items = zip(id_list, item_list, price_list)
new_prices = pd.DataFrame(list(all_items), columns = ["ID", "Name", "Price"])
</code></pre>

<p>For comparing prices, I'm just being lazy. All I care about is if the price has dropped since the last time I looked. I don't care what the highest or the lowest price is.</p>

<p>I save the prices as a CSV.  The next time the code runs it reads that CSV into a DataFrame called <code>old_prices</code></p>

<pre><code class="language-python">#   Read the old file
if exists("old_prices.csv"):
    old_prices = pd.read_csv("old_prices.csv")
else:
    old_prices = new_prices.copy()
</code></pre>

<p>Now it's a case of iterating through the <em>new</em> prices and comparing each item with its <em>old</em> price.</p>

<p>If the price has dropped, send me a message. If the price of a book is under a quid, I also want to know that as well.</p>

<pre><code class="language-python">#   Compare prices
for id in new_prices["ID"]:
    new_price = new_prices.loc[new_prices["ID"]==id, "Price"].values[0]
    name      = new_prices.loc[new_prices["ID"]==id, "Name" ].values[0]
    #   If a book has recently been added to the wishlist, it won't have an old price
    if id in old_prices.values:
        old_price = old_prices.loc[old_prices["ID"]==id, "Price"].values[0]
        #   Anything less than a quid is good knowing about.
        #   Some prices are ""-Infinity", so check the price is more than zero
        if float(new_price) &lt; 1 and float(new_price) &gt; 0:
            message += (name + "\n£" + str(new_price) + " was £" + str(old_price) + " https://www.amazon.co.uk/dp/"+id + "\n")
        elif float(new_price) &lt; float(old_price) and float(new_price) &gt; 0:
            message += (name + "\n£" + str(new_price) + " was £" + str(old_price) + " https://www.amazon.co.uk/dp/"+id + "\n")

print(message)
</code></pre>

<p>There's a slight wrinkle because some of the prices are <code>-Infinity</code></p>

<blockquote class="social-embed" id="social-embed-1481352496639623169" lang="en" itemscope="" itemtype="https://schema.org/SocialMediaPosting"><header class="social-embed-header" itemprop="author" itemscope="" itemtype="https://schema.org/Person"><a href="https://twitter.com/edent" class="social-embed-user" itemprop="url"><img class="social-embed-avatar social-embed-avatar-circle" src="data:image/webp;base64,UklGRkgBAABXRUJQVlA4IDwBAACQCACdASowADAAPrVQn0ynJCKiJyto4BaJaQAIIsx4Au9dhDqVA1i1RoRTO7nbdyy03nM5FhvV62goUj37tuxqpfpPeTBZvrJ78w0qAAD+/hVyFHvYXIrMCjny0z7wqsB9/QE08xls/AQdXJFX0adG9lISsm6kV96J5FINBFXzHwfzMCr4N6r3z5/Aa/wfEoVGX3H976she3jyS8RqJv7Jw7bOxoTSPlu4gNbfXYZ9TnbdQ0MNnMObyaRQLIu556jIj03zfJrVgqRM8GPwRoWb1M9AfzFe6Mtg13uEIqrTHmiuBpH+bTVB5EEQ3uby0C//XOAPJOFv4QV8RZDPQd517Khyba8Jlr97j2kIBJD9K3mbOHSHiQDasj6Y3forATbIg4QZHxWnCeqqMkVYfUAivuL0L/68mMnagAAA" alt="" itemprop="image"><div class="social-embed-user-names"><p class="social-embed-user-names-name" itemprop="name">Terence Eden is on Mastodon</p>@edent</div></a><img class="social-embed-logo" alt="Twitter" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%0Aaria-label%3D%22Twitter%22%20role%3D%22img%22%0AviewBox%3D%220%200%20512%20512%22%3E%3Cpath%0Ad%3D%22m0%200H512V512H0%22%0Afill%3D%22%23fff%22%2F%3E%3Cpath%20fill%3D%22%231d9bf0%22%20d%3D%22m458%20140q-23%2010-45%2012%2025-15%2034-43-24%2014-50%2019a79%2079%200%2000-135%2072q-101-7-163-83a80%2080%200%200024%20106q-17%200-36-10s-3%2062%2064%2079q-19%205-36%201s15%2053%2074%2055q-50%2040-117%2033a224%20224%200%2000346-200q23-16%2040-41%22%2F%3E%3C%2Fsvg%3E"></header><section class="social-embed-text" itemprop="articleBody">Yay The Semantic Web! <a href="https://x.com/edent/status/1481352496639623169/photo/1">pic.x.com/aY20t5hGoG</a><div class="social-embed-media-grid"><a href="https://pbs.twimg.com/media/FI7R1f-XsAEDVXa.png" class="social-embed-media-link"><img class="social-embed-media" alt="Screenshot of HTML source code. There are several data price attributes with numbers. And one which is negative infinity." src="data:image/webp;base64,UklGRggSAABXRUJQVlA4IPwRAAAwUgCdASo8AbcAPrVWpU0nJKOiJ1WKKOAWiWNu/HyZjtzY66Tih/fV/vItX83pPyHgd9Af/L3avPJ+jv/MdL7/yP//7m/87/4XsQedr6vP+p9NT0AP//wTvgD+ydpf+i8NfGp8MzjsY/UjqTfLvvN5+87P+h4F/B/UF9q+bh8B+tHcbaF/rfQC9aPmHe2/7v9s9QvsR7AHlN3rX4H/m+wR/OP7p+yfusf1/j3+uPYK/oP+F6zXpAFxl9hRIeiQ9Eh6Iqb/k+iACTslv9rn6UHchjg2wxxha3+/ByFiimElahI4gAKRSz9qYIRvlCVd/Ef2poBF/v83O4zegKYvN4I1vwW+fPdcTaqoYxCwQhfHH1nrvCb1I4BbeqoT2VHf7zxwATLJxVWyXPmBbrY6zvI+/EqT5PF4Yn+e/AivGChTcwDGe7acv3pNRFri+t1eXIWkR56CXykvlJfKS+UlV5HAqUvBwNlgi6O0Tfyw4yPe4viGdU408ZVW6kURMPj7pYxBG9R4mveW+m8zlrQTzedCpEkg//fSd3RDRF7LgnmREGFIccUWVyHDS7yBkw/8qWp0QpofLR6uDhN89ze6qR0M/38+GKI28W3pjO1OgbYVeGiVEx6yyi8qawqFMPpHUh1leGjDFOho821BFJC/KDSD+fuOqvR0SmXLKfhfuya+WCJp71yWCuMnpaeQy8Rdd/tF/75QeZVA8PgHNZLyJYZEJvhPnOYfCGHM+3nCCMJQjBhN7x/wS2GBkPAGXorVljzlQEveztlA5EFMJIQ1LSURTrb5Pve/Lvavi2piON4Dj8svgnz8HOCRezkmFCbUzRgA4T2h6xB7YIt12pekj2inrWbeMJVBD9mM5ylpjHQS38dv47fx2/jt9YAA/v8cvuTo29Zr0wBkpgazCqVJTA1mFUqSjfHuqDACZFT8fawxAi9yJQ79J4q22ec78xuaN6BSO0B+HVSJzhvPuQeRgPn1kFvmYS2+ysELA5j4yJVmSTxLJWnrA8GDbJRWtgdHo4v/uMGHPWjRzuNY6c6tQEGX3E03o1uwviNNw8GCrU7Zu1kP7ihkuHAVuPaezH9n/u4lBI2kAz6Fk5DzpsNb+S3Ts83yOAG2hzL+zbNOXog1X7i0lQQWttXhxjPPatxV01QZa8AH9v1RB+c5nx4H8IcYI19lelhTwReIXG1FOY4ni9zjoPCFBGrxoHFj7O00/zKRpJUijMYmHzGsCoNegeSeGnnuzWkE7u9B/kB2m2f25WVDZtMMvjicZUuOM/oZIAm9PHwOGb4dVOEIw2/bwj7RrRwutsGlj131TWTciRY1/H8UpYtAPlA6n0ley865ziA/v0Li1AeJrDeeRdWJiStpWJE6gPu4qHezwHtYFtnWqqhy4kslITwHPqwNfbYfp7lm/cGlxY6qjDge+AJPjb6eZZeCXHNWBhWdE7wVs4OaT4kovMczWG5Mr3Wby9Zfij5jM9LMmaGC7l5Ilx7Ztend5/H7GAEF1QtuOp4//RvBAsa6aA76eP/yZlNmF0eVaHPp7e552q6ZXmR6XsYrkKiruPWFyN+iJ1PFqt/PyQubJZJeCH4F8PScxeGh0eY6NH3HCQ8bwpgDCzloD0rdt0mBguDL2TebxsF8p5xRW7pkicX03cNWeLN2s0WxnUt0Ai8qvRhOBQCSkmT3SOlgrDYOlLa+oUXQNcRuWi4YM7N4XNZnV+UkzkqR8y35hXDQmyuTnQEl562VApExIbIgp6ewh1RxZbIqq8xNl5ZyUL3x0ZZzX0StvMAMQh+qtjxlUY9uQhSLYh7xFpbzAVQWqknFs06vZRSWAOMvvlKdUm+NVpm+W5/zA1mvYhJ7rIc9KpzEWHCoHKxUviNUidIiIKMPben0AXkVhS3x+E1xuN/Odb/Jwsu+EaevbsbBlREahXtPIReoefZO0J9gRVGIgrJnpRX36t9BnzvQVSB8uT9FcjDTmCi+e2zRZHiGcK/s8WqO4FGbHtCZg1TNlpTWx104GX/kQOGTPB2BsNRFEq1vpx6xecjfSIyBnNNGfSA0OhLg4BVKvc5ZzVci5WIIj5DyBvGAJxeyj0hgxy2HOwzU1HrMQfdtO+fGyGbu2EAjnFn4BXSxL9wSdn4TwOsAGYCC+denF+oOR9GNDNUocLGynaRU0mxBwintUja4Q8Uz23K+VRPp8Pj0IZuf3GL7NjGNbHz6vmnjtvmXjPQPki6jjkOSrfu1vMgTpJtqmWY2CeoLlMygudb/fMLMHbjRJbnDqRavZ2cTtXV8hCDwCg5HgSnBaMJ2qPxdDmOkUjSkyBTKY8MpDFjyXx37NYuVfE93TNZFm4vPWyoFfBf33jWbBQ8QgpOm2E4t7YnnI6YOkaZU41wv4NSJjRlUgaGX73QHZDfAvH3wS5q78EnNFPvMXEuZPDAW1jn9shG0p/svnCGZdYXCEeIulV2YpS3e3D1OisYswekp2MnB7k9PLkFE1Ggf7BobYBSZeb4raTOq1Z/aFXM89HBjT6d6AoG1+7fy5Q3jRX3aKK7BkKkRCWSYAazuHsGaY9WwbOHHkpSYqXMDzwq5VM269reVTniwVVcC0BFC7Lm9ADz/XG1RdAQIE7np5Yw1+jjnvX6ppjFMBF5DAAAAKIwAAAxbbosfLfJRttfq6PHzWQb1fw9Yh854Lvd3f8faf1EkL356YWP1yA7orbz+kVkCwxWgWCQRkGQln9G3N5Q6MFRDdkdQP/rUZ+Srilc5Rx0FPyXq/eFvR3hzng7i7JkAoJyve4ZJY1VJiutLoh0Mpmv4lvsATvnMuWzzBhLKGjl4jjhd88OUQtUgyYO9Tc8iqG/E7ogIjuZoP2G7nbDyoYfTZxKTzVtFhB6TmACs5miykqNjBBW2XJhdmt+dF7lu1ct/54tZjbIJtvzsFDff4LHKBfW7xChhv0jq51T08dlt2YjgbeAPGmHX7AW05nVKdwCeDefLi2ly+LRSGOZSKikeTmBlb23khddsxKyakZP1MMsJEodrdKb/L/OU4uGAOSnfTg3ZbDMcNdRuD+3msjg4iQUohPRFWrvuOI/xONDAC1fE/7g3dKEpTw2+tGZTWJQ9gB5P8q6pUeZQvrgU67rl1kHjcV+bTNHbf+gFa1X6AmM2uG7gwA4StTacQUy1nwsv+h9wWmaHKP9RMLbUQ5FSikFRVOnGl9g4pUJKkkkgsQUDE+xgg0i526bQX2WwpNzKXa8zuIXIj8Ht64a0ByMaFbzMnzEXS6SceKFo38NIhLNvkLXX2DomwzVvet8gM75UKMctbO3xl6hQ/o5MnKjwgYmmooJBF5s62zDaeToQByZnKWUpO8kCe7CCAq8v02ULDKWAcxBKl0IY+uDgnxrN2UK2Tjktn+t3pTqJJWP5y9OMxgXQqPNw6x20gbJzXO9Rxj7hHzxZLP/+Q+ucpYPUMyVHJtvE2uZ7VRHWkdeEY4nxrzAJbgWPDiTrOvQ//q19dmn9mUOnJevWdK9UJjOp/n1F43UY3R2yV9NYt8VDI+FuW7RzgWcf+KTVDlHgC96KN8hBNdxtodBJz1wqAjp9mX4sXHVvVYQ3OlpXrqpxx/xfviXx6BWlpPfLAbz0IRjpwWjMQQgkwoRAxpM1kM4kNEv1LqWfK8c9aVn/2dL1TGNHixrFClbBn5aRRF8iDOVJ7jdqCtLNbbW5/zQKQRcHVH4TFlAuEcTTXSwnbP34O+VfEgz8501qS5l7AHOX8ffEV/vaiBg40EAbdBZyfGt+V6zzNb+Gz16rBrraWtdBou1uu7ipbPZkfABUFeDWzajKXtt+SGiafPu2Qar9sRXGbZc5IXzYwJXV/74qcIheNJPKswEr75FswrgSs021yEgatvkEFGdKciP2BfAGvegIwJa+WsRPJISFjBq7YbHktvdQ+gziJka4xh9M7CujT1YJuMIl9ms8C7j6jgmNGSEJZuQawDgupG6ZjGv6S6TOMWNllt4QXARHmViefyqsZHYSpVgM9escCkCSfHdtI1042VyAmzN3IF6HC89imIZ5+ts4FgscBT57kiUisrqIZgDlZVGDXZj0DnHCHPx1o/hxsb0EulbhPNJOF6M8FZLl3KO3AWXmf+M/+uPJYu7s+SPQHuIdP4Txy9JoCAfaeG7AC6K/eQCiCVPeli9RwaVRrz2y/cBWo4prBF/K19C4W3Td+K3AHvXFe7Qq82DMRngak1OnyLWCLZoWrjsTOBCO0vc6tQwk8ezAdTEuHO9TPQmUJDksIlcygG488/X3Op5BjV1xYnhwtvso2bqn0q81hf+fEjTe5KaiPhF/VYhzo1QKQN/P4L/mq+xkbulOVPlLDd4E9rUBYSccFjxDGa3kT0LtSw+c40IiDxsA4dQp0f2E6MAAAAACKDkjDAVRgGxYpFcjSVPsd7f/jWcqd7MGkDBaP8seHbXcG0/kgsHpgkQrOq/cDo74JYbuhsKFN3mhowtwZuljXS4kGRHqm/hzSYbX+ojqAAEyOvZkqpR/jfUQX73G0jQuVlyAyBreky97bGDvyNszGa/wa1xz3EFyjFGFKdex1yOaU4dQAAmGm1KKuqqsc9WdV1WBeVJhfy/2N+ZUzZXXO+N3kG3eBNFaydQKomd02kSbsDI1D/V845eAU3BV22EYz2hk1WyfqiKXpUkPP1AKDkB2/Z59c4zD5yU3g3xRfAuAe/Omw3s3sTgtKZR2Vejfnwm0Mqz1U3CpSzMiFHCr2DJh9qzMmGRjFUQ8YKRKEqEacwB/zZT3X7IZJtCAYhX+//6GEkEKWmEVAnfHKchzS1WYpx4Z4axOO0J5nXoQzaxF+5sa5/RZvGs9PMLwbPIJfYyqaOTP5FX0XcOtIdW7YFZqDq+4oP0fq+t6W4rsO4wCz7PsvSH5mqWjeDhdd9LbXuwYOtO92gtbr31erKahSsD0XVRabV1v04Ni4qgyI/3K9EOI+RbdkBIpkV4mvjN5C+5sa1nZndxFflbR5J1NUxGzAOshpHJkA1TnSNlJJkvICKP4nkZ6sd0X+vVgctRXNOtPEub6cy9oTxwhtD5wWS8RiQZvcJjwDrtdObGHsucUF/T+qA0kl4qU91E13BMA1nxTrVtlfW+2YHyI2dOIjg1snM1ncV+xF/JLMOcHd09Ku5ltv67z6JAD0gLLx0ASHmgGbhX6lQgMsS0dOShaUAWxTk9mlZVojjuLk5dEzgXDt4Igr5B7GLDI0VeaTO0PNVChdRGunfxoigxX3gp7C1Jqtqa3iCfUj6pZaBnfwpxr82q3L2HmdgEo1g9dtSnce05bHD5Ns7lNCokGVCG2aeOqW6ozkz6V8Rp6Dfu1TaATlInGn2E2jQnQfJU60QhH2idD2mqMEPdqP2zbCCWucNr4+4gvaTqPsD4lLGwKXwsuxBsctlrl9P8X59q2btIXZzJdq5Hhbf4iKscYBsR6x9HV60T+D0t2k9u01/lywKhTTNPPXYQrYAbc3PE3u6J7P8AgHwfuzrZh9B6AivC8cc0PrTEtlZnOilz3gLr20Uw+U6Ue7c7MJEMdnNL9ddFwihDOXE4ck9SsOHaMyfjP3b4bWCd/JEgXhYGwj9lyU6iAfXSNhxO2hM2329oVwjjmKiBGCnxSVzbAg5iCpS5WK6na3QZwIWY1Tvy/eJidtY4ogsftdE5PiviZQsnOHiB6KmqFhm93E2bUqf9iPxLaGNT7paFVr+eF5MljgVLpxOlBSjJqkhSJQduRljyi/lkUBkc6bA3Z7/1vgx9lCDvV50wCMkEIap0SzIQAkBGje2b3a4kQox3Ty9DEYWyLy0J7CXUL2OIq4Gn/PiHoWdhgVQzoRuk+iqoG/ZJ8v0wAYthrLLnRrBtz7PIPsqjnHZR6cSQs8JNNTIi2wV46cJJv/M3+Y2FsnIzpS5TlLurTIojB68qAt7zwmb1I9bYHXEEOnw8jNbjpVKL4njknUzLKS9L4zsWfJn+rq7VGlKrGlCH85fel6bfsShtiiFUem87pGwrB+T6FelpOoTaz1jzm2zEbTg90jKfgCl6mTUA32vMpb2J8bjOT2kySjLmcHUMfQDH29WIxY2SBD/e2z7I6MoJw7dzzQbGCYhp01l8Mt3zl8Kjochf888GEjB4tPkuf14o4/k92K5LEKGN3gLQAAAAAAAqvz0AAAAAAAA=="></a></div></section><hr class="social-embed-hr"><footer class="social-embed-footer"><a href="https://twitter.com/edent/status/1481352496639623169"><span aria-label="11 likes" class="social-embed-meta">❤️ 11</span><span aria-label="2 replies" class="social-embed-meta">💬 2</span><span aria-label="0 reposts" class="social-embed-meta">🔁 0</span><time datetime="2022-01-12T19:48:52.000Z" itemprop="datePublished">19:48 - Wed 12 January 2022</time></a></footer></blockquote>

<p>That's usually when the item is unavailable.</p>

<p>Next, overwrite the <code>old_files.csv</code> with today's prices - we'll use these tomorrow as the old prices.</p>

<pre><code class="language-python">#   Save the Data
new_prices.to_csv('old_prices.csv', index=False)
</code></pre>

<p>Finally, how does the message get sent? It's OK to print something on the console - but I want to get the alerts via email.</p>

<p>This uses <a href="https://docs.python.org/3/library/smtplib.html"><code>smtplib</code></a> to send something via your own SMTP server. You'll need to add your <em>own</em> email server details here!</p>

<pre><code class="language-python">import smtplib
from email.message import EmailMessage

#   Send Email
def send_email(message):
    email_user = 'me@example.com'
    email_password = 'P455w0rd!'
    to = 'you@example.com'
    msg = EmailMessage()
    msg.set_content(message)
    msg['Subject'] = "Today's price drops"
    msg['From'] = email_user
    msg['To'] = to
    server = smtplib.SMTP_SSL('smtp.example.com', 465)
    server.ehlo()
    server.login(email_user, email_password)
    server.send_message(msg)
    server.quit()
</code></pre>

<p>And that's pretty much it! Stick it in a <code>crontab</code> and have it run once per day. Amazon can get a bit funny about screen scraping, so best not to run it too often or you'll hit an IP-based CAPTCHA.</p>

<p>There are a couple of minor annoyances.</p>

<ul>
<li>Some prices fluctuate by pennies. Perhaps you only want to know if the price is lower by more than £1, or more than 10%?</li>
<li>No tracking the lowest price. Do you want to know if it's the lowest price in the last few weeks?</li>
<li>Doesn't automatically buy stuff. It would be nice to just buy a book any time it drops to be lower than a quid.</li>
</ul>

<p>The full code is available on <a href="https://github.com/edent/Amazon-Wishlist-Pricedrop-Alert">github.com/edent/Amazon-Wishlist-Pricedrop-Alert</a>.</p>

<p>Enjoy!</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=41673&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2022/01/use-python-to-get-alerted-when-an-amazon-wishlist-item-drops-in-price/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
	</channel>
</rss>
