Source code for sr.tools.bom.rs

"""Routines for scraping data about parts from RS."""
from bs4 import BeautifulSoup
from decimal import Decimal as D

from sr.tools.bom import distpart
from sr.tools.bom.cachedfetch import grab_url_cached


[docs]class Item(distpart.DistItem): """ An item sold by RS. :param part_number: The number of the part. """ def __init__(self, part_number): """Initialise an RS item.""" distpart.DistItem.__init__(self, part_number) self._getinfo() # Haven't come across an RS part for which this shouldn't be 1 self.price_for = 1 # This is not yet supported self.multi = 1 def _getinfo(self): """Load information from the distributor.""" rs_url = 'http://uk.rs-online.com/web/p/products/{part_number}/' page = grab_url_cached(rs_url.format(part_number=self.part_number)) soup = BeautifulSoup(page) if not self._check_exists(soup): raise distpart.NonExistentPart(self.part_number) # Check that the page we've been returned is for the requested part: if not self._soup_check_part(soup): raise distpart.NonExistentPart self._get_availability(soup) # Only get pricing if it's not discontinued if self.avail: self._get_pricing(soup) def _check_exists(self, soup): """Work out whether the part exists based on the soup.""" # Simple test: is this div present? if soup.find(attrs={"class": "keyDetailsDiv"}) is None: return False return True def _cmp_part_numbers(self, a, b): """Return True if the two part numbers are the same.""" a = a.replace("-", "") b = b.replace("-", "") return a == b def _soup_check_part(self, soup): """Work out whether the info we've retrieved is for the right part.""" # This div contains availability kd = soup.find(attrs={"class": "keyDetailsDiv"}) # The label for the stock number field sl = kd.find(attrs={"class": "keyLabel"}, text="RS Stock No.") # And the value for that field sn = sl.find_next(attrs={"class": "keyValue"}).text return self._cmp_part_numbers(self.part_number, sn) def _get_availability(self, soup): """Extract the part availability from the soup.""" av = soup.find(attrs={"class": "instockMessage"}) if "In stock for next working day delivery" in av.text: self.avail = True elif "Discontinued" in av.text: self.avail = False else: self.avail = None def _get_pricing(self, soup): """Extract pricing information from the soup.""" # The pricing table pt = soup.find(attrs={"class": "priceTable"}) prices = [] # There are multiple rows with availability and prices in for row in pt.find("tbody").find_all("tr"): quantity = int(row.find(attrs={"class": "quantity"}).text) ps = row.find(attrs={"class": "unitprice"}).text # The first character is a '£' price = D(ps[1:]) prices.append((quantity, price)) if len(prices): # the minimum order is the smallest quantity from this table self.min_order = prices[0][0] self.prices = prices