python - BeautifulSoup devuelve una lista vacía al buscar por nombres de clase compuestos
regex python-2.7 (2)
BeautifulSoup devuelve una lista vacía cuando busca por nombres de clase compuestos usando regex.
Ejemplo:
import re
from bs4 import BeautifulSoup
bs =
"""
<a class="name-single name692" href="www.example.com"">Example Text</a>
"""
bsObj = BeautifulSoup(bs)
# this returns the class
found_elements = bsObj.find_all("a", class_= re.compile("^(name-single.*)$"))
# this returns an empty list
found_elements = bsObj.find_all("a", class_= re.compile("^(name-single name/d*)$"))
Necesito que la selección de clase sea muy precisa. ¿Algunas ideas?
Desafortunadamente, cuando intenta hacer una coincidencia de expresión regular en un valor de atributo de clase que contiene varias clases,
BeautifulSoup
aplicaría la expresión regular a cada clase por separado.
Estos son los temas relevantes sobre el problema:
- Expresión regular de Python para Beautiful Soup
- La búsqueda múltiple de clases de CSS es desagradable
Todo esto se debe a que la
class
es un atributo de valores múltiples muy especial
y cada vez que analiza HTML, uno de los constructores de árboles de
BeautifulSoup
(según la elección del analizador) divide internamente un valor de cadena de clase en una lista de clases (cita de
HTMLTreeBuilder
de HTMLTreeBuilder):
# 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.
Hay varias soluciones alternativas, pero aquí hay una alternativa: vamos a pedirle a
BeautifulSoup
no maneje la
class
como un atributo de valores múltiples al hacer nuestro simple generador de árbol personalizado:
import re
from bs4 import BeautifulSoup
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")
bs = """<a class="name-single name692" href="www.example.com"">Example Text</a>"""
bsObj = BeautifulSoup(bs, "html.parser", builder=MyBuilder())
found_elements = bsObj.find_all("a", class_=re.compile(r"^name/-single name/d+$"))
print(found_elements)
En este caso, la expresión regular se aplicaría a un valor de atributo de
class
como un todo.
Alternativamente, puede analizar el HTML con las funciones
xml
habilitadas (si esto es aplicable):
soup = BeautifulSoup(data, "xml")
También puede usar
selectores CSS
y hacer coincidir todos los elementos con
name-single
clase de
name-single
y una clase con "nombre":
soup.select("a.name-single,a[class^=name]")
Luego puede aplicar la expresión regular manualmente si es necesario:
pattern = re.compile(r"^name-single name/d+$")
for elm in bsObj.select("a.name-single,a[class^=name]"):
match = pattern.match(" ".join(elm["class"]))
if match:
print(elm)
Para este caso de uso, simplemente usaría un filtro personalizado , así:
import re
from bs4 import BeautifulSoup
from bs4.builder._htmlparser import HTMLParserTreeBuilder
def myclassfilter(tag):
return re.compile(r"^name/-single name/d+$").search('' ''.join(tag[''class'']))
bs = """<a class="name-single name692" href="www.example.com"">Example Text</a>"""
bsObj = BeautifulSoup(bs, "html.parser")
found_elements = bsObj.find_all(myclassfilter)
print(found_elements)