基本上,我想使用 BeautifulSoup 来严格抓取网页上的可见文本。例如,这个网页是我的测试用例。而且我主要想在这里和那里获取正文(文章)甚至一些选项卡名称。我已经尝试过这个SO question中的建议,它返回了很多<script>
我不想要的标签和 html 注释。我无法弄清楚该函数所需的参数findAll()
才能仅获取网页上的可见文本。
那么,我应该如何找到除脚本、评论、css 等之外的所有可见文本?
基本上,我想使用 BeautifulSoup 来严格抓取网页上的可见文本。例如,这个网页是我的测试用例。而且我主要想在这里和那里获取正文(文章)甚至一些选项卡名称。我已经尝试过这个SO question中的建议,它返回了很多<script>
我不想要的标签和 html 注释。我无法弄清楚该函数所需的参数findAll()
才能仅获取网页上的可见文本。
那么,我应该如何找到除脚本、评论、css 等之外的所有可见文本?
尝试这个:
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()
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'))
我完全尊重使用 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 中,它保留了未呈现的文本。搜索后,这里出现了几个解决方案BeautifulSoup get_text does not striped all tags and JavaScript and here Rendered 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
使用 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
以便您可以直接检查父级的标签名称,而无需经过多个循环。
虽然,我完全建议使用 beautiful-soup,如果有人希望显示格式错误的 html 的可见部分(例如,您只有网页的一段或一行),无论出于何种原因,以下将删除<
和>
标签之间的内容:
import re ## only use with malformed html - this is not efficient
def display_visible_html_using_re(text):
return(re.sub("(\<.*?\>)", "",text))
标题在<nyt_headline>
标签内,标签嵌套在<h1>
标签和<div>
id 为“文章”的标签内。
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')
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))
处理这种情况的最简单方法是使用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。