Отключить специальную обработку атрибутов класса

История:

При анализе 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 (возможно, немного более явное), но я не думаю, что есть лучший способ сделать это.