BeautifulSoup Grab Visible Webpage Text


124

Fondamentalmente, voglio usare BeautifulSoup per afferrare rigorosamente il testo visibile su una pagina web. Ad esempio, questa pagina web è il mio caso di prova. E principalmente voglio solo ottenere il corpo del testo (articolo) e forse anche alcuni nomi di tabulazioni qua e là. Ho provato il suggerimento in questa domanda SO che restituisce molti <script>tag e commenti html che non voglio. Non riesco a capire gli argomenti di cui ho bisogno per la funzione findAll()per ottenere solo i testi visibili su una pagina web.

Quindi, come dovrei trovare tutto il testo visibile esclusi script, commenti, CSS ecc.?

Risposte:


239

Prova questo:

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))

47
+1 per soup.findAll(text=True)non aver mai saputo di quella funzione
Hartley Brody

7
Per BS4 recenti (almeno) potresti identificare i commenti con isinstance(element, Comment)invece di abbinarli a una regex.
tripleee

5
Credo che la linea 2 dovrebbe esseresoup = BeautifulSoup(html)
jczaplew

11
Nella funzione visibile, l'elif per la ricerca dei commenti non sembrava funzionare. ho dovuto aggiornarlo a elif isinstance(element,bs4.element.Comment):. Ho anche aggiunto "meta" all'elenco dei genitori.
Russ Savage

4
Il filtro sopra ha molti \ n nel risultato, aggiungi il seguente codice per eliminare spazi bianchi e nuove righe: elif re.match(r"[\s\r\n]+",str(element)): return False
天才 小飞 猫

37

La risposta approvata da @jbochi non funziona per me. La chiamata alla funzione str () solleva un'eccezione perché non può codificare i caratteri non ASCII nell'elemento BeautifulSoup. Ecco un modo più succinto per filtrare la pagina web di esempio in testo visibile.

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

1
Se str(element)fallisce con problemi di codifica, dovresti provare unicode(element)invece se stai usando Python 2.
mknaf

31
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'))

4
Le risposte precedenti non hanno funzionato per me, ma questo ha funzionato :)
rjurney

Se provo questo sull'URL imfuna.com restituisce solo 6 parole (Imfuna Property Inventory and Inspection Apps) nonostante ci sia molto più testo / parole sulla pagina ... qualsiasi idea sul perché questa risposta non funziona per quello url? @bumpkin
the_t_test_1

10

Rispetto completamente l'utilizzo di Beautiful Soup per ottenere contenuti renderizzati, ma potrebbe non essere il pacchetto ideale per acquisire il contenuto renderizzato su una pagina.

Ho avuto un problema simile per ottenere il contenuto visualizzato o il contenuto visibile in un tipico browser. In particolare ho avuto molti casi forse atipici su cui lavorare con un esempio così semplice di seguito. In questo caso il tag non visualizzabile è annidato in un tag di stile e non è visibile in molti browser che ho controllato. Esistono altre varianti come la definizione di una visualizzazione dell'impostazione di un tag di classe su nessuno. Quindi usando questa classe per il 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>

Una soluzione pubblicata sopra è:

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

Questa soluzione ha certamente applicazioni in molti casi e fa il lavoro abbastanza bene in generale, ma nell'html postato sopra mantiene il testo che non viene visualizzato. Dopo aver cercato SO, un paio di soluzioni sono arrivate qui BeautifulSoup get_text non rimuove tutti i tag e JavaScript e qui HTML reso in testo normale usando Python

Ho provato entrambe queste soluzioni: html2text e nltk.clean_html e sono rimasto sorpreso dai risultati dei tempi, quindi ho pensato che garantissero una risposta per i posteri. Ovviamente le velocità dipendono fortemente dal contenuto dei dati ...

Una risposta qui da @Helge riguardava l'utilizzo di nltk di tutte le cose.

import nltk

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

Ha funzionato molto bene per restituire una stringa con html renderizzato. Questo modulo nltk era più veloce persino di html2text, anche se forse html2text è più robusto.

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

3

Se ti interessano le prestazioni, ecco un altro modo più efficiente:

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è un iteratore e restituisce in NavigableStringmodo che tu possa controllare direttamente il nome del tag genitore, senza passare attraverso più cicli.


2

Il titolo è all'interno di un <nyt_headline>tag, che è annidato all'interno di un <h1>tag e un <div>tag con id "articolo".

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

Dovrebbe funzionare.

Il corpo dell'articolo è all'interno di un <nyt_text>tag, che è annidato all'interno di un <div>tag con id "articleBody". All'interno <nyt_text> dell'elemento, il testo stesso è contenuto nei <p> tag. Le immagini non sono all'interno di questi <p>tag. È difficile per me sperimentare la sintassi, ma mi aspetto che uno scrape funzionante assomigli a questo.

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

Sono sicuro che questo funziona per questo caso di test, tuttavia, alla ricerca di una risposta più generica che può essere applicata a vari altri siti Web ... Finora, ho provato a utilizzare le espressioni regolari per trovare i tag <script> </script> e < ! -. * -> commenta e sostituiscili con "" ma questo si sta anche rivelando un po 'difficile per un motivo
sommario

2

Mentre, suggerirei completamente di usare beautiful-soup in generale, se qualcuno sta cercando di visualizzare le parti visibili di un html malformato (ad esempio, dove hai solo un segmento o una linea di una pagina web) per qualsiasi motivo, il seguente rimuoverà il contenuto tra i tag <e >:

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

2

Usare BeautifulSoup nel modo più semplice con meno codice per ottenere solo le stringhe, senza righe vuote e schifezze.

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

for i in soup.stripped_strings:
    print repr(i)

0

Il modo più semplice per gestire questo caso è usare getattr(). Puoi adattare questo esempio alle tue esigenze:

from bs4 import BeautifulSoup

source_html = """
<span class="ratingsDisplay">
    <a class="ratingNumber" href="https://www.youtube.com/watch?v=oHg5SJYRHA0" target="_blank" rel="noopener">
        <span class="ratingsContent">3.7</span>
    </a>
</span>
"""

soup = BeautifulSoup(source_html, "lxml")
my_ratings = getattr(soup.find('span', {"class": "ratingsContent"}), "text", None)
print(my_ratings)

Questo troverà l'elemento di testo "3.7", all'interno dell'oggetto tag <span class="ratingsContent">3.7</span>quando esiste, tuttavia, l'impostazione predefinita è NoneTypequando non lo è.

getattr(object, name[, default])

Restituisce il valore dell'attributo denominato dell'oggetto. il nome deve essere una stringa. Se la stringa è il nome di uno degli attributi dell'oggetto, il risultato è il valore di quell'attributo. Ad esempio, getattr (x, 'foobar') è equivalente a x.foobar. Se l'attributo denominato non esiste, viene restituito il valore predefinito se fornito, altrimenti viene sollevata l'eccezione AttributeError.


0
from bs4 import BeautifulSoup
from bs4.element import Comment
import urllib.request
import re
import ssl

def tag_visible(element):
    if element.parent.name in ['style', 'script', 'head', 'title', 'meta', '[document]']:
        return False
    if isinstance(element, Comment):
        return False
    if re.match(r"[\n]+",str(element)): return False
    return True
def text_from_html(url):
    body = urllib.request.urlopen(url,context=ssl._create_unverified_context()).read()
    soup = BeautifulSoup(body ,"lxml")
    texts = soup.findAll(text=True)
    visible_texts = filter(tag_visible, texts)  
    text = u",".join(t.strip() for t in visible_texts)
    text = text.lstrip().rstrip()
    text = text.split(',')
    clean_text = ''
    for sen in text:
        if sen:
            sen = sen.rstrip().lstrip()
            clean_text += sen+','
    return clean_text
url = 'http://www.nytimes.com/2009/12/21/us/21storm.html'
print(text_from_html(url))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.