关于python:BeautifulSoup抓取可见网页文字

BeautifulSoup Grab Visible Webpage Text

基本上,我想使用BeautifulSoup来严格抓取网页上的可见文本。 例如,此网页是我的测试用例。 我主要想获取正文文本(文章),甚至在这里和那里获得一些标签名称。 我试过这个SO问题中的建议,该建议返回很多我不想要的标记和html注释。 我无法弄清楚函数findAll()所需的参数,以便仅获取网页上的可见文本。

那么,我应该如何查找除脚本,注释,CSS等之外的所有可见文本?


尝试这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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批准的答案对我不起作用。 str()函数调用引发异常,因为它无法对BeautifulSoup元素中的非ASCII字符进行编码。这是将示例网页过滤为可见文本的更简洁的方法。

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 = '
'
.join(chunk for chunk in chunks if chunk)

print(text.encode('utf-8'))


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

我在获取渲染的内容或在典型浏览器中可见的内容时遇到了类似的问题。特别是,在下面的一个简单示例中,我可能有许多可能是非典型的案例。在这种情况下,不可显示的标签嵌套在样式标签中,在我检查过的许多浏览器中都不可见。存在其他变体,例如将类标签设置显示定义为无。然后将此类用于div。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
    Title here

  <body>

    lots of text here <p>
 
     even headings

    <style type="text/css">
         this will not be visible  
    </style>


  </body>

</html>

上面发布的一种解决方案是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
html = Utilities.ReadFile('simple.html')
soup = BeautifulSoup.BeautifulSoup(html)
texts = soup.findAll(text=True)
visible_texts = filter(visible, texts)
print(visible_texts)


[u'
'
, u'
'
, u'

        lots of text here '
, u' ', u'
'
, u' even headings ', u'
'
, u' this will not be visible ', u'
'
, u'
'
]

该解决方案当然在许多情况下都有应用程序,并且通常可以很好地完成这项工作,但是在上面发布的html中,它保留了未呈现的文本。经过搜索之后,这里出现了一些解决方案,BeautifulSoup get_text不会剥离所有标签和JavaScript,这里是使用Python将HTML渲染为纯文本的方法

我尝试了这两种解决方案:html2text和nltk.clean_html,并且对计时结果感到惊讶,因此认为它们值得后代的答案。当然,速度很大程度上取决于数据的内容...

@Helge的一个答案是关于使用nltk所有东西。

1
2
3
4
import nltk

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

返回带有呈现的html的字符串确实效果很好。这个nltk模块甚至比html2text还要快,尽管html2text可能更健壮。

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

如果您关心性能,这是另一种更有效的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
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,以便您可以直接检查父级的标记名,而无需经历多个循环。


使用BeautifulSoup是最简单的方法,只需较少的代码即可获取字符串,而不会出现空行和废话。

1
2
3
4
5
tag = <Parent_Tag_that_contains_the_data>
soup = BeautifulSoup(tag, 'html.parser')

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


虽然,我会完全建议一般使用精美的汤,但是如果有人想显示任何原因的格式不正确的html的可见部分(例如,您只有一段网页或一行),则以下内容将删除<>标签之间的内容:

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

标题位于标记内,该标记嵌套在标记和ID为" article"的标记内。

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

应该管用。

文章正文位于标记内,该标记嵌套在ID为" articleBody"的标记内。在元素内部,文本本身包含在

标记内。图像不在这些

标记内。对我来说,尝试语法很难,但是我希望工作的草稿看起来像这样。

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