从Python中的string中剥离HTML

from mechanize import Browser br = Browser() br.open('http://somewebpage') html = br.response().readlines() for line in html: print line 

当在HTML文件中打印一行时,我试图find一种方法,只显示每个HTML元素的内容,而不是格式本身。 如果发现'<a href="whatever.com">some text</a>' ,它只会打印“一些文字”, '<b>hello</b>'打印“hello”等。一个去做这个?

我总是使用这个函数去掉HTML标签,因为它只需要Python stdlib:

在Python 2上

 from HTMLParser import HTMLParser class MLStripper(HTMLParser): def __init__(self): self.reset() self.fed = [] def handle_data(self, d): self.fed.append(d) def get_data(self): return ''.join(self.fed) def strip_tags(html): s = MLStripper() s.feed(html) return s.get_data() 

对于Python 3

 from html.parser import HTMLParser class MLStripper(HTMLParser): def __init__(self): self.reset() self.strict = False self.convert_charrefs= True self.fed = [] def handle_data(self, d): self.fed.append(d) def get_data(self): return ''.join(self.fed) def strip_tags(html): s = MLStripper() s.feed(html) return s.get_data() 

注意 :这只适用于3.1。 对于3.2或更高版本,您需要调用父类的init函数。 请参阅在Python 3.2中使用HTMLParser

我没有想太多的情况下,它会错过,但你可以做一个简单的正则expression式:

 re.sub('<[^<]+?>', '', text) 

对于那些不理解正则expression式的人来说,它会search一个string<...> ,内部内容是由一个或多个不是<+ )字符组成的。 这个? 意味着它将匹配它能find的最小的string。 例如<p>Hello</p> ,它会分别与<'p></p>匹配? 。 没有它,它会匹配整个string<..Hello..>

如果非标签<在html中出现(例如2 < 3 ),则应将其作为转义序列写入&...因此^<可能是不必要的。

我需要一种方法去除标签并将 HTML实体解码为纯文本。 下面的解决scheme是基于Eloff的答案(我不能使用,因为它剥夺了实体)。

 from HTMLParser import HTMLParser import htmlentitydefs class HTMLTextExtractor(HTMLParser): def __init__(self): HTMLParser.__init__(self) self.result = [ ] def handle_data(self, d): self.result.append(d) def handle_charref(self, number): codepoint = int(number[1:], 16) if number[0] in (u'x', u'X') else int(number) self.result.append(unichr(codepoint)) def handle_entityref(self, name): codepoint = htmlentitydefs.name2codepoint[name] self.result.append(unichr(codepoint)) def get_text(self): return u''.join(self.result) def html_to_text(html): s = HTMLTextExtractor() s.feed(html) return s.get_text() 

快速testing:

 html = u'<a href="#">Demo <em>(&not; \u0394&#x03b7;&#956;&#x03CE;)</em></a>' print repr(html_to_text(html)) 

结果:

 u'Demo (\xac \u0394\u03b7\u03bc\u03ce)' 

error handling:

  • 无效的HTML结构可能会导致HTMLParseError 。
  • 无效的命名的HTML实体(例如,在XML和XHTML中有效的,而不是纯HTML)将导致ValueErrorexception。
  • 指定超出Python可接受的Unicode范围的代码点的数字HTML实体(例如,在某些系统上, 基本多语言平面以外的字符)将导致ValueErrorexception。

安全注意事项:不要将HTML清除(将纯文本转换为HTML)混淆HTML(将HTML转换为纯文本)。 这个答案将删除HTML和解码为纯文本的实体 – 这不会使结果在HTML上下文中使用安全。

例如: &lt;script&gt;alert("Hello");&lt;/script&gt; 将被转换为<script>alert("Hello");</script> ,这是100%正确的行为,但是如果生成的纯文本插入到HTML页面中,显然是不够的。

这个规则并不困难: 任何时候,当你将一个纯文本string插入到HTML输出中时,即使你“知道”它不包含HTML,也应该使用cgi.escape(s, True) (例如,因为你剥离了HTML内容)。

(然而,OP询问将结果打印到控制台,在这种情况下,不需要HTML转义。)

短版!

 import re, cgi tag_re = re.compile(r'(<!--.*?-->|<[^>]*>)') # Remove well-formed tags, fixing mistakes by legitimate users no_tags = tag_re.sub('', user_input) # Clean up anything else by escaping ready_for_web = cgi.escape(no_tags) 

正则expression式来源:MarkupSafe 。 他们的版本也处理HTML实体,而这个快速的不是。

为什么我不能把标签剥离下来呢?

让人们避免<i>italicizing</i>这是一回事。 但是,任意input又是完全无害的。 这个页面上的大多数技巧都会留下一些东西,如未封闭的注释( <!-- )和angular括号,而这些不是标签( blah <<<><blah )的一部分。 HTMLParser版本甚至可以留下完整的标签,如果他们在未封闭的评论。

如果您的模板是{{ firstname }} {{ lastname }}怎么办? firstname = '<a'lastname = 'href="http://evil.com/">'将被这个页面上的每个标签剥离器(除了@Medeiros!)所通过,因为它们不是完整的标签他们自己的。 剥离正常的HTML标签是不够的。

Django的strip_tags ,这个问题的最佳答案的改进(见下一个标题)版本给出了以下警告:

绝对不提供有关HTML安全的结果string的保证。 所以从不标记安全strip_tags调用的结果,而不是先逃脱它,例如使用escape()

按照他们的build议!

要用HTMLParser去除标签,你必须多次运行它。

回避这个问题的最佳答案很容易。

看看这个string( 来源和讨论 ):

 <img<!-- --> src=x onerror=alert(1);//><!-- --> 

HTMLParser第一次看到它,它不能说<img...>是一个标签。 它看起来破损,所以HTMLParser不会摆脱它。 它只取出<!-- comments --> ,留给你

 <img src=x onerror=alert(1);//> 

这个问题在2014年3月被披露给了Django项目。他们的旧strip_tags基本上是这个问题的最佳答案。 他们的新版本基本上循环运行,直到再次运行它不会改变string:

 # _strip_once runs HTMLParser once, pulling out just the text of all the nodes. def strip_tags(value): """Returns the given HTML with all tags stripped.""" # Note: in typical case this loop executes _strip_once once. Loop condition # is redundant, but helps to reduce number of executions of _strip_once. while '<' in value and '>' in value: new_value = _strip_once(value) if len(new_value) >= len(value): # _strip_once was not able to detect more tags break value = new_value return value 

当然,如果你总是逃避strip_tags()的结果,这些都不是问题。

2015年3月19日更新 :在1.4.20,1.6.11,1.7.7和1.8c1之前的Django版本中存在一个bug。 这些版本可以在strip_tags()函数中input一个无限循环。 上面重现了固定版本。 更多细节在这里 。

好东西要复制或使用

我的示例代码不处理HTML实体 – Django和MarkupSafe打包版本。

我的示例代码是从优秀的MarkupSafe库中提取的,用于跨站点脚本预防。 这是方便和快速的(C加速到它的本地Python版本)。 它包含在Google App Engine中 ,并被Jinja2(2.7及更高版本) ,Mako,Pylons等等使用。 它使用Django 1.7的Django模板很容易。

Django的strip_tags和其他来自最近版本的 html实用程序都很好,但我发现它们不如MarkupSafe方便。 他们是相当独立的,你可以从这个文件复制你需要的东西。

如果你需要去除几乎所有的标签, Bleach库是好的。 你可以让它执行像“我的用户可以斜体的东西,但他们不能使iframes”的规则。

了解您的标签剥离器的属性! 运行模糊testing! 这是我用来做这个答案的研究的代码 。

羞涩的注意 – 问题本身是关于打印到控制台,但这是谷歌“PythonstringHTML从string”的结果,所以这就是为什么这个答案是关于networking的99%。

如果你需要保存HTML实体(即&amp; ),我添加了“ handle_entityref ”方法给Eloff的答案 。

 from HTMLParser import HTMLParser class MLStripper(HTMLParser): def __init__(self): self.reset() self.fed = [] def handle_data(self, d): self.fed.append(d) def handle_entityref(self, name): self.fed.append('&%s;' % name) def get_data(self): return ''.join(self.fed) def html_to_text(html): s = MLStripper() s.feed(html) return s.get_data() 

有一个简单的方法来做到这一点:

 def remove_html_markup(s): tag = False quote = False out = "" for c in s: if c == '<' and not quote: tag = True elif c == '>' and not quote: tag = False elif (c == '"' or c == "'") and tag: quote = not quote elif not tag: out = out + c return out 

这个想法在这里解释:http: //youtu.be/2tu9LTDujbw

你可以看到它在这里工作:http: //youtu.be/HPkNPcYed9M?t=35s

PS – 如果你对这个类感兴趣(关于python的智能debugging),我给你一个链接: http : //www.udacity.com/overview/Course/cs259/CourseRev/1 。 免费!

别客气! 🙂

如果你想剥离所有的HTML标签,我发现最简单的方法是使用BeautifulSoup:

 from bs4 import BeautifulSoup # Or from BeautifulSoup import BeautifulSoup def stripHtmlTags(self, htmlTxt): if htmlTxt is None: return None else: return ''.join(BeautifulSoup(htmlTxt).findAll(text=True)) 

我试了接受的答案的代码,但我得到了“RuntimeError:最大recursion深度超过”,这并没有发生上述代码块。

为什么你们所有人都这么做呢? 您可以使用BeautifulSoup get_text()function。

 from bs4 import BeautifulSoup text = ''' <td><a href="http://www.fakewebsite.com">Please can you strip me?</a> <br/><a href="http://www.fakewebsite.com">I am waiting....</a> </td> ''' soup = BeautifulSoup(text) print(soup.get_text()) 

基于lxml.html的解决scheme(lxml是本地库,因此比任何纯Python解决scheme快得多)。

 from lxml import html from lxml.html.clean import clean_html tree = html.fromstring("""<span class="item-summary"> Detailed answers to any questions you might have </span>""") clean_tree = clean_html(tree) print(clean_tree.text_content().strip()) # >>> Detailed answers to any questions you might have 

另请参阅http://lxml.de/lxmlhtml.html#cleaning-up-html了解lxml.cleaner的function。;

如果您希望在转换为文本之前更好地控制HTML的清除方式 ,则可以通过在构造方法中传递所需的选项来显式使用lxml Cleaner ,例如:

 cleaner = Cleaner(page_structure=True, forms=True, style=True, scripts=True, frames=True) sanitized_html = cleaner.clean_html(unsafe_html) 

你可以使用不同的HTMLparsing器( 比如lxml或者Beautiful Soup ) – 一个提供函数来提取文本的函数。 或者,你可以在你的行string上运行一个正则expression式去掉标签。 有关更多信息,请参见http://www.amk.ca/python/howto/regex/

我已经成功地使用了Eloff的Python 3.1 [非常感谢!]。

我升级到Python 3.2.3,并出现错误。

由于响应者Thomas K, 在这里提供的解决scheme是将super().__init__()插入到以下代码中:

 def __init__(self): self.reset() self.fed = [] 

…为了使它看起来像这样:

 def __init__(self): super().__init__() self.reset() self.fed = [] 

…它将为Python 3.2.3工作。

同样,感谢托马斯K的修复和上面提供的Eloff原始代码!

你可以写你自己的function:

 def StripTags(text): finished = 0 while not finished: finished = 1 start = text.find("<") if start >= 0: stop = text[start:].find(">") if stop >= 0: text = text[:start] + text[start+stop+1:] finished = 0 return text 

HTMLparsing器的解决scheme都是可破解的,如果它们只运行一次:

 html_to_text('<<b>script>alert("hacked")<</b>/script> 

结果是:

 <script>alert("hacked")</script> 

你打算预防什么 如果您使用HTMLparsing器,则计数标签直到零被replace:

 from HTMLParser import HTMLParser class MLStripper(HTMLParser): def __init__(self): self.reset() self.fed = [] self.containstags = False def handle_starttag(self, tag, attrs): self.containstags = True def handle_data(self, d): self.fed.append(d) def has_tags(self): return self.containstags def get_data(self): return ''.join(self.fed) def strip_tags(html): must_filtered = True while ( must_filtered ): s = MLStripper() s.feed(html) html = s.get_data() must_filtered = s.has_tags() return html 

这是一个快速修复,可以进一步优化,但它会正常工作。 这段代码将用“”replace所有非空标签,并将所有html标签从给定的input文本中去除。你可以使用./file.pyinput输出

  #!/usr/bin/python import sys def replace(strng,replaceText): rpl = 0 while rpl > -1: rpl = strng.find(replaceText) if rpl != -1: strng = strng[0:rpl] + strng[rpl + len(replaceText):] return strng lessThanPos = -1 count = 0 listOf = [] try: #write File writeto = open(sys.argv[2],'w') #read file and store it in list f = open(sys.argv[1],'r') for readLine in f.readlines(): listOf.append(readLine) f.close() #remove all tags for line in listOf: count = 0; lessThanPos = -1 lineTemp = line for char in lineTemp: if char == "<": lessThanPos = count if char == ">": if lessThanPos > -1: if line[lessThanPos:count + 1] != '<>': lineTemp = replace(lineTemp,line[lessThanPos:count + 1]) lessThanPos = -1 count = count + 1 lineTemp = lineTemp.replace("&lt","<") lineTemp = lineTemp.replace("&gt",">") writeto.write(lineTemp) writeto.close() print "Write To --- >" , sys.argv[2] except: print "Help: invalid arguments or exception" print "Usage : ",sys.argv[0]," inputfile outputfile" 

美丽的汤包立即为您做到这一点。

 from bs4 import BeautifulSoup soup = BeautifulSoup(html) text = soup.get_text() print(text) 

我正在parsingGithub自述文件,我发现以下内容非常有效:

 import re import lxml.html def strip_markdown(x): links_sub = re.sub(r'\[(.+)\]\([^\)]+\)', r'\1', x) bold_sub = re.sub(r'\*\*([^*]+)\*\*', r'\1', links_sub) emph_sub = re.sub(r'\*([^*]+)\*', r'\1', bold_sub) return emph_sub def strip_html(x): return lxml.html.fromstring(x).text_content() if x else '' 

接着

 readme = """<img src="https://raw.githubusercontent.com/kootenpv/sky/master/resources/skylogo.png" /> sky is a web scraping framework, implemented with the latest python versions in mind (3.4+). It uses the asynchronous `asyncio` framework, as well as many popular modules and extensions. Most importantly, it aims for **next generation** web crawling where machine intelligence is used to speed up the development/maintainance/reliability of crawling. It mainly does this by considering the user to be interested in content from *domains*, not just a collection of *single pages* ([templating approach](#templating-approach)).""" strip_markdown(strip_html(readme)) 

正确删除所有markdown和html。

søren-løvborg答案的python 3改编

 from html.parser import HTMLParser from html.entities import html5 class HTMLTextExtractor(HTMLParser): """ Adaption of http://stackoverflow.com/a/7778368/196732 """ def __init__(self): super().__init__() self.result = [] def handle_data(self, d): self.result.append(d) def handle_charref(self, number): codepoint = int(number[1:], 16) if number[0] in (u'x', u'X') else int(number) self.result.append(unichr(codepoint)) def handle_entityref(self, name): if name in html5: self.result.append(unichr(html5[name])) def get_text(self): return u''.join(self.result) def html_to_text(html): s = HTMLTextExtractor() s.feed(html) return s.get_text() 

这种方法对我来说是完美的工作,不需要额外的安装:

 import re import htmlentitydefs def convertentity(m): if m.group(1)=='#': try: return unichr(int(m.group(2))) except ValueError: return '&#%s;' % m.group(2) try: return htmlentitydefs.entitydefs[m.group(2)] except KeyError: return '&%s;' % m.group(2) def converthtml(s): return re.sub(r'&(#?)(.+?);',convertentity,s) html = converthtml(html) html.replace("&nbsp;", " ") ## Get rid of the remnants of certain formatting(subscript,superscript,etc).