<?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>kobo &#8211; Terence Eden’s Blog</title>
	<atom:link href="https://shkspr.mobi/blog/tag/kobo/feed/" rel="self" type="application/rss+xml" />
	<link>https://shkspr.mobi/blog</link>
	<description>Regular nonsense about tech and its effects 🙃</description>
	<lastBuildDate>Wed, 19 Feb 2025 11:09:54 +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>kobo &#8211; Terence Eden’s Blog</title>
	<link>https://shkspr.mobi/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title><![CDATA[Automatic Kobo and Kindle eBook Arbitrage]]></title>
		<link>https://shkspr.mobi/blog/2025/02/automatic-kobo-and-kindle-ebook-arbitrage/</link>
					<comments>https://shkspr.mobi/blog/2025/02/automatic-kobo-and-kindle-ebook-arbitrage/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Wed, 19 Feb 2025 12:34:43 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[Amazon]]></category>
		<category><![CDATA[ebooks]]></category>
		<category><![CDATA[kindle]]></category>
		<category><![CDATA[kobo]]></category>
		<category><![CDATA[python]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=58241</guid>

					<description><![CDATA[This post will show you how to programmatically get the cheapest possible price on eBooks from Kobo.  Background  Amazon have decided to stop letting customers download their purchased eBooks onto their computers. That means I can&#039;t strip the DRM and read on my non-Amazon eReader.  So I guess I&#039;m not spending money with Amazon any more. I&#039;m moving to Kobo for three main reasons:   They provide…]]></description>
										<content:encoded><![CDATA[<p>This post will show you how to programmatically get the cheapest possible price on eBooks from Kobo.</p>

<h2 id="background"><a href="https://shkspr.mobi/blog/2025/02/automatic-kobo-and-kindle-ebook-arbitrage/#background">Background</a></h2>

<p>Amazon have decided to stop letting customers download their purchased eBooks onto their computers. That means I can't strip the DRM and read on my non-Amazon eReader.</p>

<p>So I guess I'm not spending money with Amazon any more. I'm moving to Kobo for three main reasons:</p>

<ol>
<li>They provide standard ePubs for download.</li>
<li>ePub DRM is trivial to remove.</li>
<li>Kobo will <em>undercut</em> Amazon's prices!</li>
</ol>

<p>Here's the thing. I <em>want</em> to <strong>buy</strong> my eBooks. It is <a href="https://goodereader.com/blog/e-book-news/where-do-consumers-get-their-e-books-from">trivial to pirate almost any modern book</a>. But, call me crazy, I like rewarding writers with a few pennies. That said, I'm not made of money, so I want to get the best (legal) deal possible.</p>

<p><a href="https://www.kobo.com/gb/en/p/pricematch-about">Kobo do a price-match with other eBook retailers</a>. It says:</p>

<blockquote><p>We'll award a credit to your Kobo account equal to the price difference, <strong>plus 10% of the competitor’s price</strong>.</p></blockquote>

<p>I found a book I wanted which was £4.99 on Kobo. The Amazon Kindle price was £4.31.</p>

<p><code>4.99 - ( (4.99 - 4.31) + (4.31 * 0.1) ) = 3.88</code></p>

<p>I purchased the book, sent a request for a price match, and got this email a few hours later:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2025/02/kobo.png" alt="﻿We’re pleased to confirm that the price match you requested has been successfully processed. The credit has been applied to your Kobo account. ﻿Credit amount: £ 1.11 GBP" width="1008" height="756" class="aligncenter size-full wp-image-58242">

<p>OK! So what steps can we automate, and which will have to remain manual?</p>

<h2 id="amazon-pricing-api"><a href="https://shkspr.mobi/blog/2025/02/automatic-kobo-and-kindle-ebook-arbitrage/#amazon-pricing-api">Amazon Pricing API</a></h2>

<p>Amazon have a <a href="https://webservices.amazon.com/paapi5/documentation/">Product Advertising API</a>. You will need to register for the <a href="https://affiliate-program.amazon.co.uk/">Amazon Affiliate Program</a> and make some qualifying sales before you get API access.</p>

<p>In order to search for an ISBN and get the price back, you need to send:</p>

<pre><code class="language-json">{
 "Keywords": "isbn:9781473613546",
 "Resources": ["Offers.Listings.Price"],
}
</code></pre>

<p>Using <a href="https://github.com/LeilaSchooley/paapi5-python-sdk">the updated Python API for PAAPI</a>:</p>

<pre><code class="language-python">from paapi5_python_sdk import DefaultApi, SearchItemsRequest, SearchItemsResource, PartnerType

def search_items():
    access_key = "ABC"
    secret_key = "123"
    partner_tag = "shkspr-21"
    host = "webservices.amazon.co.uk"
    region = "eu-west-1"

    api = DefaultApi(access_key=access_key, secret_key=secret_key, host=host, region=region)

    request = SearchItemsRequest(
        partner_tag=partner_tag,
        partner_type=PartnerType.ASSOCIATES,
        keywords="isbn:9781473613546",
        search_index="All",
        item_count=1,
        resources=["Offers.Listings.Price"]
    )

    response = api.search_items(request)

    print(response)

search_items()
</code></pre>

<p>(Add your own access key, secret key, and tag. You may need to change the host and region depending on where you are in the world.)</p>

<p>That returns something like:</p>

<pre><code class="language-json">{
    "search_result": {
        "items": [
            {
                "asin": "B09JLQHHXN",
                "detail_page_url": "https://www.amazon.co.uk/dp/B09JLQHHXN?tag=shkspr-21&amp;linkCode=osi&amp;th=1&amp;psc=1",
                "offers": {
                    "listings": [
                        {
                            "price": {
                                "amount": 2.99,
                                "currency": "GBP",
                                "display_amount": "£2.99"
                            }
                        }
                    ]
                }
            }
        ]
    }
}
</code></pre>

<p>(I've truncated the above so it only shows the relevant information.)</p>

<h2 id="kobo-isbn-price"><a href="https://shkspr.mobi/blog/2025/02/automatic-kobo-and-kindle-ebook-arbitrage/#kobo-isbn-price">Kobo ISBN &amp; Price</a></h2>

<p>Let's get the ISBN and Price of a book on Kobo. There's no easy API to do this. But, thankfully, Kobo embeds some Schema.org metadata.</p>

<p>Look at the source code for <a href="https://www.kobo.com/gb/en/ebook/venomous-lumpsucker-1">https://www.kobo.com/gb/en/ebook/venomous-lumpsucker-1</a></p>

<pre><code class="language-html">&lt;div id="ratings-widget-details-wrapper" class="kobo-gizmo"
     data-kobo-gizmo="RatingAndReviewWidget"
     data-kobo-gizmo-config ='{&amp;quot;googleBook&amp;quot;:&amp;quot;{\r\n  \&amp;quot;@context\&amp;quot;: \&amp;quot;http://schema.org\&amp;quot;,\r\n  \&amp;quot;@type\&amp;quot;: \&amp;quot;Book\&amp;quot;,\r\n  \&amp;quot;name\&amp;quot;: \&amp;quot;Venomous Lumpsucker\&amp;quot;,\r\n  \&amp;quot;genre\&amp;quot;: [\r\n    \&amp;quot;Fiction \\u0026 Literature\&amp;quot;,\r\n    \&amp;quot;Humorous\&amp;quot;,\r\n    \&amp;quot;Literary\&amp;quot;\r\n  ],\r\n  \&amp;quot;inLanguage\&amp;quot;: \&amp;quot;en\&amp;quot;,\r\n  \&amp;quot;author\&amp;quot;: {\r\n    \&amp;quot;@type\&amp;quot;: \&amp;quot;Person\&amp;quot;,\r\n    \&amp;quot;name\&amp;quot;: \&amp;quot;Ned Beauman\&amp;quot;\r\n  },\r\n  \&amp;quot;workExample\&amp;quot;: {\r\n    \&amp;quot;@type\&amp;quot;: \&amp;quot;Book\&amp;quot;,\r\n    \&amp;quot;author\&amp;quot;: {\r\n      \&amp;quot;@type\&amp;quot;: \&amp;quot;Person\&amp;quot;,\r\n      \&amp;quot;name\&amp;quot;: \&amp;quot;Ned Beauman\&amp;quot;\r\n    },\r\n    \&amp;quot;isbn\&amp;quot;: \&amp;quot;9781473613546\&amp;quot; …'&gt;
&lt;/div&gt;
</code></pre>

<p>Getting the data from the <code>data-kobo-gizmo-config</code> is a little tricky.</p>

<ul>
<li>Using Python Requests won't work because Kobo seem to run a JS CAPTCHA to detect scraping.</li>
<li>There is a <a href="https://github.com/janeczku/calibre-web/wiki/Kobo-Integration">Calibre-Web Kobo plugin</a> but it requires you to have a physical Kobo eReader in order to get an API key.</li>
<li>The <a href="https://webservice.rakuten.co.jp/explorer/api">Rakuten API</a> is only for the Japanese store.</li>
</ul>

<p>So we have to use the <a href="https://www.selenium.dev/documentation/webdriver/">Selenium WebDriver</a> to scrape the data:</p>

<pre><code class="language-python">from selenium import webdriver
from bs4 import BeautifulSoup
import json

#   Open the web page
browser = webdriver.Firefox()
browser.get("https://www.kobo.com/gb/en/ebook/venomous-lumpsucker-1")

#   Get the source
html_source = browser.page_source

#   Soupify
soup = BeautifulSoup(html_source, 'html.parser')

#   Get the encoded JSON Schema
schema = soup.find_all(id="ratings-widget-details-wrapper")[0].get("data-kobo-gizmo-config")

#   Convert to object from JSON
parsed_data = json.loads(schema)

#   Decode the nested JSON strings
parsed_data["googleBook"] = json.loads(parsed_data["googleBook"])

#    Get ISBN and Price
price = parsed_data["googleBook"]["workExample"]["potentialAction"]["expectsAcceptanceOf"]["price"]
isbn  = parsed_data["googleBook"]["workExample"]["isbn"]
print(isbn)
print(price)
</code></pre>

<h2 id="kobo-wishlist"><a href="https://shkspr.mobi/blog/2025/02/automatic-kobo-and-kindle-ebook-arbitrage/#kobo-wishlist">Kobo Wishlist</a></h2>

<p>OK, nearly there! Given a Kobo book URl we can get the price and ISBN, then use that ISBN to get the Kindle price.  But how do we get the Kobo book URl in the first place?</p>

<p>I'm adding all the books I want to my <a href="https://www.kobo.com/blog/how-to-use-the-kobo-wishlist-feature">Kobo Wishlist</a>.</p>

<p>Inside the Wishlist is a scrap of JavaScript which contains this JSON:</p>

<pre><code class="language-json">{
    "value": {
        "Items": [
            {
                "Title": "Venomous Lumpsucker",
                "Price": "£2.99",
                "ProductUrl": "/gb/en/ebook/venomous-lumpsucker-1",
            }
        ],
        "TotalItemCount": 11,
        "ItemCountByProductType": {
            "book": 11
        },
        "PageIndex": 1,
        "TotalNumPages": 1,
       }
}
</code></pre>

<p>(Simplified to make it easier to understand.)</p>

<p>Although there's a price, there's no ISBN, So you'll need to use the "ProductUrl" to get the ISBN and Price as above.</p>

<p>Sadly, unlike Amazon, there's no way to publicly share a wishlist. Getting the JSON requires logging in, so it's back to Selenium again!</p>

<p>This should be enough:</p>

<pre><code class="language-python">from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from bs4 import BeautifulSoup
import time

browser = webdriver.Firefox()
browser.get("https://www.kobo.com/gb/en/account/wishlist")

#       Log in
username_box = browser.find_element(By.NAME, "LogInModel.UserName")
username_box.clear()
username_box.send_keys('you@example.com')

password_box = browser.find_element(By.NAME, "LogInModel.Password")
password_box.clear()
password_box.send_keys('p455w0rd')

password_box.send_keys(Keys.RETURN)

time.sleep(5) # Wait for load and rendering
</code></pre>

<p>But the Kobo presents a CAPTCHA which prevents login.</p>

<p>There is an <em>unofficial</em> API which, sadly, <a href="https://github.com/subdavis/kobo-book-downloader/issues/121">doesn't seem to work at the moment</a>.</p>

<h2 id="next-steps"><a href="https://shkspr.mobi/blog/2025/02/automatic-kobo-and-kindle-ebook-arbitrage/#next-steps">Next Steps</a></h2>

<p>For now, I'm saving specific Kobo book URls into a file and then running a scrape once per day. Hopefully, the <a href="https://github.com/subdavis/kobo-book-downloader/">unofficial Kobo API</a> will be working again soon.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=58241&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2025/02/automatic-kobo-and-kindle-ebook-arbitrage/feed/</wfw:commentRss>
			<slash:comments>8</slash:comments>
		
		
			</item>
	</channel>
</rss>
