J'utilise Python et BeautifulSoup pour le web scraping.
Disons que j'ai le code html suivant à gratter:
<body>
<div class="product">Product 1</div>
<div class="product">Product 2</div>
<div class="product special">Product 3</div>
<div class="product special">Product 4</div>
</body>
En utilisant BeautifulSoup, je veux trouver UNIQUEMENT les produits avec l'attribut class = "product" (uniquement les produits 1 et 2), pas les produits "spéciaux"
Si je fais ce qui suit:
result = soup.find_all('div', {'class': 'product'})
le résultat inclut TOUS les produits (1, 2, 3 et 4).
Que dois-je faire pour trouver des produits dont la classe correspond exactement à "produit" ??
Le code que j'ai exécuté:
from bs4 import BeautifulSoup
import re
text = """
<body>
<div class="product">Product 1</div>
<div class="product">Product 2</div>
<div class="product special">Product 3</div>
<div class="product special">Product 4</div>
</body>"""
soup = BeautifulSoup(text)
result = soup.findAll(attrs={'class': re.compile(r"^product$")})
print result
Production:
[<div class="product">Product 1</div>, <div class="product">Product 2</div>, <div class="product special">Product 3</div>, <div class="product special">Product 4</div>]
Dans BeautifulSoup 4, l'attribut class
(et plusieurs autres attributs, tels que accesskey
et l'attribut headers
sur les éléments de cellule de tableau) est traité comme un ensemble; vous comparez avec les éléments individuels répertoriés dans l'attribut. Cela suit la norme HTML.
En tant que tel, vous ne pouvez pas limiter la recherche à une seule classe.
Vous devrez utiliser une fonction personnalisée ici pour faire correspondre la classe à la place:
result = soup.find_all(lambda tag: tag.name == 'div' and
tag.get('class') == ['product'])
J'ai utilisé un lambda
pour créer une fonction anonyme; chaque balise correspond au nom (doit être 'div'
), et l'attribut class doit être exactement égal à la liste ['product']
; par exemple. avoir une seule valeur.
Démo:
>>> from bs4 import BeautifulSoup
>>> text = """
... <body>
... <div class="product">Product 1</div>
... <div class="product">Product 2</div>
... <div class="product special">Product 3</div>
... <div class="product special">Product 4</div>
... </body>"""
>>> soup = BeautifulSoup(text)
>>> soup.find_all(lambda tag: tag.name == 'div' and tag.get('class') == ['product'])
[<div class="product">Product 1</div>, <div class="product">Product 2</div>]
Par souci d'exhaustivité, voici tous ces attributs d'ensemble, à partir du code source BeautifulSoup:
# 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.
cdata_list_attributes = {
"*" : ['class', 'accesskey', 'dropzone'],
"a" : ['rel', 'rev'],
"link" : ['rel', 'rev'],
"td" : ["headers"],
"th" : ["headers"],
"td" : ["headers"],
"form" : ["accept-charset"],
"object" : ["archive"],
# These are HTML5 specific, as are *.accesskey and *.dropzone above.
"area" : ["rel"],
"icon" : ["sizes"],
"iframe" : ["sandbox"],
"output" : ["for"],
}
soup.findAll(attrs={'class': re.compile(r"^product$")})
Ce code correspond à tout ce qui n'a pas le product
à la fin de sa classe.
Vous pouvez utiliser des sélecteurs CSS comme ceci:
result = soup.select('div.product.special')