如何刮只有可见的网页文字与美丽的汤?

基本上,我想使用 BeautifulSoup抓取严格的 可见文字在网页上。例如,本网页是我的测试用例。我主要想得到的只是正文(文章) ,甚至可能是一些标签名在这里和那里。我已经尝试了在这个 有个问题的建议,返回大量的 <script>标签和 html 注释,我不想。我不能算出我需要的函数 findAll()的参数,以便只是得到可见的文本在网页上。

那么,我应该如何找到所有可见的文本,除了脚本,注释,css 等?

153430 次浏览

标题位于一个 <nyt_headline>标记中,该标记嵌套在一个 <h1>标记和一个标识为“ article”的 <div>标记中。

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

应该可以。

文章主体位于一个 <nyt_text>标记中,该标记嵌套在一个 ID 为“ artileBody”的 <div>标记中。在 <nyt_text>元素内部,文本本身包含在 <p>标记中。图像不在这些 <p>标签中。对于我来说,实验语法是困难的,但是我希望一个工作刮片看起来像这样。

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

试试这个:

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 字符。下面是一种更简洁的方法,可以将示例网页过滤为可见文本。

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

我完全尊重使用 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 中,它保留了未呈现的文本。经过搜索,有几个解决方案出现在这里和这里

我尝试了这两种解决方案: 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
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'))

虽然,我完全建议使用优美汤一般,如果有人正在寻找显示可见的部分,一个畸形的 html (例如,你只有一段或一行的网页) ,无论什么原因,以下将删除内容之间的 <>标签:

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

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

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


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

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

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,这样您就可以直接检查父标记的名称,而不必经过多个循环。

处理这种情况最简单的方法是使用 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)

这将在标记对象 <span class="ratingsContent">3.7</span>中找到文本元素 "3.7"(当它存在时) ,但是,如果它不存在,则默认为 NoneType

getattr(object, name[, default])

返回 object 的命名属性的值。Name 必须是字符串。如果字符串是对象的一个属性的名称,则结果是该属性的值。例如,getattr (x,‘ foobar’)等价于 x.foobar。如果命名属性不存在,则如果提供了默认值,则返回默认值,否则引发 AttributeError。

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

更新

来自文件: 从 ABC0版本4.9.0开始,当使用 ABC1或 ABC2时,ABC3、 ABC4和 <template>标签的内容通常不被认为是“文本”,因为这些标签不是页面的可见内容的一部分。

为了得到所有人类可读的 HTML <body>文本,你可以使用 .get_text(),去掉冗余的空格,等等,设置带参数和连接/分隔所有的一个空格:

import bs4, requests


response = requests.get('https://www.nytimes.com/interactive/2022/09/13/us/politics/congress-stock-trading-investigation.html',headers={'User-Agent': 'Mozilla/5.0','cache-control': 'max-age=0'}, cookies={'cookies':''})
soup = bs4.BeautifulSoup(response.text)


soup.article.get_text(' ', strip=True)

在较新的代码中避免使用旧的语法 findAll(),而是使用 find_all()select()css selectors-为更多花一分钟到 查查文件