Текст веб-сторінки BeautifulSoup Grab


124

В основному, я хочу використовувати BeautifulSoup, щоб чітко схопити видимий текст на веб-сторінці. Наприклад, ця веб-сторінка є моїм тестом. І в основному я хочу просто отримати текст тексту (статтю) і, можливо, навіть кілька назв вкладок тут і там. Я спробував пропозицію в цьому запитанні ТАК, який повертає безліч <script>тегів і html-коментарів, які я не хочу. Я не можу з’ясувати аргументи, які мені потрібні для findAll()того, щоб просто отримати видимі тексти на веб-сторінці.

Отже, як я можу знайти весь видимий текст, за винятком сценаріїв, коментарів, css тощо?

Відповіді:


239

Спробуйте це:

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 бо soup.findAll(text=True)ніколи не знав про цю особливість
Хартлі Броді

7
Для останніх BS4 (принаймні) ви можете ідентифікувати коментарі, isinstance(element, Comment)а не збігатися з регулярними виразами.
tripleee

5
Я вважаю, що рядок 2 повинен бутиsoup = BeautifulSoup(html)
jczaplew

11
У видимій функції еліф для пошуку коментарів не здавався. мені довелося його оновити elif isinstance(element,bs4.element.Comment):. Я також додав "мета" до списку батьків.
Russ Savage

4
Наведений вище фільтр має багато \ n в результаті, додайте наступний код, щоб усунути пробіли та нові рядки: elif re.match(r"[\s\r\n]+",str(element)): return False
天才 小飞 猫

37

Затверджена відповідь від @jbochi для мене не працює. Виклик функції str () викликає виняток, оскільки він не може кодувати символи, що не входять в елемент BeautifulSoup. Ось більш складний спосіб відфільтрувати приклад веб-сторінки до видимого тексту.

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

1
Якщо str(element)проблеми з кодуванням не вдається, спробуйте unicode(element)скоріше спробувати 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
Попередні відповіді для мене не спрацювали, але це було :)
rjurney

Якщо я спробую це на url imfuna.com, він повертає лише 6 слів (програми інвентаризації властивостей Imfuna та інспекції), незважаючи на те, що на сторінці є набагато більше тексту / слів ... будь-які ідеї, чому ця відповідь не працює для цього URL? @bumpkin
the_t_test_1

10

Я повністю з повагою використовую Beautiful Soup для отримання візуалізованого контенту, але це може бути не ідеальним пакетом для придбання викладеного контенту на сторінці.

У мене була схожа проблема з отриманням виведеного вмісту або видимого вмісту в звичайному браузері. Зокрема, у мене було багато, мабуть, нетипових випадків, щоб працювати з таким простим прикладом нижче. У цьому випадку тег, який не відображається, вкладений у тег стилю та не видно у багатьох браузерах, які я перевірив. Існують й інші варіанти, такі як визначення відображення налаштування тегів класу для жодних. Потім використовуючи цей клас для діл.

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

Одне з розміщених вище рішень:

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

Це рішення, безумовно, має додатки у багатьох випадках і виконує цю роботу досить добре, але у розміщеному вище html-файлі він зберігає текст, який не надається. Після пошуку ТАК тут з'явилися пару рішень. BeautifulSoup get_text не знімає всі теги та JavaScript, і ось наданий HTML для простого тексту за допомогою Python

Я спробував обидва ці рішення: html2text та nltk.clean_html і був здивований результатами часу, тому подумав, що вони гарантують відповідь на потомство. Звичайно, швидкості сильно залежать від змісту даних ...

Одна відповідь від @Helge стосувалася використання nltk усіх речей.

import nltk

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

Це справді добре спрацювало, щоб повернути рядок із виведеним HTML. Цей модуль nltk був швидшим, ніж навіть html2text, хоча, можливо, html2text є більш надійним.

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

3

Якщо ви дбаєте про ефективність, ось ще один більш ефективний спосіб:

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є ітератором, і він повертається, NavigableStringщоб ви могли перевірити ім'я батьківського тегу безпосередньо, не проходячи кілька циклів.


2

Заголовок знаходиться всередині <nyt_headline>тегу, який вкладений всередині <h1>тегу та <div>тегу з id "article".

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

Треба працювати.

Корпус статті знаходиться всередині <nyt_text>тегу, який вкладений всередині <div>тегу з id "articleBody". Всередині <nyt_text> елемента сам текст міститься в <p> тегах. Зображення не входять до цих <p>тегів. Мені складно експериментувати з синтаксисом, але я очікую, що робочий скребок виглядає приблизно так.

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

Я впевнений, що це працює для цього тестового випадку, але шукаю більш загальну відповідь, яка може бути застосована до інших інших веб-сайтів ... Поки я намагався за допомогою regexps знайти теги <script> </script> та < ! -. * -> коментарі та замініть їх на "", але це навіть довести
своєрідне

2

Хоча я б повністю запропонував використовувати гарні супи взагалі, якщо хтось хоче з будь-якої причини відображати видимі частини неправильного формату HTML (наприклад, де у вас є лише сегмент або рядок веб-сторінки), наступне буде видалено вміст між <та >тегами:

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

2

Використовувати BeautifulSoup найпростішим способом з меншим кодом, щоб отримати рядки, без порожніх рядків і лайна.

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

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

0

Найпростіший спосіб впоратися з цією справою - за допомогою getattr(). Ви можете адаптувати цей приклад до своїх потреб:

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)

Цей текстовий елемент знайдеться в "3.7"межах об’єкта тегу, <span class="ratingsContent">3.7</span>коли він існує, однак за замовчуванням, NoneTypeколи цього немає.

getattr(object, name[, default])

Повернути значення названого атрибута об'єкта. ім'я повинно бути рядком. Якщо рядок - це ім'я одного з атрибутів об'єкта, результат - це значення цього атрибута. Наприклад, getattr (x, 'foobar') еквівалентно x.foobar. Якщо названого атрибута не існує, за умовчанням повертається, якщо це передбачено, в іншому випадку 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))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.