Add review to Goodreads from Schema markup

by @edent | # # # # # #

I write book reviews on my blog. I also want to syndicate them to Goodreads.

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.

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’ll use the example keys – please substitute them with your own keys.

from rauth.service import OAuth1Service, OAuth1Session

# Get a real consumer key & 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)

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

session = goodreads.get_auth_session(OAUTH_TOKEN, OAUTH_SECRET)
ACCESS_TOKEN = session.access_token
ACCESS_TOKEN_SECRET = session.access_token_secret

Save those tokens, we’ll need them later!

Get JSON-LD from HTML page

This uses Requests and BeautifulSoup.

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

ISBN to Goodreads ID

goodreads_id = requests.get("https://www.goodreads.com/book/isbn_to_id/" + isbn + "?key=" + API_KEY).text

This will return a number – the Goodreads ID.

Post a review

The documentation for posting Goodreads reviews is a bit sparse.

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)

Setting the date

The Goodreads API is… crap. Posting the review doesn’t actually add a date to the review. So you need to edit the review to post it again. To start with, we need to get the review’s ID:

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)

Get the code

The Python code is available on my GitLab.

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 mostly works, I don’t run it that often.

Leave a Reply

Your email address will not be published. Required fields are marked *