BeautifulSoup Görünür Web Sayfası Metnini Yakalayın


124

Temel olarak, bir web sayfasında kesinlikle görünen metni yakalamak için BeautifulSoup'u kullanmak istiyorum . Örneğin, bu web sayfası benim test örneğim. Ve esas olarak sadece gövde metnini (makale) ve hatta burada ve orada birkaç sekme adını almak istiyorum. Bu SO sorusundaki , <script>istemediğim birçok etiket ve html yorumu döndüren öneriyi denedim . findAll()Sadece bir web sayfasındaki görünür metinleri elde etmek için işlev için ihtiyacım olan argümanları çözemiyorum.

Öyleyse, komut dosyaları, yorumlar, css vb. Hariç tüm görünür metni nasıl bulmalıyım?

Yanıtlar:


239

Bunu dene:

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
soup.findAll(text=True)Bu özellik hakkında hiç bilmediğim için +1
Hartley Brody

7
Son BS4 için (en azından) yorumları isinstance(element, Comment)bir normal ifadeyle eşleştirmek yerine ile tanımlayabilirsiniz .
üçlü

5
2. satırın olması gerektiğine inanıyorumsoup = BeautifulSoup(html)
jczaplew

11
Görünür işlevde, yorumları bulmak için elif çalışmıyor gibi görünüyordu. güncellemem gerekiyordu elif isinstance(element,bs4.element.Comment):. Ayrıca ebeveyn listesine 'meta' ekledim.
Russ Savage

4
Yukarıdaki filtrede çok fazla \ n var, beyaz boşlukları ve yeni satırları ortadan kaldırmak için aşağıdaki kodu ekleyin: elif re.match(r"[\s\r\n]+",str(element)): return False
天才 小飞 猫

37

@Jbochi'den onaylanan cevap benim için çalışmıyor. Str () işlev çağrısı, BeautifulSoup öğesindeki ascii olmayan karakterleri kodlayamadığından bir istisna oluşturur. Örnek web sayfasını görünür metne göre filtrelemenin daha kısa ve öz bir yolu.

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

1
Eğer str(element)kodlama sorunları ile başarısız, denemek gerekir unicode(element)Python 2. kullanıyorsanız yerine
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
Önceki cevaplar benim için işe yaramadı, ama bu işe yaradı :)
rjurney

Bunu imfuna.com url'sinde denersem, sayfada çok daha fazla metin / kelime olmasına rağmen yalnızca 6 kelime (Imfuna Property Inventory ve Inspection Apps) döndürür ... bu cevabın neden işe yaramadığına dair herhangi bir fikir url? @bumpkin
the_t_test_1

10

Oluşturulan içeriği elde etmek için Beautiful Soup'u kullanmaya tamamen saygı duyuyorum, ancak bir sayfada oluşturulan içeriği elde etmek için ideal paket olmayabilir.

Oluşturulan içeriği veya tipik bir tarayıcıda görünen içeriği almak için benzer bir sorun yaşadım. Özellikle, aşağıda bu kadar basit bir örnekle çalışmak için pek çok atipik vakam oldu. Bu durumda görüntülenemeyen etiket bir stil etiketinin içine yerleştirildi ve kontrol ettiğim birçok tarayıcıda görünmüyor. Bir sınıf etiketi ayarı görüntüsünü yok olarak tanımlamak gibi başka varyasyonlar da mevcuttur. Sonra bu sınıfı div için kullanarak.

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

Yukarıda yayınlanan bir çözüm:

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

Bu çözüm kesinlikle birçok durumda uygulamalara sahiptir ve işi genel olarak oldukça iyi yapar, ancak yukarıda yayınlanan html'de işlenmeyen metni tutar. SO arandıktan sonra buraya birkaç çözüm geldi BeautifulSoup get_text tüm etiketleri ve JavaScript'i kaldırmaz ve burada Python kullanarak HTML'yi düz metne dönüştürür

Bu çözümlerin ikisini de denedim: html2text ve nltk.clean_html ve zamanlama sonuçlarına şaşırdım, bu yüzden gelecek nesillere bir cevap vereceğini düşündüm. Tabii ki, hızlar büyük ölçüde verilerin içeriğine bağlıdır ...

@ Helge'den gelen bir cevap, her şeyde nltk kullanmakla ilgiliydi.

import nltk

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

İşlenmiş html ile bir dizge döndürmek gerçekten iyi çalıştı. Bu nltk modülü html2text'ten bile daha hızlıydı, ancak html2text daha sağlam olabilir.

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

3

Performansı önemsiyorsanız, işte daha etkili bir yol:

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.stringsbir yineleyicidir ve NavigableStringbirden çok döngüden geçmeden ebeveynin etiket adını doğrudan kontrol edebilmeniz için geri döner .


2

Başlık, <nyt_headline>bir <h1>etiketi ve <div>"makale" kimliğine sahip bir etiketin içinde yer alan bir etiketin içindedir.

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

Çalışmalı.

Makale gövdesi, "articleBody" kimliğine sahip <nyt_text>bir <div>etiketin içine yerleştirilmiş bir etiketin içindedir. İçinde <nyt_text> elemanı, metnin kendisi içinde bulunur <p> etiketleri. Görüntüler bu <p>etiketlerin içinde değil . Sözdizimini denemek benim için zor, ancak çalışan bir sıyrığın böyle bir şeye benzemesini bekliyorum.

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

Eminim bu test senaryosu için işe yarar, ancak diğer çeşitli web sitelerine uygulanabilecek daha genel bir yanıt arıyordum ... Şimdiye kadar <script> </script> etiketlerini bulmak için normal ifadeleri kullanmayı denedim ve < ! -. * -> yorum yapın ve bunları "" ile değiştirin, ancak bu bile özetle biraz zor oluyor ..
user233864

2

Bununla birlikte, genel olarak güzel çorba kullanmanızı tamamen öneririm, herhangi bir nedenle hatalı biçimlendirilmiş bir html'nin görünen kısımlarını (örneğin, bir web sayfasının sadece bir segmentine veya satırına sahip olduğunuzda) görüntülemek isteyen varsa, aşağıdaki <ve >etiketleri arasındaki içeriği kaldıracak :

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

2

BeautifulSoup'u daha az kodla, boş satırlar ve saçmalıklar olmadan sadece dizeleri elde etmenin en kolay yolu.

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

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

0

Bu vakayı halletmenin en basit yolu kullanmaktır getattr(). Bu örneği ihtiyaçlarınıza göre uyarlayabilirsiniz:

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)

Bu, mevcut olduğunda "3.7"etiket nesnesi içindeki metin öğesini bulacaktır <span class="ratingsContent">3.7</span>, ancak NoneTypemevcut olmadığı zaman varsayılan olarak ayarlanır.

getattr(object, name[, default])

Nesnenin adlandırılmış özniteliğinin değerini döndür. isim bir dizge olmalıdır. Dize, nesnenin özniteliklerinden birinin adıysa, sonuç o özniteliğin değeridir. Örneğin getattr (x, 'foobar') x.foobar'a eşdeğerdir. Adlandırılmış öznitelik yoksa, sağlanmışsa varsayılan döndürülür, aksi takdirde AttributeError yükseltilir.


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))
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.