from examples example beautifulsoup4 python regex beautifulsoup

python - examples - BeautifulSoup: busca por texto dentro de una etiqueta



install beautifulsoup python 3 (3)

Observe el siguiente problema:

import re from bs4 import BeautifulSoup as BS soup = BS(""" <a href="/customer-menu/1/accounts/1/update"> Edit </a> """) # This returns the <a> element soup.find( ''a'', href="/customer-menu/1/accounts/1/update", text=re.compile(".*Edit.*") ) soup = BS(""" <a href="/customer-menu/1/accounts/1/update"> <i class="fa fa-edit"></i> Edit </a> """) # This returns None soup.find( ''a'', href="/customer-menu/1/accounts/1/update", text=re.compile(".*Edit.*") )

Por alguna razón, BeautifulSoup no coincidirá con el texto, cuando la etiqueta <i> esté allí. Encontrar la etiqueta y mostrar su texto produce

>>> a2 = soup.find( ''a'', href="/customer-menu/1/accounts/1/update" ) >>> print(repr(a2.text)) ''/n Edit/n''

Derecha. Según los Docs , la sopa utiliza la función de coincidencia de la expresión regular, no la función de búsqueda. Entonces necesito proporcionar la bandera DOTALL:

pattern = re.compile(''.*Edit.*'') pattern.match(''/n Edit/n'') # Returns None pattern = re.compile(''.*Edit.*'', flags=re.DOTALL) pattern.match(''/n Edit/n'') # Returns MatchObject

Bien. Se ve bien. Probémoslo con sopa

soup = BS(""" <a href="/customer-menu/1/accounts/1/update"> <i class="fa fa-edit"></i> Edit </a> """) soup.find( ''a'', href="/customer-menu/1/accounts/1/update", text=re.compile(".*Edit.*", flags=re.DOTALL) ) # Still return None... Why?!

Editar

Mi solución basada en geckons responde: implementé estos ayudantes:

import re MATCH_ALL = r''.*'' def like(string): """ Return a compiled regular expression that matches the given string with any prefix and postfix, e.g. if string = "hello", the returned regex matches r".*hello.*" """ string_ = string if not isinstance(string_, str): string_ = str(string_) regex = MATCH_ALL + re.escape(string_) + MATCH_ALL return re.compile(regex, flags=re.DOTALL) def find_by_text(soup, text, tag, **kwargs): """ Find the tag in soup that matches all provided kwargs, and contains the text. If no match is found, return None. If more than one match is found, raise ValueError. """ elements = soup.find_all(tag, **kwargs) matches = [] for element in elements: if element.find(text=like(text)): matches.append(element) if len(matches) > 1: raise ValueError("Too many matches:/n" + "/n".join(matches)) elif len(matches) == 0: return None else: return matches[0]

Ahora, cuando quiero encontrar el elemento anterior, solo ejecuto find_by_text(soup, ''Edit'', ''a'', href=''/customer-menu/1/accounts/1/update'')


El problema es que su etiqueta <a> con la etiqueta <i> dentro, no tiene el atributo de string que espera que tenga. Primero echemos un vistazo a lo que hace el argumento text="" para find() .

NOTA: El argumento de text es un nombre antiguo, ya que BeautifulSoup 4.4.0 se llama string .

De los docs :

Aunque la cadena es para encontrar cadenas, puede combinarla con argumentos que busquen etiquetas: Beautiful Soup encontrará todas las etiquetas cuya cadena coincida con su valor para la cadena. Este código encuentra las etiquetas cuyo .string es "Elsie":

soup.find_all("a", string="Elsie") # [<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>]

Ahora echemos un vistazo a cuál es el atributo de string Tag (de los docs nuevamente):

Si una etiqueta tiene solo un elemento secundario y ese elemento secundario es NavigableString, el elemento secundario estará disponible como .string:

title_tag.string # u''The Dormouse''s story''

(...)

Si una etiqueta contiene más de una cosa, entonces no está claro a qué debe referirse .string, por lo que .string se define como Ninguno:

print(soup.html.string) # None

Este es exactamente tu caso. Su etiqueta <a> contiene un texto y una etiqueta <i> . Por lo tanto, la búsqueda obtiene None cuando intenta buscar una cadena y, por lo tanto, no puede coincidir.

¿Cómo resolver esto?

Quizás haya una mejor solución, pero probablemente elegiría algo como esto:

import re from bs4 import BeautifulSoup as BS soup = BS(""" <a href="/customer-menu/1/accounts/1/update"> <i class="fa fa-edit"></i> Edit </a> """) links = soup.find_all(''a'', href="/customer-menu/1/accounts/1/update") for link in links: if link.find(text=re.compile("Edit")): thelink = link break print(thelink)

Creo que no hay demasiados enlaces que apuntan a /customer-menu/1/accounts/1/update por lo que debería ser lo suficientemente rápido.


Puede pasar una function que devuelva True si a texto contiene "Editar" para .find

def Edit_in_text(tag): return tag.name == ''a'' and ''Edit'' in tag.get_text()

EDITAR:

Puede usar el método .get_text() lugar del text en su función que da el mismo resultado:

soup.find(lambda tag:tag.name=="a" and "Edit" in tag.text)


en una línea usando lambda

In [51]: def Edit_in_text(tag): ....: return tag.name == ''a'' and ''Edit'' in tag.text ....: In [52]: soup.find(Edit_in_text, href="/customer-menu/1/accounts/1/update") Out[52]: <a href="/customer-menu/1/accounts/1/update"> <i class="fa fa-edit"></i> Edit </a>