web-dev-qa-db-fra.com

Supprimer une balise à l'aide de BeautifulSoup mais conserver son contenu

Actuellement, j'ai du code qui fait quelque chose comme ceci:

soup = BeautifulSoup(value)

for tag in soup.findAll(True):
    if tag.name not in VALID_TAGS:
        tag.extract()
soup.renderContents()

Sauf que je ne veux pas jeter le contenu à l'intérieur de la balise invalide. Comment puis-je me débarrasser de la balise mais garder le contenu à l'intérieur lors de l'appel de soup.renderContents ()?

47
Jason Christa

La stratégie que j'ai utilisée est de remplacer une balise par son contenu si elle est de type NavigableString et si ce n'est pas le cas, puis de la recréer et de remplacer son contenu par NavigableString, etc. Essayez ceci :

from BeautifulSoup import BeautifulSoup, NavigableString

def strip_tags(html, invalid_tags):
    soup = BeautifulSoup(html)

    for tag in soup.findAll(True):
        if tag.name in invalid_tags:
            s = ""

            for c in tag.contents:
                if not isinstance(c, NavigableString):
                    c = strip_tags(unicode(c), invalid_tags)
                s += unicode(c)

            tag.replaceWith(s)

    return soup

html = "<p>Good, <b>bad</b>, and <i>ug<b>l</b><u>y</u></i></p>"
invalid_tags = ['b', 'i', 'u']
print strip_tags(html, invalid_tags)

Le résultat est:

<p>Good, bad, and ugly</p>

J'ai donné cette même réponse sur une autre question. Cela semble beaucoup.

55
Jesse Dhillon

Les versions actuelles de la bibliothèque BeautifulSoup ont une méthode non documentée sur les objets Tag appelée replaceWithChildren (). Vous pouvez donc faire quelque chose comme ceci:

html = "<p>Good, <b>bad</b>, and <i>ug<b>l</b><u>y</u></i></p>"
invalid_tags = ['b', 'i', 'u']
soup = BeautifulSoup(html)
for tag in invalid_tags: 
    for match in soup.findAll(tag):
        match.replaceWithChildren()
print soup

On dirait qu'il se comporte comme vous le souhaitez et qu'il s'agit d'un code assez simple (bien qu'il fasse quelques passages dans le DOM, mais cela pourrait facilement être optimisé.)

67
slacy

Bien que cela ait déjà été mentionné par d'autres personnes dans les commentaires, j'ai pensé publier une réponse complète montrant comment le faire avec Mozilla's Bleach. Personnellement, je pense que c'est beaucoup plus agréable que d'utiliser BeautifulSoup pour cela.

import bleach
html = "<b>Bad</b> <strong>Ugly</strong> <script>Evil()</script>"
clean = bleach.clean(html, tags=[], strip=True)
print clean # Should print: "Bad Ugly Evil()"
17
corford

J'ai une solution plus simple mais je ne sais pas s'il y a un inconvénient.

MISE À JOUR: il y a un inconvénient, voir le commentaire de Jesse Dhillon. De plus, une autre solution sera d'utiliser Bleach de Mozilla au lieu de BeautifulSoup.

from BeautifulSoup import BeautifulSoup

VALID_TAGS = ['div', 'p']

value = '<div><p>Hello <b>there</b> my friend!</p></div>'

soup = BeautifulSoup(value)

for tag in soup.findAll(True):
    if tag.name not in VALID_TAGS:
        tag.replaceWith(tag.renderContents())

print soup.renderContents()

Cela imprimera également <div><p>Hello there my friend!</p></div> comme voulu.

10
Etienne

vous pouvez utiliser soup.text

.text supprime toutes les balises et concatène tout le texte.

8
jimmy

Vous devrez probablement déplacer les enfants du tag pour qu'ils soient les enfants du parent du tag avant de supprimer le tag - c'est ce que vous voulez dire?

Si c'est le cas, alors, alors que l'insertion du contenu au bon endroit est délicate, quelque chose comme ça devrait fonctionner:

from BeautifulSoup import BeautifulSoup

VALID_TAGS = 'div', 'p'

value = '<div><p>Hello <b>there</b> my friend!</p></div>'

soup = BeautifulSoup(value)

for tag in soup.findAll(True):
    if tag.name not in VALID_TAGS:
        for i, x in enumerate(tag.parent.contents):
          if x == tag: break
        else:
          print "Can't find", tag, "in", tag.parent
          continue
        for r in reversed(tag.contents):
          tag.parent.insert(i, r)
        tag.extract()
print soup.renderContents()

avec l'exemple de valeur, cela imprime <div><p>Hello there my friend!</p></div> comme voulu.

6
Alex Martelli

Utilisez déballer.

Déballer supprimera une occurrence multiple de la balise et conservera toujours le contenu.

Exemple:

>> soup = BeautifulSoup('Hi. This is a <nobr> nobr </nobr>')
>> soup
<html><body><p>Hi. This is a <nobr> nobr </nobr></p></body></html>
>> soup.nobr.unwrap
<nobr></nobr>
>> soup
>> <html><body><p>Hi. This is a nobr </p></body></html>
2
Bishwas Mishra

Aucune des réponses proposées ne semblait fonctionner avec BeautifulSoup pour moi. Voici une version qui fonctionne avec BeautifulSoup 3.2.1, et insère également un espace lors de la jonction de contenu à partir de différentes balises au lieu de concaténer des mots.

def strip_tags(html, whitelist=[]):
    """
    Strip all HTML tags except for a list of whitelisted tags.
    """
    soup = BeautifulSoup(html)

    for tag in soup.findAll(True):
        if tag.name not in whitelist:
            tag.append(' ')
            tag.replaceWithChildren()

    result = unicode(soup)

    # Clean up any repeated spaces and spaces like this: '<a>test </a> '
    result = re.sub(' +', ' ', result)
    result = re.sub(r' (<[^>]*> )', r'\1', result)
    return result.strip()

Exemple:

strip_tags('<h2><a><span>test</span></a> testing</h2><p>again</p>', ['a'])
# result: u'<a>test</a> testing again'
2
Olof Sjöbergh

Voici la meilleure solution sans tracas et code passe-partout pour filtrer les balises en gardant le contenu. Disons que vous souhaitez supprimer toutes les balises enfants dans la balise parent et que vous souhaitez simplement conserver le contenu/texte, vous pouvez simplement le faire:

for p_tags in div_tags.find_all("p"):
    print(p_tags.get_text())

C'est tout et vous pouvez être libre avec toutes les balises br ou i b dans les balises parent et obtenir le texte clair.

1
robus gauli

Voici une python 3 version conviviale de cette fonction:

from bs4 import BeautifulSoup, NavigableString
invalidTags = ['br','b','font']
def stripTags(html, invalid_tags):
    soup = BeautifulSoup(html, "lxml")
    for tag in soup.findAll(True):
        if tag.name in invalid_tags:
            s = ""
            for c in tag.contents:
                if not isinstance(c, NavigableString):
                    c = stripTags(str(c), invalid_tags)
                s += str(c)
            tag.replaceWith(s)
    return soup
0
Dom DaFonte

C'est une vieille question, mais juste pour dire de meilleures façons de le faire. Tout d'abord, BeautifulSoup 3 * n'est plus en cours de développement, vous devriez donc plutôt utiliser BeautifulSoup 4 *, appelé bs4 .

De plus, lxml a juste la fonction dont vous avez besoin: classe Cleaner a l'attribut remove_tags, que vous pouvez définir pour les balises qui seront supprimées lors de l'extraction de leur contenu dans la balise parent.

0
Tommz