@@ -225,41 +225,64 @@ def _get_rating(self, product):
225225 return rating
226226
227227
228- def _get_price (self , product ):
229- """Given the HTML for a particular `product`, extract the price"""
228+ def _get_prices (self , product ):
229+ """
230+ Given the HTML for a particular `product`, extract all prices.
231+ """
232+
233+ # match all prices of the form $X,XXX.XX:
234+ raw_prices = product .find_all (text = re .compile ('\$[\d,]+.\d\d' ))
235+
236+ prices = {
237+ 'prices_per_unit' : [float ('nan' )],
238+ 'units' : [None ],
239+ 'prices_main' : [float ('nan' )],
240+ 'prices_more_buying_choices' : [float ('nan' )],
241+ }
230242
231- # match prices of the form $X,XXX.XX.
232- # Note the '<' at the end that distinguishes between list prices and per unit prices
233- # By using the minimum non-zero price, strikethrough prices are ignored
234- prices = re .findall (r'\$([\d,]*.\d\d)<' , str (product ))
243+ # attempt to identify the prices
244+ for raw_price in raw_prices :
235245
236- # convert strings to floats and sort
237- prices = list ( sorted ( map ( float , prices )) )
246+ # get the price as a float rather than a string or BeautifulSoup object
247+ price = float ( re . search ( '\$([\d,]+.\d\d)' , raw_price ). groups ()[ 0 ] )
238248
239- # sometimes a promotional price of zero dolars is returned
240- try :
241- prices .remove (0.0 )
242- except ValueError :
243- pass
249+ # extract "More Buying Choices" price
250+ # import pdb; pdb.set_trace()
244251
245- if not prices :
246- print (f' Failed to extract price!' )
252+ # ignore strikethrough prices used for advertising
253+ if raw_price .parent .parent .attrs .get ('data-a-strike' ) == 'true' :
254+ print (' Price {} discarded as promotional.' .format (raw_price ))
255+ continue
247256
248- return min (prices )
257+ # ignore promotional freebies
258+ elif raw_price == '$0.00' :
259+ print (' Price {} discarded as promotional' .format (raw_price ))
260+ continue
249261
262+ # extract price per unit price and unit
263+ elif raw_price .startswith ('(' ) and '/' in raw_price :
264+ price_per_unit = re .findall (r'/(.*)\)' , raw_price )[0 ]
265+ prices ['prices_per_unit' ].append (price )
266+ prices ['units' ].append (price_per_unit )
250267
251- def _get_unit_price (self , product ):
252- """Given the HTML for a particular `product`, extact the price per unit and the unit"""
268+ # extract price for More Buying Choices
269+ elif raw_price .previous .previous .previous == "More Buying Choices" :
270+ prices ['prices_more_buying_choices' ].append (price )
253271
254- unit_prices = re .findall (r'\(\$([\d,]*.\d\d)/(.*)?\)' , str (product ))
272+ # any other price if hopefully the main price
273+ else :
274+ prices ['prices_main' ].append (price )
255275
256- if len ( unit_prices ) == 0 :
257- return float ( 'nan' ), None
276+ # return just one value for each price, the most recent found
277+ for price_type , price_values in prices . copy (). items ():
258278
259- if len (unit_prices ) > 1 :
260- print ('Taking the first unit price found {}' .format (unit_prices ))
279+ if len (price_values ) > 2 :
280+ print (' Encountered multiple {} and using the last of {}' .format (price_type , price_values ))
261281
262- return float (unit_prices [0 ][0 ]), unit_prices [0 ][1 ]
282+ # take the last value. If no value of was added, this will be NaN or None
283+ prices [price_type ] = price_values [- 1 ]
284+
285+ return prices
263286
264287
265288 def _get_products (self , keywords = "" , search_url = "" , max_product_nb = 100 ):
@@ -321,17 +344,14 @@ def _get_products(self, keywords="", search_url="", max_product_nb=100):
321344 # extract title
322345 product_dict ['title' ] = self ._get_title (product )
323346
324- print ('Extracting {}' .format (product_dict ['title' ]))
347+ print ('Extracting {}' .format (product_dict ['title' ][: 80 ] ))
325348
326349 # extract rating
327350 product_dict ['rating' ] = self ._get_rating (product )
328351
329352 # extract number of ratings
330353 product_dict ['review_nb' ] = self ._get_n_ratings (product )
331354
332- # extract unit price
333- product_dict ['unit_price' ], product_dict ['unit' ] = self ._get_unit_price (product )
334-
335355 # Get image before url and asin
336356 css_selector = css_selector_dict .get ("img" , "" )
337357 img_product_soup = product .select (css_selector )
@@ -376,17 +396,16 @@ def _get_products(self, keywords="", search_url="", max_product_nb=100):
376396 if price : # Doesn't work for ebooks
377397 product_dict ['price' ] = price [0 ].getText ()
378398
379- # use alternate method to extract price
380- if 'price' not in product_dict :
381- product_dict [ 'price' ] = self . _get_price ( product )
399+ # Amazon has many prices associated with a given product
400+ prices = self . _get_prices ( product )
401+ product_dict . update ( prices )
382402
383403 self .product_dict_list .append (product_dict )
384404 # end for loop
385405
386-
406+ # get more products if we haven't reached the limit
387407 if len (self .product_dict_list ) < max_product_nb :
388- # Check if there is another page
389- # only if we have not already reached the max number of products
408+
390409 css_selector = css_selector_dict .get ("next_page_url" , "" )
391410 url_next_page_soup = soup .select (css_selector )
392411 if url_next_page_soup :
0 commit comments