Расширение селекторов CSS в BeautifulSoup
Вопрос:
BeautifulSoup
предоставляет очень ограниченная поддержка селекторы CSS. Например, единственным поддерживаемым псевдоклассом является nth-of-type
и он может принимать только числовые значения - аргументы вроде even
или odd
Не допускается.
можно ли продлить BeautifulSoup
CSS селекторы или пусть он использует lxml.cssselect
внутренне как базовый выбор CSS механизм?
давайте посмотрим на пример проблемы / случай использования. Найдите только четные строки в следующем HTML:
<table>
<tr>
<td>1</td>
<tr>
<td>2</td>
</tr>
<tr>
<td>3</td>
</tr>
<tr>
<td>4</td>
</tr>
</table>
на lxml.html
и lxml.cssselect
, это легко сделать через :nth-of-type(even)
:
from lxml.html import fromstring
from lxml.cssselect import CSSSelector
tree = fromstring(data)
sel = CSSSelector('tr:nth-of-type(even)')
print [e.text_content().strip() for e in sel(tree)]
а, в BeautifulSoup
:
print(soup.select("tr:nth-of-type(even)"))
выдаст ошибку:
NotImplementedError: в настоящее время поддерживаются только числовые значения для nth-типа псевдокласс.
обратите внимание, что мы можем обойти его с .find_all()
:
print([row.get_text(strip=True) for index, row in enumerate(soup.find_all("tr"), start=1) if index % 2 == 0])
2 ответов
после проверки исходного кода, кажется, что BeautifulSoup
не предоставляет какой-либо удобной точки в своем интерфейсе для расширения или исправления существующих функций в этом отношении. Использование функций из lxml
не возможно, поскольку BeautifulSoup
использует только lxml
во время разбора и использует результаты разбора для создания из них собственных соответствующих объектов. The lxml
объекты не сохраняются и не могут быть доступны позже.
это, как говорится, с достаточно определение и с гибкостью и возможностями самоанализа Python, все возможно. Вы можете изменить внутренние компоненты метода BeautifulSoup даже во время выполнения:
import inspect
import re
import textwrap
import bs4.element
def replace_code_lines(source, start_token, end_token,
replacement, escape_tokens=True):
"""Replace the source code between `start_token` and `end_token`
in `source` with `replacement`. The `start_token` portion is included
in the replaced code. If `escape_tokens` is True (default),
escape the tokens to avoid them being treated as a regular expression."""
if escape_tokens:
start_token = re.escape(start_token)
end_token = re.escape(end_token)
def replace_with_indent(match):
indent = match.group(1)
return textwrap.indent(replacement, indent)
return re.sub(r"^(\s+)({}[\s\S]+?)(?=^{})".format(start_token, end_token),
replace_with_indent, source, flags=re.MULTILINE)
# Get the source code of the Tag.select() method
src = textwrap.dedent(inspect.getsource(bs4.element.Tag.select))
# Replace the relevant part of the method
start_token = "if pseudo_type == 'nth-of-type':"
end_token = "else"
replacement = """\
if pseudo_type == 'nth-of-type':
try:
if pseudo_value in ("even", "odd"):
pass
else:
pseudo_value = int(pseudo_value)
except:
raise NotImplementedError(
'Only numeric values, "even" and "odd" are currently '
'supported for the nth-of-type pseudo-class.')
if isinstance(pseudo_value, int) and pseudo_value < 1:
raise ValueError(
'nth-of-type pseudo-class value must be at least 1.')
class Counter(object):
def __init__(self, destination):
self.count = 0
self.destination = destination
def nth_child_of_type(self, tag):
self.count += 1
if pseudo_value == "even":
return not bool(self.count % 2)
elif pseudo_value == "odd":
return bool(self.count % 2)
elif self.count == self.destination:
return True
elif self.count > self.destination:
# Stop the generator that's sending us
# these things.
raise StopIteration()
return False
checker = Counter(pseudo_value).nth_child_of_type
"""
new_src = replace_code_lines(src, start_token, end_token, replacement)
# Compile it and execute it in the target module's namespace
exec(new_src, bs4.element.__dict__)
# Monkey patch the target method
bs4.element.Tag.select = bs4.element.select
этой является частью изменяемого кода.
конечно, это все, но элегантный и надежный. Я не представляю, что это будет всерьез использоваться где-либо, когда-либо.
официально Beautifulsoup не поддерживает все селекторы CSS.
Если python не единственный выбор, я настоятельно рекомендую JSoup (эквивалент java этого). Он поддерживает все селекторы CSS.
- это с открытым исходным кодом (лицензия MIT)
- синтаксис прост
- поддерживает все селекторы css
- может охватывать несколько потоков, чтобы масштабировать
- поддержка Rich API в java для магазин в DBs. Так, легко интегрировать.
другой альтернативный способ, если вы все еще хотите придерживаться python, сделайте его реализацией jython.