source: trunk/amazonbot/my_amazon.py @ 24

Revision 24, 15.4 KB checked in by atzm, 18 years ago (diff)
  • Fixed a bug occured when Amazon returned no valid data
Line 
1"""Python wrapper
2
3
4for Amazon web APIs
5
6This module allows you to access Amazon's web APIs,
7to do things like search Amazon and get the results programmatically.
8Described here:
9  http://www.amazon.com/webservices
10
11You need a Amazon-provided license key to use these services.
12Follow the link above to get one.  These functions will look in
13several places (in this order) for the license key:
14- the "license_key" argument of each function
15- the module-level LICENSE_KEY variable (call setLicense once to set it)
16- an environment variable called AMAZON_LICENSE_KEY
17- a file called ".amazonkey" in the current directory
18- a file called "amazonkey.txt" in the current directory
19- a file called ".amazonkey" in your home directory
20- a file called "amazonkey.txt" in your home directory
21- a file called ".amazonkey" in the same directory as amazon.py
22- a file called "amazonkey.txt" in the same directory as amazon.py
23
24Sample usage:
25>>> import amazon
26>>> amazon.setLicense('...') # must get your own key!
27>>> pythonBooks = amazon.searchByKeyword('Python')
28>>> pythonBooks[0].ProductName
29u'Learning Python (Help for Programmers)'
30>>> pythonBooks[0].URL
31...
32>>> pythonBooks[0].OurPrice
33...
34
35Other available functions:
36- browseBestSellers
37- searchByASIN
38- searchByUPC
39- searchByAuthor
40- searchByArtist
41- searchByActor
42- searchByDirector
43- searchByManufacturer
44- searchByListMania
45- searchSimilar
46- searchByWishlist
47
48Other usage notes:
49- Most functions can take product_line as well, see source for possible values
50- All functions can take type="lite" to get less detail in results
51- All functions can take page=N to get second, third, fourth page of results
52- All functions can take license_key="XYZ", instead of setting it globally
53- All functions can take http_proxy="http://x/y/z" which overrides your system setting
54"""
55
56__author__ = "Mark Pilgrim (f8dy@diveintomark.org)"
57__version__ = "0.64.1"
58__cvsversion__ = "$Revision: 1.12 $"[11:-2]
59__date__ = "$Date: 2004/07/02 13:24:09 $"[7:-2]
60__copyright__ = "Copyright (c) 2002 Mark Pilgrim"
61__license__ = "Python"
62# Powersearch and return object type fix by Joseph Reagle <geek@goatee.net>
63
64# Locale support by Michael Josephson <mike@josephson.org>
65
66# Modification to _contentsOf to strip trailing whitespace when loading Amazon key
67# from a file submitted by Patrick Phalen.
68
69# Support for specifying locale and associates ID as search parameters and
70# internationalisation fix for the SalesRank integer conversion by
71# Christian Theune <ct@gocept.com>, gocept gmbh & co. kg
72
73# Support for BlendedSearch contributed by Alex Choo
74
75from xml.dom import minidom
76import os, sys, getopt, cgi, urllib, string
77try:
78    import timeoutsocket # http://www.timo-tasi.org/python/timeoutsocket.py
79    timeoutsocket.setDefaultSocketTimeout(10)
80except ImportError:
81    pass
82
83LICENSE_KEY = None
84ASSOCIATE = "webservices-20"
85HTTP_PROXY = None
86LOCALE = "us"
87
88# don't touch the rest of these constants
89class AmazonError(Exception): pass
90class NoLicenseKey(Exception): pass
91_amazonfile1 = ".amazonkey"
92_amazonfile2 = "amazonkey.txt"
93_licenseLocations = (
94    (lambda key: key, 'passed to the function in license_key variable'),
95    (lambda key: LICENSE_KEY, 'module-level LICENSE_KEY variable (call setLicense to set it)'),
96    (lambda key: os.environ.get('AMAZON_LICENSE_KEY', None), 'an environment variable called AMAZON_LICENSE_KEY'),
97    (lambda key: _contentsOf(os.getcwd(), _amazonfile1), '%s in the current directory' % _amazonfile1),
98    (lambda key: _contentsOf(os.getcwd(), _amazonfile2), '%s in the current directory' % _amazonfile2),
99    (lambda key: _contentsOf(os.environ.get('HOME', ''), _amazonfile1), '%s in your home directory' % _amazonfile1),
100    (lambda key: _contentsOf(os.environ.get('HOME', ''), _amazonfile2), '%s in your home directory' % _amazonfile2),
101    (lambda key: _contentsOf(_getScriptDir(), _amazonfile1), '%s in the amazon.py directory' % _amazonfile1),
102    (lambda key: _contentsOf(_getScriptDir(), _amazonfile2), '%s in the amazon.py directory' % _amazonfile2)
103    )
104_supportedLocales = {
105        "us" : (None, "xml.amazon.com"),   
106        "uk" : ("uk", "xml-eu.amazon.com"),
107        "de" : ("de", "xml-eu.amazon.com"),
108        "jp" : ("jp", "xml.amazon.co.jp")
109    }
110
111## administrative functions
112def version():
113    print """PyAmazon %(__version__)s
114%(__copyright__)s
115released %(__date__)s
116""" % globals()
117
118def setAssociate(associate):
119    global ASSOCIATE
120    ASSOCIATE=associate
121
122def getAssociate(override=None):
123    return override or ASSOCIATE
124
125## utility functions
126
127def _checkLocaleSupported(locale):
128    if not _supportedLocales.has_key(locale):
129        raise AmazonError, ("Unsupported locale. Locale must be one of: %s" %
130            string.join(_supportedLocales, ", "))
131
132def setLocale(locale):
133    """set locale"""
134    global LOCALE
135    _checkLocaleSupported(locale)
136    LOCALE = locale
137
138def getLocale(locale=None):
139    """get locale"""
140    return locale or LOCALE
141
142def setLicense(license_key):
143    """set license key"""
144    global LICENSE_KEY
145    LICENSE_KEY = license_key
146
147def getLicense(license_key = None):
148    """get license key
149
150    license key can come from any number of locations;
151    see module docs for search order"""
152    for get, location in _licenseLocations:
153        rc = get(license_key)
154        if rc: return rc
155    raise NoLicenseKey, 'get a license key at http://www.amazon.com/webservices'
156
157def setProxy(http_proxy):
158    """set HTTP proxy"""
159    global HTTP_PROXY
160    HTTP_PROXY = http_proxy
161
162def getProxy(http_proxy = None):
163    """get HTTP proxy"""
164    return http_proxy or HTTP_PROXY
165
166def getProxies(http_proxy = None):
167    http_proxy = getProxy(http_proxy)
168    if http_proxy:
169        proxies = {"http": http_proxy}
170    else:
171        proxies = None
172    return proxies
173
174def _contentsOf(dirname, filename):
175    filename = os.path.join(dirname, filename)
176    if not os.path.exists(filename): return None
177    fsock = open(filename)
178    contents =  fsock.read().strip()
179    fsock.close()
180    return contents
181
182def _getScriptDir():
183    if __name__ == '__main__':
184        return os.path.abspath(os.path.dirname(sys.argv[0]))
185    else:
186        return os.path.abspath(os.path.dirname(sys.modules[__name__].__file__))
187
188class Bag: pass
189
190def unmarshal(element):
191    rc = Bag()
192    if isinstance(element, minidom.Element) and (element.tagName == 'Details'):
193        rc.URL = element.attributes["url"].value
194    childElements = [e for e in element.childNodes if isinstance(e, minidom.Element)]
195    if childElements:
196        for child in childElements:
197            key = child.tagName
198            if hasattr(rc, key):
199                if type(getattr(rc, key)) <> type([]):
200                    setattr(rc, key, [getattr(rc, key)])
201                setattr(rc, key, getattr(rc, key) + [unmarshal(child)])
202            elif isinstance(child, minidom.Element) and (child.tagName == 'Details'):
203                # make the first Details element a key
204                setattr(rc,key,[unmarshal(child)])
205                #dbg: because otherwise 'hasattr' only tests
206                #dbg: on the second occurence: if there's a
207                #dbg: single return to a query, it's not a
208                #dbg: list. This module should always
209                #dbg: return a list of Details objects.
210            else:
211                setattr(rc, key, unmarshal(child))
212    else:
213        rc = "".join([e.data for e in element.childNodes if isinstance(e, minidom.Text)])
214        if element.tagName == 'SalesRank':
215            rc = rc.replace('.', '')
216            rc = rc.replace(',', '')
217            rc = int(rc)
218    return rc
219
220def buildURL(search_type, keyword, product_line, type, page, license_key, locale, associate):
221    _checkLocaleSupported(locale)
222    url = "http://" + _supportedLocales[locale][1] + "/onca/xml3?f=xml"
223    url += "&t=%s" % associate
224    url += "&dev-t=%s" % license_key.strip()
225    url += "&type=%s" % type
226    if _supportedLocales[locale][0]:
227        url += "&locale=%s" % _supportedLocales[locale][0]
228    if page:
229        url += "&page=%s" % page
230    if product_line:
231        url += "&mode=%s" % product_line
232    url += "&%s=%s" % (search_type, urllib.quote(keyword))
233    return url
234
235
236## main functions
237
238
239def search(search_type, keyword, product_line, type = "heavy", page = None,
240           license_key=None, http_proxy = None, locale = None, associate = None):
241    """search Amazon
242
243    You need a license key to call this function; see
244    http://www.amazon.com/webservices
245    to get one.  Then you can either pass it to
246    this function every time, or set it globally; see the module docs for details.
247
248    Parameters:
249    keyword - keyword to search
250    search_type - in (KeywordSearch, BrowseNodeSearch, AsinSearch, UpcSearch, AuthorSearch, ArtistSearch, ActorSearch, DirectorSearch, ManufacturerSearch, ListManiaSearch, SimilaritySearch)
251    product_line - type of product to search for.  restrictions based on search_type
252        UpcSearch - in (music, classical)
253        AuthorSearch - must be "books"
254        ArtistSearch - in (music, classical)
255        ActorSearch - in (dvd, vhs, video)
256        DirectorSearch - in (dvd, vhs, video)
257        ManufacturerSearch - in (electronics, kitchen, videogames, software, photo, pc-hardware)
258    http_proxy (optional) - address of HTTP proxy to use for sending and receiving SOAP messages
259
260    Returns: list of Bags, each Bag may contain the following attributes:
261      Asin - Amazon ID ("ASIN" number) of this item
262      Authors - list of authors
263      Availability - "available", etc.
264      BrowseList - list of related categories
265      Catalog - catalog type ("Book", etc)
266      CollectiblePrice - ?, format "$34.95"
267      ImageUrlLarge - URL of large image of this item
268      ImageUrlMedium - URL of medium image of this item
269      ImageUrlSmall - URL of small image of this item
270      Isbn - ISBN number
271      ListPrice - list price, format "$34.95"
272      Lists - list of ListMania lists that include this item
273      Manufacturer - manufacturer
274      Media - media ("Paperback", "Audio CD", etc)
275      NumMedia - number of different media types in which this item is available
276      OurPrice - Amazon price, format "$24.47"
277      ProductName - name of this item
278      ReleaseDate - release date, format "09 April, 1999"
279      Reviews - reviews (AvgCustomerRating, plus list of CustomerReview with Rating, Summary, Content)
280      SalesRank - sales rank (integer)
281      SimilarProducts - list of Product, which is ASIN number
282      ThirdPartyNewPrice - ?, format "$34.95"
283      URL - URL of this item
284    """
285    license_key = getLicense(license_key)
286    locale = getLocale(locale)
287    associate = getAssociate(associate)
288    url = buildURL(search_type, keyword, product_line, type, page, 
289            license_key, locale, associate)
290    proxies = getProxies(http_proxy)
291    u = urllib.FancyURLopener(proxies)
292    usock = u.open(url)
293    xmldoc = None
294    try:
295        xmldoc = minidom.parse(usock)
296    except:
297        raise AmazonError, 'XML Parsing Error'
298    if xmldoc is None:
299        raise AmazonError, 'XML Parsing Error (Result is None)'
300
301#     from xml.dom.ext import PrettyPrint
302#     PrettyPrint(xmldoc)
303
304    usock.close()
305    if search_type == "BlendedSearch":
306        data = unmarshal(xmldoc).BlendedSearch
307    else:   
308        data = unmarshal(xmldoc).ProductInfo       
309       
310    if hasattr(data, 'ErrorMsg'):
311        raise AmazonError, data.ErrorMsg
312    else:
313        #if search_type == "BlendedSearch":
314            # a list of ProductLine containing a list of ProductInfo
315            # containing a list of Details.
316        return data
317        #else:           
318        #    return data.Details
319
320def searchByKeyword(keyword, product_line="books", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
321    return search("KeywordSearch", keyword, product_line, type, page, license_key, http_proxy, locale, associate)
322
323def browseBestSellers(browse_node, product_line="books", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
324    return search("BrowseNodeSearch", browse_node, product_line, type, page, license_key, http_proxy, locale, associate)
325
326def searchByASIN(ASIN, type="heavy", license_key=None, http_proxy=None, locale=None, associate=None):
327    return search("AsinSearch", ASIN, None, type, None, license_key, http_proxy, locale, associate)
328
329def searchByUPC(UPC, type="heavy", license_key=None, http_proxy=None, locale=None, associate=None):
330    return search("UpcSearch", UPC, None, type, None, license_key, http_proxy, locale, associate)
331
332def searchByAuthor(author, type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
333    return search("AuthorSearch", author, "books", type, page, license_key, http_proxy, locale, associate)
334
335def searchByArtist(artist, product_line="music", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
336    if product_line not in ("music", "classical"):
337        raise AmazonError, "product_line must be in ('music', 'classical')"
338    return search("ArtistSearch", artist, product_line, type, page, license_key, http_proxy, locale, associate)
339
340def searchByActor(actor, product_line="dvd", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
341    if product_line not in ("dvd", "vhs", "video"):
342        raise AmazonError, "product_line must be in ('dvd', 'vhs', 'video')"
343    return search("ActorSearch", actor, product_line, type, page, license_key, http_proxy, locale, associate)
344
345def searchByDirector(director, product_line="dvd", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
346    if product_line not in ("dvd", "vhs", "video"):
347        raise AmazonError, "product_line must be in ('dvd', 'vhs', 'video')"
348    return search("DirectorSearch", director, product_line, type, page, license_key, http_proxy, locale, associate)
349
350def searchByManufacturer(manufacturer, product_line="pc-hardware", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
351    if product_line not in ("electronics", "kitchen", "videogames", "software", "photo", "pc-hardware"):
352        raise AmazonError, "product_line must be in ('electronics', 'kitchen', 'videogames', 'software', 'photo', 'pc-hardware')"
353    return search("ManufacturerSearch", manufacturer, product_line, type, page, license_key, http_proxy, locale, associate)
354
355def searchByListMania(listManiaID, type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
356    return search("ListManiaSearch", listManiaID, None, type, page, license_key, http_proxy, locale, associate)
357
358def searchSimilar(ASIN, type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
359    return search("SimilaritySearch", ASIN, None, type, page, license_key, http_proxy, locale, associate)
360
361def searchByWishlist(wishlistID, type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
362    return search("WishlistSearch", wishlistID, None, type, page, license_key, http_proxy, locale, associate)
363
364def searchByPower(keyword, product_line="books", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
365    return search("PowerSearch", keyword, product_line, type, page, license_key, http_proxy, locale, associate)
366    # >>> RecentKing = amazon.searchByPower('author:Stephen King and pubdate:2003')
367    # >>> SnowCrash = amazon.searchByPower('title:Snow Crash')
368
369def searchByBlended(keyword, type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
370    return search("BlendedSearch", keyword, None, type, page, license_key, http_proxy, locale, associate)
Note: See TracBrowser for help on using the repository browser.