BeautifulSoup Захватить видимый текст веб-страницы

125

По сути, я хочу использовать BeautifulSoup для получения строго видимого текста на веб-странице. Например, эта веб-страница - мой тестовый пример. И в основном я хочу просто получить основной текст (статью) и, возможно, даже несколько названий вкладок здесь и там. Я пробовал предложение в этом вопросе SO, который возвращает множество <script>тегов и комментариев html, которые мне не нужны. Я не могу понять, какие аргументы мне нужны для функции findAll(), чтобы просто получить видимый текст на веб-странице.

Итак, как мне найти весь видимый текст, кроме скриптов, комментариев, CSS и т. Д.?

user233864
источник

Ответы:

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))
jbochi
источник
47
+1 за то, что soup.findAll(text=True)никогда не знал об этой особенности
Хартли Броуди
7
Для недавнего BS4 (по крайней мере) вы могли идентифицировать комментарии, isinstance(element, Comment)а не совпадать с регулярным выражением.
Tripleee
5
Я считаю, что строка 2 должна бытьsoup = BeautifulSoup(html)
jczaplew
11
В видимой функции elif для поиска комментариев, похоже, не работал. мне пришлось обновить его до elif isinstance(element,bs4.element.Comment):. Я также добавил «мета» в список родителей.
Расс Сэвидж
4
Вышеупомянутый фильтр содержит много \ n в результате, добавьте следующий код для удаления пробелов и новых строк: elif re.match(r"[\s\r\n]+",str(element)): return False
天才 小飞 猫
37

Утвержденный ответ от @jbochi у меня не работает. Вызов функции str () вызывает исключение, поскольку он не может кодировать символы, отличные от ascii, в элементе BeautifulSoup. Вот более лаконичный способ отфильтровать пример веб-страницы для отображения видимого текста.

html = open('21storm.html').read()
soup = BeautifulSoup(html)
[s.extract() for s in soup(['style', 'script', '[document]', 'head', 'title'])]
visible_text = soup.getText()
nmgeek
источник
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
Предыдущие ответы не
помогли
Если я попробую это на URL-адресе imfuna.com, он вернет только 6 слов (Imfuna Property Inventory и Inspection Apps), несмотря на то, что на странице гораздо больше текста / слов ... любые идеи, почему этот ответ не работает для этого URL? @bumpkin
the_t_test_1
есть ли способ заменить <br>теги \nсимволами новой строки?
12 ромбов в сетке без углов
10

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

У меня была аналогичная проблема с получением визуализированного контента или видимого контента в обычном браузере. В частности, у меня было много, возможно, нетипичных случаев для работы с таким простым примером, приведенным ниже. В этом случае неотображаемый тег вложен в тег стиля и не отображается во многих проверенных мной браузерах. Существуют и другие варианты, такие как определение для параметра отображения тега класса значения none. Затем используя этот класс для 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>

Одно из решений, опубликованных выше:

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 оно сохраняет текст, который не отображается. После поиска SO здесь появилась пара решений 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>тега с идентификатором «article».

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

Должно сработать.

Тело статьи находится внутри <nyt_text>тега, который вложен в <div>тег с идентификатором articleBody. Внутри <nyt_text> элемента сам текст содержится в <p> тегах. Изображения не попадают в эти <p>теги. Мне сложно экспериментировать с синтаксисом, но я ожидаю, что рабочая царапина будет выглядеть примерно так.

text = soup.findAll('nyt_text', limit=1)[0]
text.findAll('p')
Юэн Тодд
источник
Однако я уверен, что это работает для этого тестового примера, ищу более общий ответ, который может быть применен к различным другим веб-сайтам ... До сих пор я пробовал использовать регулярные выражения для поиска тегов <script> </script> и < ! -. * -> комментарии и замените их на "", но это даже оказывается трудным по причине суммы ..
user233864
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))
Камран Каусар
источник