2021s2_workshop6_solutions
Elements Of Data Processing (2021S2) – Week 6¶
Web scraping¶
The BeautifulSoup library can be used to scrape data from a web page for processing and analysis. You can find out more about BeautifulSoup at https://www.crummy.com/software/BeautifulSoup/
This example extracts tennis scores from the 2019 ATP Tour¶
In [1]:
#This example extracts tennis scores from the 2019 ATP Tour
import requests
from bs4 import BeautifulSoup
import pandas as pd
import unicodedata
import re
import matplotlib.pyplot as plt
%matplotlib inline
#Specify the page to download
u = ‘https://en.wikipedia.org/wiki/2019_ATP_Tour’
page = requests.get(u)
soup = BeautifulSoup(page.text, ‘html.parser’)
#Locate the section we’re interested in. Here we are after the second table after the ‘ATP_ranking id’
section = soup.find(id=’ATP_ranking’)
results = section.findNext(‘table’).findNext(‘table’)
#Iterate through all rows in the resultant table
rows = results.find_all(‘tr’)
i = 0
records = []
#for row in rows[1:2]:
# cells = row.find_all(‘th’)
# print(“{0}, {1}, {2} ,{3}”.format(cells[0].text.strip(), cells[1].text.strip(), cells[2].text.strip(), cells[3].text.strip()))
# # column headers are #, Player, Points, Tours
for row in rows[2:]:
cells = row.find_all(‘td’)
record = []
#print(“{0}::{1}::{2}::{3}”.format(cells[0].text.strip(), cells[1].text.strip(), cells[2].text.strip(), cells[3].text.strip()))
# column value: 1::Rafael Nadal (ESP)::9,585::12
#Removes junk characters from string and stores the result
ranking = int(unicodedata.normalize(“NFKD”, cells[0].text.strip()))
record.append(int(ranking))
player = unicodedata.normalize(“NFKD”, cells[1].text.strip())
#Removes the country from the player name, removing surrounding whitespaces.
player_name = re.sub(‘\(.*\)’, ”, player).strip()
#print(player_name)
record.append(player_name)
#Remove the thousands separator from the points value and store as an integer
points = unicodedata.normalize(“NFKD”, cells[2].text.strip())
record.append(int(re.sub(‘,’, ”, points)))
# number of tours: integer type
tours = unicodedata.normalize(“NFKD”, cells[3].text.strip())
record.append(int(tours))
#Store the country code separately
country_code = re.search(‘\((.*)\)’, player).group(1)
record.append(country_code)
#print(record)
#[1, ‘Rafael Nadal’, 9585, 12, ‘ESP’]
records.append(record)
i = i+1
column_names = [“ranking”, “player”, “points”, “tours”, “country”]
tennis_data = pd.DataFrame(records, columns = column_names)
plt.xticks(rotation=’vertical’)
plt.bar(tennis_data[‘player’], tennis_data[‘points’])
plt.ylabel(‘points’)
plt.title(“ATP Tour – player points”)
plt.show()
Side note on unicodedata.normalize()¶
Web pages commonlu uses uncode encoding.
Most ASCII characters are printable characters of the english alphabet such as abc, ABC, 123, ?&!, etc., represented as a number between 32 and 127.
Unicode represents most written languages and still has room for even more; this
includes typical left-to-right scripts like English and even right-to-left scripts like Arabic. Chinese, Japanese, and the many other variants are also represented within Unicode
ASCII has its equivalent within Unicode.
In Unicode, several characters can be expressed in various way.
For example, the character U+00C7 (LATIN CAPITAL LETTER C WITH CEDILLA)
can also be expressed as the sequence U+0043 (LATIN CAPITAL LETTER C) U+0327 (COMBINING CEDILLA).
The Unicode standard defines various normalization forms of a Unicode string,
based on the definition of canonical equivalence and compatibility equivalence.
The function unicodedata.normalize(“NFKD”, unistr)
will apply the compatibility decomposition, i.e. replace all compatibility characters with their equivalents.
Example:¶
In [2]:
unistr = u’\u2460′
print(“{0} is the equivalent character of {1}”.format(unicodedata.normalize(‘NFKD’, unistr), unistr))
1 is the equivalent character of ①
Exercise 1 ¶
Produce a graph similar to the example above for the 2019 ATP Doubles Scores.
First locate the section we’re interested in.
In [3]:
# Solution of exercise 1
import requests
from bs4 import BeautifulSoup
import pandas as pd
import unicodedata
import re
import matplotlib.pyplot as plt
#Specify the page to download
u = ‘https://en.wikipedia.org/wiki/2019_ATP_Tour’
page = requests.get(u)
soup = BeautifulSoup(page.text, ‘html.parser’)
#***Locate the section we’re interested in. Here we are after the second table after the ‘Double’ id
section = soup.find(id=’Doubles’)
results = section.findNext(‘table’).findNext(‘table’)
#Iterate through all rows in the resultant table
rows = results.find_all(‘tr’)
i = 0
records = []
#for row in rows[1:2]:
# cells = row.find_all(‘th’)
# #print(“{0}, {1}, {2} ,{3}”.format(cells[0].text.strip(), cells[1].text.strip(), cells[2].text.strip(), cells[3].text.strip()))
# # column headers are #, Team, Points ,Tours
for row in rows[2:]:
cells = row.find_all(‘td’)
record = []
#print(“{0}::{1}::{2}::{3}”.format(cells[0].text.strip(), cells[1].text.strip(), cells[2].text.strip(), cells[3].text.strip()))
# column value: 1::Juan Sebastián Cabal (COL) Robert Farah (COL)::8,300::21
#Removes junk characters from string and stores the result
ranking = int(unicodedata.normalize(“NFKD”, cells[0].text.strip()))
record.append(int(ranking))
team = unicodedata.normalize(“NFKD”, cells[1].text.strip())
#Removes the country from the player names, removing surrounding whitespaces.
#print(team)
# *** note this will not work because matching is greedy) : re.sub(‘\(.*\)’, ”, player).strip()
player_names = re.sub(‘\([^)]+\)$’, ”, team).strip()
player_names = re.sub(‘\([^)]+\)’, ‘-‘, player_names).strip()
#print(player_names)
record.append(player_names)
#Remove the thousands separator from the points value and store as an integer
points = unicodedata.normalize(“NFKD”, cells[2].text.strip())
record.append(int(re.sub(‘,’, ”, points)))
# number of tours: integer type
tours = unicodedata.normalize(“NFKD”, cells[3].text.strip())
record.append(int(tours))
#***Store the country_code pair separately
countries = re.findall(‘\(([^)]+)\)’, team)
record.append(‘-‘.join(countries))
#print(record)
# [1, ‘Juan Sebastián Cabal – Robert Farah’, 8300, 21, ‘COL-COL’]
records.append(record)
i = i+1
column_names = [“ranking”, “team”, “points”, “tours”, “countries”]
tennis_data = pd.DataFrame(records, columns = column_names)
plt.xticks(rotation=’vertical’)
plt.bar(tennis_data[‘team’], tennis_data[‘points’])
plt.ylabel(‘points’)
plt.title(“ATP Tour – Doubles points”)
Out[3]:
Text(0.5, 1.0, ‘ATP Tour – Doubles points’)
Web crawling¶
This example implements a simplified Web crawler that traverses the site
http://books.toscrape.com/
In [4]:
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
import pandas as pd
import unicodedata
import re
import matplotlib.pyplot as plt
page_limit = 20
#Specify the initial page to crawl
base_url = ‘http://books.toscrape.com/’
seed_item = ‘index.html’
seed_url = base_url + seed_item
page = requests.get(seed_url)
soup = BeautifulSoup(page.text, ‘html.parser’)
visited = {};
visited[seed_url] = True
pages_visited = 1
print(seed_url)
#Remove index.html
links = soup.findAll(‘a’)
seed_link = soup.findAll(‘a’, href=re.compile(“^index.html”))
#to_visit_relative = list(set(links) – set(seed_link))
to_visit_relative = [l for l in links if l not in seed_link]
# Resolve to absolute urls
to_visit = []
for link in to_visit_relative:
to_visit.append(urljoin(seed_url, link[‘href’]))
#Find all outbound links on succsesor pages and explore each one
while (to_visit):
# Impose a limit to avoid breaking the site
if pages_visited == page_limit :
break
# consume the list of urls
link = to_visit.pop(0)
print(link)
# need to concat with base_url, an example item
page = requests.get(link)
# scarping code goes here
soup = BeautifulSoup(page.text, ‘html.parser’)
# mark the item as visited, i.e., add to visited list, remove from to_visit
visited[link] = True
to_visit
new_links = soup.findAll(‘a’)
for new_link in new_links :
new_item = new_link[‘href’]
new_url = urljoin(link, new_item)
if new_url not in visited and new_url not in to_visit:
to_visit.append(new_url)
pages_visited = pages_visited + 1
print(‘\nvisited {0:5d} pages; {1:5d} pages in to_visit’.format(len(visited), len(to_visit)))
#print(‘{0:1d}’.format(pages_visited))
http://books.toscrape.com/index.html
http://books.toscrape.com/catalogue/category/books_1/index.html
http://books.toscrape.com/catalogue/category/books/travel_2/index.html
http://books.toscrape.com/catalogue/category/books/mystery_3/index.html
http://books.toscrape.com/catalogue/category/books/historical-fiction_4/index.html
http://books.toscrape.com/catalogue/category/books/sequential-art_5/index.html
http://books.toscrape.com/catalogue/category/books/classics_6/index.html
http://books.toscrape.com/catalogue/category/books/philosophy_7/index.html
http://books.toscrape.com/catalogue/category/books/romance_8/index.html
http://books.toscrape.com/catalogue/category/books/womens-fiction_9/index.html
http://books.toscrape.com/catalogue/category/books/fiction_10/index.html
http://books.toscrape.com/catalogue/category/books/childrens_11/index.html
http://books.toscrape.com/catalogue/category/books/religion_12/index.html
http://books.toscrape.com/catalogue/category/books/nonfiction_13/index.html
http://books.toscrape.com/catalogue/category/books/music_14/index.html
http://books.toscrape.com/catalogue/category/books/default_15/index.html
http://books.toscrape.com/catalogue/category/books/science-fiction_16/index.html
http://books.toscrape.com/catalogue/category/books/sports-and-games_17/index.html
http://books.toscrape.com/catalogue/category/books/add-a-comment_18/index.html
http://books.toscrape.com/catalogue/category/books/fantasy_19/index.html
visited 20 pages; 372 pages in to_visit
Exercise 2 ¶
The code above can easily be end up stuck in a crawler trap.
Explain three ways this could occur and suggest possible solutions
In [5]:
#Exercise 2: The code above can easily be end up stuck in a crawler trap.
#Explain three ways this could occur and suggest possible solutions
#Some possibilities include:
#- Infinite links (e.g. calendar dates)
#- Autogenerated pages
#- Auto-generated URLs linking to the same content
#- Bookmarks or URL parameters making the same page appear to have a different URL
possibilities = [
‘- Infinite links (e.g. calendar dates)’,
‘- Autogenerated pages’,
‘- Auto-generated URLs linking to the same content’,
‘- Bookmarks or URL parameters making the same page appear to have a different URL’
]
for p in possibilities:
print(p)
– Infinite links (e.g. calendar dates)
– Autogenerated pages
– Auto-generated URLs linking to the same content
– Bookmarks or URL parameters making the same page appear to have a different URL
Exercise 3 ¶
Modify the code above to print the titles of as many books as can be found within the page_limit
In [6]:
# Solution to exercise 3
# Modify the code above to print the titles of as many books as can be found within the page_limit
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
import pandas as pd
import unicodedata
import re
import matplotlib.pyplot as plt
page_limit = 20
#Specify the initial page to crawl
base_url = ‘http://books.toscrape.com/’
seed_item = ‘index.html’
seed_url = base_url + seed_item
#print(seed_url)
page = requests.get(seed_url)
soup = BeautifulSoup(page.text, ‘html.parser’)
#visited = {};
#visited[seed_url] = True
pages_visited = 1
#Remove index.html
links = soup.findAll(‘a’)
seed_link = soup.findAll(‘a’, href=re.compile(“^index.html”))
#to_visit_relative = list(set(links) – set(seed_link))
to_visit_relative = [l for l in links if l not in seed_link]
titles = set()
# 1. Resolve to absolute urls
# 2. Add titles in seed_url page
to_visit = []
for link in to_visit_relative:
to_visit.append(urljoin(seed_url, link[‘href’]))
if link.has_attr(‘title’):
titles.add(link[‘title’])
#Find all outbound links on succsesor pages and explore each one
while (to_visit):
# consume the list of urls
link = to_visit.pop(0)
#print(link)
# Impose a limit to avoid breaking the site
if pages_visited == page_limit :
break
# need to concat with base_url, an example item
page = requests.get(link)
# scarping code goes here
soup = BeautifulSoup(page.text, ‘html.parser’)
# mark the item as visited, i.e., add to visited list, remove from to_visit
visited[link] = True
new_links = soup.findAll(‘a’)
for new_link in new_links :
# add all titles of the links of the current link
if new_link.has_attr(‘title’):
titles.add(new_link[‘title’])
# add new absolute url to to_visit list
new_item = new_link[‘href’]
new_url = urljoin(link, new_item)
if new_url not in visited and new_url not in to_visit:
to_visit.append(new_url)
pages_visited = pages_visited + 1
print(‘\nvisited {0:5d} pages; {1:5d} pages in to_visit’.format(len(visited), len(to_visit)))
print(“{0:3d} unique titles”.format(len(titles)))
for b in titles:
print(“\t{0}”.format(b))
visited 20 pages; 371 pages in to_visit
307 unique titles
Soumission
Twenties Girl
Glory over Everything: Beyond The Kitchen House
Playing with Fire
Set Me Free
The Regional Office Is Under Attack!
Maybe Something Beautiful: How Art Transformed a Neighborhood
Bridget Jones’s Diary (Bridget Jones #1)
The Undomestic Goddess
The Perfect Play (Play by Play #1)
Girl With a Pearl Earring
King’s Folly (The Kinsman Chronicles #1)
A Paris Apartment
Lilac Girls
The Last Mile (Amos Decker #2)
The Murder of Roger Ackroyd (Hercule Poirot #4)
The Thing About Jellyfish
The Psychopath Test: A Journey Through the Madness Industry
This Is Your Brain on Music: The Science of a Human Obsession
The Coming Woman: A Novel Based on the Life of the Infamous Feminist, Victoria Woodhull
Old Records Never Die: One Man’s Quest for His Vinyl and His Past
Meditations
Forever Rockers (The Rocker #12)
Still Life with Bread Crumbs
William Shakespeare’s Star Wars: Verily, A New Hope (William Shakespeare’s Star Wars #4)
The Genius of Birds
The Gutsy Girl: Escapades for Your Life of Epic Adventure
Lumberjanes, Vol. 1: Beware the Kitten Holy (Lumberjanes #1-4)
My Name Is Lucy Barton
The Great Railway Bazaar
Tastes Like Fear (DI Marnie Rome #3)
A Gentleman’s Position (Society of Gentlemen #3)
Please Kill Me: The Uncensored Oral History of Punk
The Stranger
How Music Works
Shopaholic Ties the Knot (Shopaholic #3)
The Death of Humanity: and the Case for Life
Lumberjanes Vol. 3: A Terrible Plan (Lumberjanes #9-12)
A Girl’s Guide to Moving On (New Beginnings #2)
Take Me with You
Thirst
Everydata: The Misinformation Hidden in the Little Data You Consume Every Day
Icing (Aces Hockey #2)
A Year in Provence (Provence #1)
Dune (Dune #1)
The Power of Now: A Guide to Spiritual Enlightenment
Angels Walking (Angels Walking #1)
The Book of Mormon
The Road to Little Dribbling: Adventures of an American in Britain (Notes From a Small Island #2)
Island of Dragons (Unwanteds #7)
Off the Hook (Fishing for Trouble #1)
The Omnivore’s Dilemma: A Natural History of Four Meals
Judo: Seven Steps to Black Belt (an Introductory Guide for Beginners)
Of Mice and Men
Something Borrowed (Darcy & Rachel #1)
Crown of Midnight (Throne of Glass #2)
My Mrs. Brown
The Hound of the Baskervilles (Sherlock Holmes #5)
The Picture of Dorian Gray
We Love You, Charlie Freeman
That Darkness (Gardiner and Renner #1)
The Bane Chronicles (The Bane Chronicles #1-11)
Shrunken Treasures: Literary Classics, Short, Sweet, and Silly
The Devil Wears Prada (The Devil Wears Prada #1)
The White Queen (The Cousins’ War #1)
A Brush of Wings (Angels Walking #3)
You Are What You Love: The Spiritual Power of Habit
A World of Flavor: Your Gluten Free Passport
I am a Hero Omnibus Volume 1
Sugar Rush (Offensive Line #2)
Nap-a-Roo
The Inefficiency Assassin: Time Management Tactics for Working Smarter, Not Longer
The Nameless City (The Nameless City #1)
The Past Never Ends
Maude (1883-1993):She Grew Up with the country
Rain Fish
The Lonely Ones
Foundation (Foundation (Publication Order) #1)
Demigods & Magicians: Percy and Annabeth Meet the Kanes (Percy Jackson & Kane Chronicles Crossover #1-3)
Algorithms to Live By: The Computer Science of Human Decisions
Boar Island (Anna Pigeon #19)
orange: The Complete Collection 1 (orange: The Complete Collection #1)
Danganronpa Volume 1
Little Red
Her Backup Boyfriend (The Sorensen Family #1)
Hide Away (Eve Duncan #20)
The Mindfulness and Acceptance Workbook for Anxiety: A Guide to Breaking Free from Anxiety, Phobias, and Worry Using Acceptance and Commitment Therapy
Once Was a Time
Meternity
Pop Gun War, Volume 1: Gift
The Art Forger
So You’ve Been Publicly Shamed
A Court of Thorns and Roses (A Court of Thorns and Roses #1)
A Murder in Time
The Whale
The Bridge to Consciousness: I’m Writing the Bridge Between Science and Our Old and New Beliefs.
#HigherSelfie: Wake Up Your Life. Free Your Soul. Find Your Tribe.
Throwing Rocks at the Google Bus: How Growth Became the Enemy of Prosperity
Most Wanted
Olio
The Passion of Dolssa
Reskilling America: Learning to Labor in the Twenty-First Century
Avatar: The Last Airbender: Smoke and Shadow, Part 3 (Smoke and Shadow #3)
Will You Won’t You Want Me?
A History of God: The 4,000-Year Quest of Judaism, Christianity, and Islam
Eligible (The Austen Project #4)
Neither Here nor There: Travels in Europe
The Metamorphosis
Alice in Wonderland (Alice’s Adventures in Wonderland #1)
The Pilgrim’s Progress
Birdsong: A Story in Pictures
Delivering the Truth (Quaker Midwife Mystery #1)
The Girl on the Train
Gone with the Wind
Chase Me (Paris Nights #2)
Arena
Hold Your Breath (Search and Rescue #1)
I Am Pilgrim (Pilgrim #1)
Our Band Could Be Your Life: Scenes from the American Indie Underground, 1981-1991
Raymie Nightingale
A Flight of Arrows (The Pathfinders #2)
Lumberjanes, Vol. 2: Friendship to the Max (Lumberjanes #5-8)
The Glittering Court (The Glittering Court #1)
Thirteen Reasons Why
The Song of Achilles
The Bachelor Girl’s Guide to Murder (Herringford and Watts Mysteries #1)
Ready Player One
The Wedding Dress
A Piece of Sky, a Grain of Rice: A Memoir in Four Meditations
Animal Farm
The Bhagavad Gita
Luis Paints the World
Keep Me Posted
Scott Pilgrim’s Precious Little Life (Scott Pilgrim #1)
The Dovekeepers
Playing from the Heart
Soul Reader
The Dirty Little Secrets of Getting Your Dream Job
The Wedding Pact (The O’Malleys #2)
Can You Keep a Secret?
The White Cat and the Monk: A Retelling of the Poem “Pangur Bán�
Saga, Volume 5 (Saga (Collected Editions) #5)
God: The Most Unpleasant Character in All Fiction
Walt Disney’s Alice in Wonderland
Suddenly in Love (Lake Haven #1)
Beyond Good and Evil
Cell
Something More Than This
Join
The Immortal Life of Henrietta Lacks
Worlds Elsewhere: Journeys Around Shakespeare’s Globe
A Time of Torment (Charlie Parker #14)
The Hidden Oracle (The Trials of Apollo #1)
Rip it Up and Start Again
Mr. Mercedes (Bill Hodges Trilogy #1)
The Nicomachean Ethics
Hollow City (Miss Peregrine’s Peculiar Children #2)
Princess Jellyfish 2-in-1 Omnibus, Vol. 01 (Princess Jellyfish 2-in-1 Omnibus #1)
The Torch Is Passed: A Harding Family Story
Do Androids Dream of Electric Sheep? (Blade Runner #1)
Life
The Secret of Dreadwillow Carse
Love Is a Mix Tape (Music #1)
Finders Keepers (Bill Hodges Trilogy #2)
The Edge of Reason (Bridget Jones #2)
The Emerald Mystery
City of Glass (The Mortal Instruments #3)
Soft Apocalypse
The Complete Stories and Poems (The Works of Edgar Allan Poe [Cameo Edition])
Rook
The Story of Hong Gildong
Libertarianism for Beginners
Sapiens: A Brief History of Humankind
The Five Love Languages: How to Express Heartfelt Commitment to Your Mate
The Widow
Vagabonding: An Uncommon Guide to the Art of Long-Term World Travel
The Book of Basketball: The NBA According to The Sports Guy
The Kite Runner
1,000 Places to See Before You Die
What Happened on Beale Street (Secrets of the South Mysteries #2)
The Last Girl (The Dominion Trilogy #1)
Private Paris (Private #10)
Aladdin and His Wonderful Lamp
Choosing Our Religion: The Spiritual Lives of America’s Nones
Fifty Shades Darker (Fifty Shades #2)
Starving Hearts (Triangular Trade Trilogy, #1)
Life, the Universe and Everything (Hitchhiker’s Guide to the Galaxy #3)
The Life-Changing Magic of Tidying Up: The Japanese Art of Decluttering and Organizing
Emma
The First Hostage (J.B. Collins #2)
Twenty Yawns
Critique of Pure Reason
I Had a Nice Time And Other Lies…: How to find love & sh*t like that
A Study in Scarlet (Sherlock Holmes #1)
Chronicles, Vol. 1
Don’t Be a Jerk: And Other Practical Advice from Dogen, Japan’s Greatest Zen Master
Searching for Meaning in Gailana
Poisonous (Max Revere Novels #3)
Girl in the Blue Coat
Masks and Shadows
On a Midnight Clear
Giant Days, Vol. 2 (Giant Days #5-8)
First and First (Five Boroughs #3)
Call the Nurse: True Stories of a Country Nurse on a Scottish Isle
The Nanny Diaries (Nanny #1)
At The Existentialist Café: Freedom, Being, and apricot cocktails with: Jean-Paul Sartre, Simone de Beauvoir, Albert Camus, Martin Heidegger, Edmund Husserl, Karl Jaspers, Maurice Merleau-Ponty and others
The Day the Crayons Came Home (Crayons)
Tracing Numbers on a Train
Doing It Over (Most Likely To #1)
Throne of Glass (Throne of Glass #1)
The Requiem Red
Sophie’s World
Something Blue (Darcy & Rachel #2)
America’s Cradle of Quarterbacks: Western Pennsylvania’s Football Factory from Johnny Unitas to Joe Montana
I’ve Got Your Number
Friday Night Lights: A Town, a Team, and a Dream
The House by the Lake
Mrs. Houdini
Having the Barbarian’s Baby (Ice Planet Barbarians #7.5)
Little Women (Little Women #1)
The Testament of Mary
Modern Romance
Love, Lies and Spies
The Midnight Watch: A Novel of the Titanic and the Californian
The Constant Princess (The Tudor Court #1)
Code Name Verity (Code Name Verity #1)
World Without End (The Pillars of the Earth #2)
Beowulf
Under the Tuscan Sun
The Electric Pencil: Drawings from Inside State Hospital No. 3
The Red Tent
Full Moon over Noah’s Ark: An Odyssey to Mount Ararat and Beyond
Some Women
The Time Keeper
Unicorn Tracks
Three Wishes (River of Time: California #1)
Run, Spot, Run: The Ethics of Keeping Pets
Take Me Home Tonight (Rock Star Romance #3)
The Star-Touched Queen
Rat Queens, Vol. 3: Demons (Rat Queens (Collected Editions) #11-15)
Candide
The Secret (The Secret #1)
The Black Maria
Kierkegaard: A Christian Missionary to Christians
Sit, Stay, Love
The Boys in the Boat: Nine Americans and Their Epic Quest for Gold at the 1936 Berlin Olympics
Kill ‘Em and Leave: Searching for James Brown and the American Soul
Proofs of God: Classical Arguments from Tertullian to Barth
I Hate Fairyland, Vol. 1: Madly Ever After (I Hate Fairyland (Compilations) #1-5)
Grey (Fifty Shades #4)
Wuthering Heights
Black Dust
Camp Midnight
The Little Prince
The Restaurant at the End of the Universe (Hitchhiker’s Guide to the Galaxy #2)
In a Dark, Dark Wood
Sleeping Giants (Themis Files #1)
Outcast, Vol. 1: A Darkness Surrounds Him (Outcast #1)
Sense and Sensibility
The Marriage of Opposites
The Invention of Wings
This One Summer
Dirty (Dive Bar #1)
In the Country We Love: My Family Divided
Princess Between Worlds (Wide-Awake Princess #5)
A Light in the Attic
Tuesday Nights in 1980
Saga, Volume 6 (Saga (Collected Editions) #6)
The Murder That Never Was (Forensic Instincts #5)
More Than Music (Chasing the Dream #1)
See America: A Celebration of Our National Parks & Treasured Sites
Reasons to Stay Alive
The Guernsey Literary and Potato Peel Pie Society
Unseen City: The Majesty of Pigeons, the Discreet Charm of Snails & Other Wonders of the Urban Wilderness
Bossypants
The Artist’s Way: A Spiritual Path to Higher Creativity
The Secret Garden
Sharp Objects
Shtum
Shakespeare’s Sonnets
Murder at the 42nd Street Library (Raymond Ambler #1)
The Project
The Bear and the Piano
Penny Maybe
Orchestra of Exiles: The Story of Bronislaw Huberman, the Israel Philharmonic, and the One Thousand Jews He Saved from Nazi Horrors
Codename Baboushka, Volume 1: The Conclave of Death
Tsubasa: WoRLD CHRoNiCLE 2 (Tsubasa WoRLD CHRoNiCLE #2)
A Shard of Ice (The Black Symphony Saga #1)
Patience
Changing the Game (Play by Play #2)
No One Here Gets Out Alive
Shobu Samurai, Project Aryoku (#3)
Tipping the Velvet
The Last Painting of Sara de Vos
A Summer In Europe
Voyager (Outlander #3)
Mesaerion: The Best Science Fiction Stories 1800-1849
And Then There Were None
The Vacationers
Dark Lover (Black Dagger Brotherhood #1)
A Series of Catastrophes and Miracles: A True Story of Love, Science, and Cancer
Spark Joy: An Illustrated Master Class on the Art of Organizing and Tidying Up
Settling the Score (The Summer Games #1)
A People’s History of the United States
The Wild Robot
Forever and Forever: The Courtship of Henry Longfellow and Fanny Appleton
It’s Only the Himalayas
In [ ]: