BeautifulSoup抓住可见的网页文本

时间:2009-12-20 17:55:58

标签: python text beautifulsoup html-content-extraction

基本上,我想使用BeautifulSoup严格抓取网页上的可见文字。例如,this webpage是我的测试用例。而且我主要想在这里和那里获得正文(文章)甚至几个标签名称。我已经尝试了这个SO question中的建议,它返回了许多我不想要的<script>标签和html评论。我无法找出函数findAll()所需的参数,以便在网页上获取可见文本。

那么,我应该如何找到除脚本,评论,CSS等之外的所有可见文本?

10 个答案:

答案 0 :(得分:189)

试试这个:

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

答案 1 :(得分:32)

来自@jbochi的批准答案对我不起作用。 str()函数调用引发异常,因为它无法编码BeautifulSoup元素中的非ascii字符。以下是将示例网页过滤为可见文本的更简洁方法。

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

答案 2 :(得分:29)

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

答案 3 :(得分:10)

我完全尊重使用Beautiful Soup来获取渲染内容,但它可能不是获取页面上渲染内容的理想包。

我遇到了类似的问题,无法获取渲染内容或典型浏览器中的可见内容。特别是我有许多非典型案例可以使用下面这么简单的例子。在这种情况下,不可显示的标记嵌套在样式标记中,并且在我检查过的许多浏览器中都不可见。存在其他变体,例如将类标签设置显示为无。然后使用这个类作为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 does not strip all tags and JavaScriptRendered HTML to plain text using 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

答案 4 :(得分:2)

使用BeautifulSoup是最简单的方法,只需更少的代码就可以获得字符串,而不需要空行和废话。

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

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

答案 5 :(得分:1)

标题位于<nyt_headline>标记内,该标记嵌套在<h1>标记和<div>标记内,标记为“文章”。

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

应该工作。

文章正文位于<nyt_text>标记内,该标记嵌套在ID为“articleBody”的<div>标记内。在<nyt_text>元素内,文本本身包含在<p>标记内。图片不在<p>个标签内。我很难尝试使用语法,但我希望看起来像这样的工作。

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

答案 6 :(得分:1)

虽然如果有人想要显示格式错误的html的可见部分(例如,你只有一个网页的一个部分或一行),我会完全建议使用漂亮的汤,无论出于什么原因,以下内容将删除<>代码之间的内容:

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

答案 7 :(得分:1)

如果你关心表现,这是另一种更有效的方法:

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,以便您可以直接检查父标记名称,而无需经过多个循环。

答案 8 :(得分: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。

答案 9 :(得分: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))