Skip to content

Commit fe4a09c

Browse files
committed
Refactor price extraction code and make price extraction more flexible
1 parent b596ec6 commit fe4a09c

1 file changed

Lines changed: 53 additions & 34 deletions

File tree

amazonscraper/client.py

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)