Отключить специальную обработку атрибутов класса
История:
При анализе HTML с BeautifulSoup
атрибут class
считается многозначным атрибутом и обрабатывается особым образом
Помните, что один тег может иметь несколько значений для своего атрибута "class". При поиске тега, который соответствует определенному классу CSS, вы сопоставляетесь с любым из его классов CSS.
Кроме того, цитата из встроенного HTMLTreeBuilder
, используемая BeautifulSoup
в качестве основы для других классов построителя дерева, например, HTMLParserTreeBuilder
:
# The HTML standard defines these attributes as containing a
# space-separated list of values, not a single value. That is,
# class="foo bar" means that the 'class' attribute has two values,
# 'foo' and 'bar', not the single value 'foo bar'. When we
# encounter one of these attributes, we will parse its value into
# a list of values if possible. Upon output, the list will be
# converted back into a string.
Вопрос:
Как настроить BeautifulSoup
для обработки class
как обычного однозначного атрибута? Другими словами, я не хочу, чтобы он обрабатывал class
специально и считал его регулярным атрибутом.
FYI, вот один из вариантов использования, когда это может быть полезно:
Что я пробовал:
Я действительно сделал это, выполнив собственный класс строителя деревьев и удалив class
из списка специально обработанных атрибутов:
from bs4.builder._htmlparser import HTMLParserTreeBuilder
class MyBuilder(HTMLParserTreeBuilder):
def __init__(self):
super(MyBuilder, self).__init__()
# BeautifulSoup, please don't treat "class" specially
self.cdata_list_attributes["*"].remove("class")
soup = BeautifulSoup(data, "html.parser", builder=MyBuilder())
Что мне не нравится в этом подходе, так это то, что он довольно "неестественный" и "магический", связанный с импортом "private" internal _htmlparser
. Надеюсь, что есть более простой способ.
ПРИМЕЧАНИЕ. Я хочу сохранить все другие связанные с HTML функции синтаксического разбора, что означает, что я не хочу анализировать HTML
с помощью "xml" - только функции (которые могли быть другим обходным решением).
Ответы
Ответ 1
То, что мне не нравится в этом подходе, заключается в том, что оно довольно "неестественное" и "волшебное", связанное с импортом "private" internal _htmlparser
. Надеюсь, что есть более простой способ.
Да, вы можете импортировать его из bs4.builder
вместо:
from bs4 import BeautifulSoup
from bs4.builder import HTMLParserTreeBuilder
class MyBuilder(HTMLParserTreeBuilder):
def __init__(self):
super(MyBuilder, self).__init__()
# BeautifulSoup, please don't treat "class" as a list
self.cdata_list_attributes["*"].remove("class")
soup = BeautifulSoup(data, "html.parser", builder=MyBuilder())
И если это достаточно важно, чтобы вы не хотели повторять себя, поместите построитель в свой собственный модуль и зарегистрируйте его с помощью register_treebuilders_from()
, чтобы он имел приоритет.
Ответ 2
Класс HTMLParserTreeBuilder
фактически объявлен в верхнем модуле _init__.py
, поэтому нет необходимости напрямую импортировать его из частного подмодуля, Я сказал бы это следующим образом:
import re
from bs4 import BeautifulSoup
from bs4.builder import HTMLParserTreeBuilder
bb = HTMLParserTreeBuilder()
bb.cdata_list_attributes["*"].remove("class")
soup = BeautifulSoup(bs, "html.parser", builder=bb)
found_elements = soup.find_all(class_=re.compile(r"^name\-single name\d+$"))
print found_elements
Это в основном то же самое, что определение класса, как в OP (возможно, немного более явное), но я не думаю, что есть лучший способ сделать это.