Ну можно и через лямбды, но - имеет ли это смысл? В чем будет выигрыш по сравнению с простым try except?
Будет менее читаемо, по-моему.
К сожалению, суп не поддерживает из коробки XPath - эта штука позволяла бы задать селектор как в примере одной строкой вида
//li[contains-token(@class, 'CardInfoRow_year')]//span[contains-token(@class, 'CardInfoRow__cell')][1]/a[contains-token(@class, 'link')]
Можно наколхозить грубое подобие:
class BSItem: # одиночный элемент
def __init__(self, item):
self._item = start
def __bool__(self):
return bool(self._item)
def __str__(self):
return self._item.contents[0] if self else ''
@property
def tag(self):
return self._item
def __truediv__(self, other): # оператор / ищет один элемент
if not self: # пустой элемент так и останется пустым
return self
if isinstance(other, str): # item / 'tag.classname'
tag, _, cls = other.partition('.')
if cls:
return BSItem(self._item.find(tag, class_=cls))
else:
return BSItem(self._item.find(tag))
if isinstance(other, int) and other == 0: # item / 0 == item
return self
if callable(other): # item / lambda tag: tag.has_attr('id')
return BSItem(self._item.find(other))
raise ValueError() # передали ерунду
def __floordiv__(self, other): # оператор // ищет все элементы
if not self:
return BSItems([])
if isinstance(other, str): # item // 'tag.classname'
tag, _, cls = other.partition('.')
if cls:
return BSItems(self._item.find_all(tag, class_=cls))
else:
return BSItems(self._item.find_all(tag))
if callable(other): # item // lambda tag: tag.has_attr('id')
return BSItems(self._item.find_all(other))
raise ValueError()
class BSItems: # коллекция элементов
def __init__(self, items):
self._items = items
def __bool__(self):
return bool(self._items)
def __iter__(self): #позволяет делать for tag in BSItems:
return iter(self._items)
def __len__(self):
return len(self._items)
def __truediv__(self, other): # оператор / ищет один элемент в каждом элементе
if not self: # пустой элемент так и останется пустым
return self
if isinstance(other, str): # item / 'tag.classname'
tag, _, cls = other.partition('.')
if cls:
return BSItems([item.find(tag, class_=cls) for item in self._items])
else:
return BSItem([item.find(tag) for item in self._items])
if isinstance(other, int): # items / 2 найдет третий элемент в коллекции
return BSItem(self._items[other]) if len(self._items) > other else BSItem(None)
if callable(other): # items / lambda tag: tag.has_attr('id')
return BSItems([item.find(other) for item in self._items])
raise ValueError() # передали ерунду
def __floordiv__(self, other): # оператор // ищет все элементы
if not self:
return self
if isinstance(other, str): # item // 'tag.classname'
tag, _, cls = other.partition('.')
result = []
for item in self._items:
result.extend(item.find_all(tag, class_=cls) if cls else item.find_all(tag))
return BSItems(result)
if callable(other): # item // lambda tag: tag.has_attr('id')
result = []
for item in self._items:
result.extend(item.find_all(other))
return BSItems(result)
raise ValueError()
За точность кода не ручаюсь, но идею должен передать.
Пример использования будет примерно такой:
year_tag = BSItem(soup) / 'li.CardInfoRow_year' // 'span.CardInfoRow__cell' / 1 / 'a.Link'
year = str(year_tag) or '-'