web-dev-qa-db-fra.com

Texte de la page Web visible de BeautifulSoup Grab

En gros, je veux utiliser BeautifulSoup pour saisir strictement le texte visible sur une page Web. Par exemple, cette page Web est mon cas de test. Et je veux surtout obtenir le corps du texte (article) et peut-être même quelques noms d'onglets ici et là. J'ai essayé la suggestion dans cette SO question qui retourne beaucoup de balises <script> Et de commentaires HTML que je ne veux pas. Je n'arrive pas à comprendre les arguments dont j'ai besoin pour la fonction findAll() ) afin d'obtenir uniquement les textes visibles sur une page Web.

Alors, comment trouver tout le texte visible, à l’exclusion des scripts, des commentaires, des CSS, etc.?

111
user233864

Essaye ça:

from bs4 import BeautifulSoup
from bs4.element import Comment
import urllib.request


def tag_visible(element):
    if element.parent.name in ['style', 'script', 'head', 'title', 'meta', '[document]']:
        return False
    if isinstance(element, Comment):
        return False
    return True


def text_from_html(body):
    soup = BeautifulSoup(body, 'html.parser')
    texts = soup.findAll(text=True)
    visible_texts = filter(tag_visible, texts)  
    return u" ".join(t.strip() for t in visible_texts)

html = urllib.request.urlopen('http://www.nytimes.com/2009/12/21/us/21storm.html').read()
print(text_from_html(html))
200
jbochi

La réponse approuvée de @jbochi ne fonctionne pas pour moi. L'appel à la fonction str () déclenche une exception car il ne peut pas coder les caractères non ascii de l'élément BeautifulSoup. Voici un moyen plus succinct de filtrer la page Web exemple en texte visible.

html = open('21storm.html').read()
soup = BeautifulSoup(html)
[s.extract() for s in soup(['style', 'script', '[document]', 'head', 'title'])]
visible_text = soup.getText()
32
nmgeek
import urllib
from bs4 import BeautifulSoup

url = "https://www.yahoo.com"
html = urllib.urlopen(url).read()
soup = BeautifulSoup(html)

# kill all script and style elements
for script in soup(["script", "style"]):
    script.extract()    # rip it out

# get text
text = soup.get_text()

# break into lines and remove leading and trailing space on each
lines = (line.strip() for line in text.splitlines())
# break multi-headlines into a line each
chunks = (phrase.strip() for line in lines for phrase in line.split("  "))
# drop blank lines
text = '\n'.join(chunk for chunk in chunks if chunk)

print(text.encode('utf-8'))
29
bumpkin

Je respecte totalement l'utilisation de Beautiful Soup pour obtenir un contenu rendu, mais cela peut ne pas être le package idéal pour acquérir le contenu affiché sur une page.

J'ai eu un problème similaire pour obtenir le contenu rendu, ou le contenu visible dans un navigateur classique. En particulier, j'ai eu beaucoup de cas peut-être atypiques pour travailler avec un exemple aussi simple ci-dessous. Dans ce cas, la balise non affichable est imbriquée dans une balise de style et n'est pas visible dans de nombreux navigateurs que j'ai vérifiés. D'autres variantes existent, telles que la définition d'un paramètre d'affichage de balise de classe égal à aucun. Puis en utilisant cette classe pour le div.

<html>
  <title>  Title here</title>

  <body>

    lots of text here <p> <br>
    <h1> even headings </h1>

    <style type="text/css"> 
        <div > this will not be visible </div> 
    </style>


  </body>

</html>

Une solution affichée ci-dessus est:

html = Utilities.ReadFile('simple.html')
soup = BeautifulSoup.BeautifulSoup(html)
texts = soup.findAll(text=True)
visible_texts = filter(visible, texts)
print(visible_texts)


[u'\n', u'\n', u'\n\n        lots of text here ', u' ', u'\n', u' even headings ', u'\n', u' this will not be visible ', u'\n', u'\n']

Cette solution a certes des applications dans de nombreux cas et fait très bien le travail en général, mais dans le code HTML affiché ci-dessus, elle conserve le texte non rendu. Après la recherche SO, quelques solutions ont été trouvées ici BeautifulSoup get_text ne supprime pas toutes les balises et JavaScript et ici Le rendu HTML en texte brut à l'aide de Python

J'ai essayé ces deux solutions: html2text et nltk.clean_html et j'ai été surpris par les résultats du chronométrage. Nous avons donc pensé qu'ils méritaient une réponse pour la postérité. Bien entendu, les vitesses dépendent fortement du contenu des données ...

Une des réponses de @Helge était d'utiliser nltk de toutes choses.

import nltk

%timeit nltk.clean_html(html)
was returning 153 us per loop

Cela a très bien fonctionné de renvoyer une chaîne avec HTML rendu. Ce module nltk était plus rapide que même html2text, bien que html2text soit peut-être plus robuste.

betterHTML = html.decode(errors='ignore')
%timeit html2text.html2text(betterHTML)
%3.09 ms per loop
10
Paul

Si vous vous souciez de la performance, voici un autre moyen plus efficace:

import re

INVISIBLE_ELEMS = ('style', 'script', 'head', 'title')
RE_SPACES = re.compile(r'\s{3,}')

def visible_texts(soup):
    """ get visible text from a document """
    text = ' '.join([
        s for s in soup.strings
        if s.parent.name not in INVISIBLE_ELEMS
    ])
    # collapse multiple spaces to two spaces.
    return RE_SPACES.sub('  ', text)

soup.strings est un itérateur et renvoie NavigableString pour que vous puissiez vérifier directement le nom de la balise du parent, sans passer par plusieurs boucles.

2
Polor Beer

Utiliser BeautifulSoup de la manière la plus simple avec moins de code pour obtenir uniquement les chaînes, sans lignes vides ni merde.

tag = <Parent_Tag_that_contains_the_data>
soup = BeautifulSoup(tag, 'html.parser')

for i in soup.stripped_strings:
    print repr(i)
2
Diego Suarez

Bien que, je suggérerais complètement d’utiliser beautiful-soup en général, si quelqu'un cherche à afficher les parties visibles d’un code HTML malformé (par exemple, lorsque vous n’avez qu’un segment ou une ligne d’une page Web), quelle que soit la raison, la suivante: enlèvera le contenu entre < et > Mots clés:

import re   ## only use with malformed html - this is not efficient
def display_visible_html_using_re(text):             
    return(re.sub("(\<.*?\>)", "",text))
1
kyrenia

Le titre se trouve dans une balise <nyt_headline>, Qui est imbriquée dans une balise <h1> Et une balise <div> Avec l'id "article".

soup.findAll('nyt_headline', limit=1)

Devrait marcher.

Le corps de l'article se trouve dans une balise <nyt_text>, Qui est imbriquée dans une balise <div> Avec l'id "articleBody". Dans l'élément <nyt_text>, Le texte lui-même est contenu dans les balises <p>. Les images ne sont pas dans ces balises <p>. Il m'est difficile d'expérimenter avec la syntaxe, mais je m'attends à ce qu'une pièce de travail ressemble à ceci.

text = soup.findAll('nyt_text', limit=1)[0]
text.findAll('p')
1
Ewan Todd