Book info script May 26, 2007
Posted by samwyse in Scripting.trackback
The Problem
I like books. I like them a lot. Over the years, I’ve collected a large number of books. And now I want to get rid of some of them.
A lot of them I’m giving to Goodwill, but there are several sets that all deal with one topic or another. As I was packing some of these for transport, it occured to me that someone else may want to buy some of my books, so I needed to write an ad. Of course, good ads are packed with useful information about the product being sold, and collecting that data for a collection of random books is a bit labor intensive.
So, I wrote a Python script.
The idea is, you enter a bunch of ISBN numbers (10 digits, easy to find on any book with a barcode) and the script runs out to Amazon and finds out as much as it can about them. It then assembles the text of an ad: One line saying how many books there are, one line for each book detailing its title, author and original cost, and a final line saying how much you want to sell them for. (The latter is based on the total of the books’ used prices.) Next, the script downloads cover art for all of the books and assembles it into a single image showing the books laid out in a row. (This is because craigslist allows no more than four images per posting, and I often have more than four books in a set.)
What’s important is what this script doesn’t do. I decided not to load it up with a bunch of command line options; I realized that any changes that I make would be used going forward, and source code isn’t that hard to modify. If I ever need a different version for eBay, then I’ll just fork the source.
The Script
#!/usr/bin/python
"""
Creates a 'for sale' posting (suitable for craigslist, or maybe eBay) for a
list of books using information gathered from amazon.com. Simply supply a
list of ISBN numbers on the command line, then cut and paste the results into
your web browser.
Expected errors:
*** No module named amazon
This script needs the 'amazon.py' module. It may be obtained from
http://svn.plone.org/svn/collective/ATAmazon/trunk/amazon.py or
http://www.josephson.org/projects/pyamazon/files/pyamazon-0.65.zip
*** No module named PIL
This script needs the Python Imaging Library. It may be obtained from
http://www.pythonware.com/products/pil/
"""
import sys
AMZN_LICENSE_KEY = 'XXXXXXXXXXXXXXXXXXXX' # must get your own key!
OUTPUT_FILENAME = 'posting'
IMAGE_BACKGROUND = (0xAA, 0xAA, 0xAA) # light gray
FRAME_SIZE = 8
WIDTH_LIMIT = 640
HEIGHT_LIMIT = 960
def die(msg):
"""die(msg) -> Doesn't return.
Just like Perl, this prints a message to stderr and exits, but with a
twist! Before printing, we scan __doc__ for the message for a more
verbose version to print instead.
"""
docList = __doc__.splitlines()
try:
del docList[0:docList.index('*** %s' % msg)]
except ValueError:
docList = ('', msg)
try:
del docList[docList.index(''):]
except:
pass
print >>sys.stderr, '\n'.join(docList[1:])
sys.exit(1)
def joinEn(theList):
"""joinEn(list) -> string
Join the elements of a list using normal English syntax.
"""
if hasattr(theList, '__iter__'):
if len(theList) > 1:
last = theList.pop()
return ', '.join(theList) + " and " + last
return theList[0]
return theList
def stepOne(isbnList):
"""stepOne(isbnList) -> imageUrls
Looks up a list of ISBN numbers at amazon.com and returns a list of URLs
of cover images. As an important side-effect, it also prints a few facts
about each book to the file 'posting.txt' in the current directory.
"""
from time import sleep
try:
from amazon import setLicense, searchByPower
except ImportError, msg:
die(msg)
# set the Amazon developer key
setLicense(AMZN_LICENSE_KEY)
totalPrice = 0
posting = []
imageUrls = []
for isbn in isbnList:
sleep(1.1)
searchResults = searchByPower('isbn:'+isbn)
for book in searchResults:
totalPrice += float(book.UsedPrice[1:])
posting.append("\"%s\" by %s (%s new)" % (
book.ProductName,
joinEn(book.Authors.Author),
book.ListPrice,
))
imageUrls.append(book.ImageUrlMedium)
# create a description of what we found
postingText = open(OUTPUT_FILENAME + '.txt', 'wt')
print >>postingText, '\n'.join(
["%d books\n" % len(posting)] + posting +
["\nThis set can be yours for just $%.2f." % totalPrice]
)
postingText.close()
# return the list of URLs
return imageUrls
def stepTwo(imageUrls):
"""stepTwo(imageUrls) -> imageFiles
Given a list of URLs, saves each as a file in the current directory, and
returns the list of filenames.
"""
from urllib2 import urlopen
from urlparse import urlparse
# parse each url, keeping only the path
# split the path, keeping only the last piece
imageFiles = [ urlparse(url)[2].rsplit('/', 2)[-1] for url in imageUrls ]
# download the files
for url, fname in zip(imageUrls, imageFiles):
try:
img_bin_data = urlopen(url).read()
new_img_file = open(fname, 'wb')
new_img_file.write(img_bin_data)
new_img_file.close()
except: pass
# return the list of names
return imageFiles
def stepThree(imageFiles):
"""stepThree(imageFiles) -> None
Given a list of image files, creates a single gridded image consisting
of the images arranged in columns and rows. The resulting image is
saved to the file 'posting.jpg' in the current directory.
"""
try:
from PIL import Image
except ImportError, msg:
die(msg)
# load all of the images
imageList = [ Image.open(fname) for fname in imageFiles ]
# deduce an optimum number of rows and columns
for nr in xrange(1, len(imageList)):
nc = (len(imageList) + nr - 1) // nr
maxWidth = max([i.size[0] for i in imageList]) + FRAME_SIZE
maxHeight = max([i.size[1] for i in imageList]) + FRAME_SIZE
size = (maxWidth * nc + FRAME_SIZE, maxHeight * nr + FRAME_SIZE)
if size[0] < WIDTH_LIMIT: break
if size[1] > HEIGHT_LIMIT: break
# create the enclosing image
combo = Image.new('RGB', size, IMAGE_BACKGROUND)
offsetList = [ (maxWidth * x + FRAME_SIZE//2, maxHeight * y + FRAME_SIZE//2)
for y in xrange(nr) for x in xrange(nc) ]
for image, offset in zip(imageList, offsetList):
combo.paste(image, (
offset[0] + (maxWidth - image.size[0]) // 2,
offset[1] + (maxHeight - image.size[1]) // 2,
))
# save the results
combo.save(OUTPUT_FILENAME + '.jpg')
# For testing purposes, here are some lists of ISBNs and the names
# (as of 5/25/07) of their associated cover images.
threeBooks = ( '0345333926', '0671741926', '0671695320' )
threeImages = ( '21O8IWsJopL.jpg', '214NPWFP3NL.jpg', '21R15WKVYBL.jpg' )
fiveBooks = ( '0688149529', '0671541390', '0914728490',
'0939616173', '0960607013' )
fiveImages = ( '21EDRZPZP0L.jpg', '21VNTBV2SVL.jpg', '21FF9NXVSBL.jpg',
'21CA607WJ1L.jpg', '41W5GRS54AL.gif' )
def main(isbnList=None):
"""main(isbnList) -> None
The idea is, you enter a bunch of ISBN numbers (10 digits, easy to find
on any book with a barcode) and the script runs out to Amazon and finds
out as much as it can about them. It then assembles the text of an ad:
One line saying how many books there are, one line for each book detailing
its title, author and original cost, and a final line saying how much you
want to sell them for. (The latter is based on the total of the books’
used prices.) Next, the script downloads cover art for all of the books
and assembles it into a single image showing the books laid out in a grid.
(This is because craigslist allows no more than four images per posting,
and I often have more than four books in a set.)
"""
if isbnList is None: isbnList = sys.argv
imageUrls = stepOne(isbnList)
imageFiles = stepTwo(imageUrls)
stepThree(imageFiles)
if __name__ == '__main__':
sys.exit(main())
Comments»
No comments yet — be the first.