it-swarm.com.de

BeautifulSoup Grab Visible Webpage Text

Grundsätzlich möchte ich BeautifulSoup verwenden, um den sichtbaren Text auf einer Webseite genau zu erfassen. Zum Beispiel ist diese Webseite mein Testfall. Und ich möchte hauptsächlich nur den Body-Text (Artikel) und vielleicht sogar ein paar Tab-Namen hier und da bekommen. Ich habe den Vorschlag in dieser SO-Frage versucht, der viele <script> - Tags und HTML-Kommentare zurückgibt, die ich nicht möchte. Ich kann die Argumente, die ich für die Funktion findAll() benötige, nicht herausfinden, um nur die sichtbaren Texte auf einer Webseite zu erhalten.

Wie soll ich also den gesamten sichtbaren Text außer Skripten, Kommentaren, CSS usw. finden?

111
user233864

Versuche dies:

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

Die bestätigte Antwort von @jbochi funktioniert bei mir nicht. Der Funktionsaufruf str () löst eine Ausnahme aus, da die Nicht-ASCII-Zeichen im BeautifulSoup-Element nicht codiert werden können. Hier ist eine prägnantere Möglichkeit, die Beispielwebseite nach sichtbarem Text zu filtern.

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

Ich respektiere es vollkommen, Beautiful Soup zu verwenden, um gerenderten Inhalt zu erhalten, aber es ist möglicherweise nicht das ideale Paket, um den gerenderten Inhalt auf einer Seite zu erhalten.

Ich hatte ein ähnliches Problem, um gerenderten Inhalt oder den sichtbaren Inhalt in einem typischen Browser zu erhalten. Insbesondere hatte ich viele vielleicht atypische Fälle, um mit einem so einfachen Beispiel weiter unten zu arbeiten. In diesem Fall ist das nicht anzeigbare Tag in einem Style-Tag verschachtelt und in vielen von mir überprüften Browsern nicht sichtbar. Es gibt auch andere Variationen, z. B. das Definieren einer Klassen-Tag-Einstellungsanzeige als "Keine". Dann benutze diese Klasse für den 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>

Eine der oben genannten Lösungen lautet:

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']

Diese Lösung hat sicherlich in vielen Fällen Anwendungen und erledigt die Aufgabe im Allgemeinen ganz gut, aber in dem oben angegebenen HTML-Code wird der nicht gerenderte Text beibehalten. Nach der Suche SO hier sind ein paar Lösungen aufgetaucht BeautifulSoup get_text entfernt nicht alle Tags und JavaScript und hier HTML wird mit Python in einfachen Text gerendert

Ich habe beide Lösungen ausprobiert: html2text und nltk.clean_html und war von den Timing-Ergebnissen überrascht, sodass sie eine Antwort für die Nachwelt rechtfertigten. Natürlich hängen die Geschwindigkeiten stark vom Inhalt der Daten ab ...

Eine Antwort von @Helge war, ausgerechnet nltk zu verwenden.

import nltk

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

Es hat sehr gut funktioniert, einen String mit gerendertem HTML zurückzugeben. Dieses nltk-Modul war schneller als sogar HTML2-Text, obwohl HTML2-Text möglicherweise robuster ist.

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

Wenn Sie Wert auf Leistung legen, gibt es noch eine effizientere Möglichkeit:

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 ist ein Iterator und gibt NavigableString zurück, sodass Sie den übergeordneten Tag-Namen direkt überprüfen können, ohne mehrere Schleifen durchlaufen zu müssen.

2
Polor Beer

Verwenden Sie BeautifulSoup auf einfachste Weise mit weniger Code, um nur die Zeichenfolgen abzurufen, ohne Leerzeilen und Mist.

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

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

Ich würde zwar generell die Verwendung von beautiful-soup empfehlen, wenn jemand die sichtbaren Teile eines fehlerhaften HTML-Codes anzeigen möchte (z. B. wenn Sie nur einen Ausschnitt oder eine Zeile einer Webseite haben) Entfernt den Inhalt zwischen < und > Stichworte:

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

Der Titel befindet sich in einem <nyt_headline> - Tag, das in einem <h1> - Tag und einem <div> - Tag mit der ID "article" verschachtelt ist.

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

Sollte arbeiten.

Der Artikelkörper befindet sich in einem <nyt_text> - Tag, das in einem <div> - Tag mit der ID "articleBody" verschachtelt ist. Innerhalb des Elements <nyt_text> Ist der Text selbst in <p> - Tags enthalten. Bilder befinden sich nicht innerhalb dieser <p> - Tags. Es ist schwierig für mich, mit der Syntax zu experimentieren, aber ich erwarte, dass ein funktionierendes Scrape ungefähr so ​​aussieht.

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