<?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>goodreads &#8211; Terence Eden’s Blog</title>
	<atom:link href="https://shkspr.mobi/blog/tag/goodreads/feed/" rel="self" type="application/rss+xml" />
	<link>https://shkspr.mobi/blog</link>
	<description>Regular nonsense about tech and its effects 🙃</description>
	<lastBuildDate>Tue, 10 Dec 2024 08:40:30 +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>goodreads &#8211; Terence Eden’s Blog</title>
	<link>https://shkspr.mobi/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title><![CDATA[Add review to Goodreads from Schema markup]]></title>
		<link>https://shkspr.mobi/blog/2019/12/add-review-to-goodreads-from-schema-markup/</link>
					<comments>https://shkspr.mobi/blog/2019/12/add-review-to-goodreads-from-schema-markup/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Tue, 10 Dec 2019 19:40:23 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[code]]></category>
		<category><![CDATA[goodreads]]></category>
		<category><![CDATA[hack]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[schema.org]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=33209</guid>

					<description><![CDATA[I write book reviews on my blog. I also want to syndicate them to Goodreads.  Sadly, Goodreads doesn&#039;t natively read the Schema.org markup I so carefully craft.  So here&#039;s the scrap of code I use to syndicate my reviews.  Goodreads API Keys  Get your Keys from https://www.goodreads.com/api/keys  You will also need to get OAuth tokens  For this documentation, I&#039;ll use the example keys - please…]]></description>
										<content:encoded><![CDATA[<p>I write book reviews on my blog. I also want to syndicate them to Goodreads.</p>

<p>Sadly, Goodreads doesn't natively read the Schema.org markup I so carefully craft.  So here's the scrap of code I use to syndicate my reviews.</p>

<h2 id="goodreads-api-keys"><a href="https://shkspr.mobi/blog/2019/12/add-review-to-goodreads-from-schema-markup/#goodreads-api-keys">Goodreads API Keys</a></h2>

<p>Get your Keys from <a href="https://www.goodreads.com/api/keys"></a><a href="https://www.goodreads.com/api/keys">https://www.goodreads.com/api/keys</a></p>

<p>You will also need to <a href="https://www.goodreads.com/api/oauth_example#python">get OAuth tokens</a></p>

<p>For this documentation, I'll use the example keys - please substitute them with your own keys.</p>

<pre><code class="language-python">from rauth.service import OAuth1Service, OAuth1Session

# Get a real consumer key &amp; secret from: https://www.goodreads.com/api/keys
API_KEY    = 'ABC123'
API_SECRET = 'XYZ789'

goodreads = OAuth1Service(
    consumer_key    = API_KEY,
    consumer_secret = API_SECRET,
    name='goodreads',
    request_token_url = 'https://www.goodreads.com/oauth/request_token',
    authorize_url     = 'https://www.goodreads.com/oauth/authorize',
    access_token_url  = 'https://www.goodreads.com/oauth/access_token',
    base_url          = 'https://www.goodreads.com/'
)

OAUTH_TOKEN, OAUTH_SECRET = goodreads.get_request_token(header_auth=True)
authorize_url = goodreads.get_authorize_url(request_token)
print(authorize_url)
</code></pre>

<p>Visit the URL printed out, and authorise the app.  Then run this to get the access tokens you need:</p>

<pre><code class="language-python">session = goodreads.get_auth_session(OAUTH_TOKEN, OAUTH_SECRET)
ACCESS_TOKEN = session.access_token
ACCESS_TOKEN_SECRET = session.access_token_secret
</code></pre>

<p>Save those tokens, we'll need them later!</p>

<h2 id="get-json-ld-from-html-page"><a href="https://shkspr.mobi/blog/2019/12/add-review-to-goodreads-from-schema-markup/#get-json-ld-from-html-page">Get JSON-LD from HTML page</a></h2>

<p>This uses <a href="https://docs.python-requests.org/en/latest/index.html">Requests</a> and <a href="https://pypi.org/project/beautifulsoup4/">BeautifulSoup</a>.</p>

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

review_url = 'https://shkspr.mobi/blog/2019/11/review-because-internet-by-gretchen-mcculloch/'
r = requests.get(review_url)
soup = BeautifulSoup(r.text)
pattern = re.compile(r"\"@type\":\"Review\"")
script = soup.find("script", text=pattern)
review = script.text
review_json = json.loads(review)

isbn        = review_json["itemReviewed"]["isbn"]
rating      = review_json["reviewRating"]["ratingValue"]
date        = review_json["datePublished"]:date[0:10]
description = review_json["description"] + " " + review_url

</code></pre>

<h2 id="isbn-to-goodreads-id"><a href="https://shkspr.mobi/blog/2019/12/add-review-to-goodreads-from-schema-markup/#isbn-to-goodreads-id">ISBN to Goodreads ID</a></h2>

<pre><code class="language-python">goodreads_id = requests.get("https://www.goodreads.com/book/isbn_to_id/" + isbn + "?key=" + API_KEY).text
</code></pre>

<p>This will return a number - the Goodreads ID.</p>

<h2 id="post-a-review"><a href="https://shkspr.mobi/blog/2019/12/add-review-to-goodreads-from-schema-markup/#post-a-review">Post a review</a></h2>

<p>The <a href="https://www.goodreads.com/api/index#review.create">documentation for posting Goodreads reviews</a> is a bit sparse.</p>

<pre><code class="language-python">goodreads_api = OAuth1Session(
    consumer_key        = API_KEY,
    consumer_secret     = API_SECRET,
    access_token        = ACCESS_TOKEN,
    access_token_secret = ACCESS_TOKEN_SECRET,
)

#   Post the review to the API
#   https://www.goodreads.com/api/index#review.create

review_data = {"book_id": goodreads_id, "review[review]":description, "review[rating]":rating, "review[read_at]":date}

response = goodreads_api.post('https://www.goodreads.com/review.xml', review_data)

print(response.text)
</code></pre>

<h2 id="setting-the-date"><a href="https://shkspr.mobi/blog/2019/12/add-review-to-goodreads-from-schema-markup/#setting-the-date">Setting the date</a></h2>

<p>The Goodreads API is... crap. <a href="https://www.goodreads.com/topic/show/20997772-review-read-at-doesn-t-work">Posting the review doesn't actually add a date to the review</a>. So you need to edit the review to post it <em>again</em>.  To start with, we need to get the review's ID:</p>

<pre><code class="language-python">import xml.etree.ElementTree as ET
tree = ET.fromstring(response.text)
review_id = tree.find("id").text
review_data = {"review[read_at]":date[0:10]}
response = goodreads_api.post('https://www.goodreads.com/review/'+review_id+'.xml', review_data)
print(response.text)
</code></pre>

<h2 id="get-the-code"><a href="https://shkspr.mobi/blog/2019/12/add-review-to-goodreads-from-schema-markup/#get-the-code">Get the code</a></h2>

<p><a href="https://gitlab.com/edent/schema-to-goodreads/tree/master">The Python code is available on my GitLab</a>.</p>

<p>But... The Goodreads API is a bit picky. ISBN lookup doesn't work very well, and bits of the API are flaky. So while this code <em>mostly</em> works, I don't run it that often.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=33209&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2019/12/add-review-to-goodreads-from-schema-markup/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
