<?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>
	<copyright>© Terence Eden. 🄯 CC BY-SA. See https://shkspr.mobi/blog/copyright-and-copyleft/</copyright>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.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[Terence Eden]]></dc:creator>
		<pubDate>Sat, 22 Jan 2022 12:34:54 +0000</pubDate>
		<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>&lt;p&gt;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 &lt;a href="https://github.com/ldodds/wishlist-monitor"&gt;Leigh Dodds' Wishlist Monitor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;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 &lt;em&gt;some&lt;/em&gt; microdata on the page, which makes things slightly easier.&lt;/p&gt;

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

&lt;p&gt;First up, the standard boilerplate for getting and parsing HTML&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-python"&gt;from bs4 import BeautifulSoup
import requests
import re
import json
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I start by passing this function my wishlist URL &lt;a href="https://www.amazon.co.uk/gp/registry/wishlist/1A1NYHTAZ3N6V/"&gt;&lt;/a&gt;&lt;a href="https://www.amazon.co.uk/gp/registry/wishlist/1A1NYHTAZ3N6V/"&gt;https://www.amazon.co.uk/gp/registry/wishlist/1A1NYHTAZ3N6V/&lt;/a&gt;. 
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.&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-python"&gt;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
&lt;/code&gt;&lt;/pre&gt;

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

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

&lt;p&gt;Use BeautifulSoup to grab the string inside the anchor. Use a regex to match anything starting with &lt;code&gt;itemName_&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-python"&gt;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)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Prices and product IDs are slightly easier to scrape. They have microdata and JSON. For example:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-html"&gt;&amp;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"&amp;gt;...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The JSON in &lt;code&gt;data-reposition-action-params&lt;/code&gt; parses out to:&lt;/p&gt;

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

&lt;p&gt;Beautiful soup can grab the &lt;code&gt;data-price&lt;/code&gt; and then the Amazon ID:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-python"&gt;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)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That will get us all the names, IDs, and prices on the first page. What about subsequent pages?&lt;/p&gt;

&lt;p&gt;At the bottom of the page is this HTML:&lt;/p&gt;

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

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

&lt;pre&gt;&lt;code class="language-python"&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Putting it all together looks like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-python"&gt;#   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/")
&lt;/code&gt;&lt;/pre&gt;

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

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

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;I save the prices as a CSV.  The next time the code runs it reads that CSV into a DataFrame called &lt;code&gt;old_prices&lt;/code&gt;&lt;/p&gt;

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

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

&lt;p&gt;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.&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-python"&gt;#   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) &amp;lt; 1 and float(new_price) &amp;gt; 0:
            message += (name + "\n£" + str(new_price) + " was £" + str(old_price) + " https://www.amazon.co.uk/dp/"+id + "\n")
        elif float(new_price) &amp;lt; float(old_price) and float(new_price) &amp;gt; 0:
            message += (name + "\n£" + str(new_price) + " was £" + str(old_price) + " https://www.amazon.co.uk/dp/"+id + "\n")

print(message)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;There's a slight wrinkle because some of the prices are &lt;code&gt;-Infinity&lt;/code&gt;&lt;/p&gt;

&lt;blockquote class="social-embed" id="social-embed-1481352496639623169" lang="en" itemscope="" itemtype="https://schema.org/SocialMediaPosting"&gt;&lt;header class="social-embed-header" itemprop="author" itemscope="" itemtype="https://schema.org/Person"&gt;&lt;a href="https://twitter.com/edent" class="social-embed-user" itemprop="url"&gt;&lt;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"&gt;&lt;div class="social-embed-user-names"&gt;&lt;p class="social-embed-user-names-name" itemprop="name"&gt;Terence Eden is on Mastodon&lt;/p&gt;@edent&lt;/div&gt;&lt;/a&gt;&lt;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"&gt;&lt;/header&gt;&lt;section class="social-embed-text" itemprop="articleBody"&gt;Yay The Semantic Web! &lt;a href="https://x.com/edent/status/1481352496639623169/photo/1"&gt;pic.x.com/aY20t5hGoG&lt;/a&gt;&lt;div class="social-embed-media-grid"&gt;&lt;a href="https://pbs.twimg.com/media/FI7R1f-XsAEDVXa.png" class="social-embed-media-link"&gt;&lt;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,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"&gt;&lt;/a&gt;&lt;/div&gt;&lt;/section&gt;&lt;hr class="social-embed-hr"&gt;&lt;footer class="social-embed-footer"&gt;&lt;a href="https://twitter.com/edent/status/1481352496639623169"&gt;&lt;span aria-label="11 likes" class="social-embed-meta"&gt;❤️ 11&lt;/span&gt;&lt;span aria-label="2 replies" class="social-embed-meta"&gt;💬 2&lt;/span&gt;&lt;span aria-label="0 reposts" class="social-embed-meta"&gt;🔁 0&lt;/span&gt;&lt;time datetime="2022-01-12T19:48:52.000Z" itemprop="datePublished"&gt;19:48 - Wed 12 January 2022&lt;/time&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/blockquote&gt;

&lt;p&gt;That's usually when the item is unavailable.&lt;/p&gt;

&lt;p&gt;Next, overwrite the &lt;code&gt;old_files.csv&lt;/code&gt; with today's prices - we'll use these tomorrow as the old prices.&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

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

&lt;pre&gt;&lt;code class="language-python"&gt;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()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And that's pretty much it! Stick it in a &lt;code&gt;crontab&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;There are a couple of minor annoyances.&lt;/p&gt;

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

&lt;p&gt;The full code is available on &lt;a href="https://github.com/edent/Amazon-Wishlist-Pricedrop-Alert"&gt;github.com/edent/Amazon-Wishlist-Pricedrop-Alert&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;
&lt;img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=41673&amp;amp;HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager"&gt;</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>
