BeautifulSoup - recherche par texte à l'intérieur d'un tag
Observer le problème suivant:
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.*")
)
Pour une raison quelconque, BeautifulSoup ne correspond pas au texte, lorsque le <i>
balise est là aussi. Trouver la balise et en montrant son texte produit
>>> a2 = soup.find(
'a',
href="/customer-menu/1/accounts/1/update"
)
>>> print(repr(a2.text))
'\n Edit\n'
Droit. Selon le Docsde la soupe utilise la fonction match de l'expression régulière, et non pas la fonction de recherche. J'ai donc besoin de fournir à l'DOTALL drapeau:
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. Semble bon. Faites un essai avec de la soupe
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?!
Modifier
Ma solution basée sur geckons réponse: j'ai mis en place ces aides:
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]
Maintenant, quand je veux trouver l'élément ci-dessus, je viens de lancer find_by_text(soup, 'Edit', 'a', href='/customer-menu/1/accounts/1/update')
source d'informationauteur Eldamir
Vous devez vous connecter pour publier un commentaire.
Le problème, c'est que votre
<a>
étiquette avec le<i>
balise à l'intérieur, n'a pas lestring
attribut vous vous attendez à avoir. D'abord nous allons jeter un oeil à cetext=""
argument pourfind()
.REMARQUE: Le
text
argument est un nom ancien, depuis BeautifulSoup 4.4.0 il est appeléstring
.De la docs:
Maintenant, nous allons jeter un oeil ce que
Tag
'sstring
attribut est (à partir de la docs nouveau):(...)
C'est exactement votre cas. Votre
<a>
balise contient un texte et<i>
tag. Par conséquent, la recherche devientNone
lorsque vous essayez de rechercher une chaîne de caractères et donc il ne peut pas correspondre.Comment résoudre ce problème?
Peut-être il y a une meilleure solution, mais je serais probablement aller avec quelque chose comme ceci:
Je pense qu'il n'y a pas trop de liens pointant vers
/customer-menu/1/accounts/1/update
donc ça devrait être assez rapide.Vous pouvez passer une function que le retour
True
sia
texte contient "Modifier" pour.find
EDIT:
Vous pouvez utiliser le
.get_text()
méthode au lieu de latext
dans votre fonction qui donne le même résultat:dans une ligne en utilisant lambda