python: 获取开始和结束 html 标签
python: get opening and closing html tags
问题:
如何使用 python (3.6) 找到所有开始和结束 HTML 标签的文本。
这需要是准确的文本,保留空格并且可能是非法的 html:
# input
html = """<p>This <a href="book"> book </a > will help you</p attr="e">"""
# desired output
output = ['<p>', '<a href="book">', '</a >', '</p attr="e">']
尝试解决方案:
显然这在 Beautifulsoup 中是不可能的,这个问题:How to get the opening and closing tag in beautiful soup from HTML string? links to html.parser
实现自定义解析器很容易。您可以使用 self.get_starttag_text()
来获取与最后打开的标签对应的文本。但是由于某种原因,没有类似的方法get_endtag_text()
。
这意味着我的解析器产生了这个输出:
class MyHTMLParser(HTMLParser):
def __init__(self):
super().__init__()
self.tags = []
def reset_stored_tags(self):
self.tags = []
def handle_starttag(self, tag, attrs):
self.tags.append(self.get_starttag_text())
def handle_endtag(self, tag):
self.tags.append(self.get_endtag_text())
def handle_startendtag(self, data):
self.tags.append(self.get_starttag_text())
# input
input_doc = """<p>This <a href="book"> book </a> will help you</p>"""
parser = MyHTMLParser()
parser.feed(input_doc)
print(parser.tags)
# ['<p>', '<a href="book">', '<a href="book">', '<a href="book">']
handle_endtag
的 tag
参数只是一个字符串 "a"
或 "p"
,而不是可以提供整个标签的自定义数据类型。
您可以使用递归并迭代 soup.contents
属性:
from bs4 import BeautifulSoup as soup
html = """<p>This <a href="book"> book </a> will help you</p>"""
def attrs(_d):
if _d.name != '[document]':
_attrs = ' '.join(f'{a}="{b}"' for a, b in getattr(_d, 'attrs', {}).items())
yield f'<{_d.name}>' if not _attrs else f'<{_d.name} {_attrs}>'
for i in _d.contents:
if not isinstance(i, str):
yield from attrs(i)
if _d.name != '[document]':
yield f'</{_d.name}>'
print(list(attrs(soup(html, 'html.parser'))))
输出:
['<p>', '<a href="book">', '</a>', '</p>']
编辑:对于无效的HTML,可以使用re
:
import re
html = """<p>This <a href="book"> book </a > will help you</p attr="e">"""
new_results = re.findall('\<[a-zA-Z]+.*?\>|\</[a-zA-Z]+.*?\>', html)
输出:
['<p>', '<a href="book">', '</a >', '</p attr="e">']
虽然来自@Ajax1234 的答案包含一些不错的 python + beautifulsoup,但我发现它非常不稳定。主要是因为我需要 html 标签的确切字符串。 该方法找到的每个标记都必须出现在 html 文本中。 这会导致以下问题:
从HTML解析标签名称和属性并插入
一起形成标签 yield f'<{_d.name}>' if not _attrs else f'<{_d.name} {_attrs}>'
的字符串。这消除了标签中的额外空格:<p >
变为 <p>
它总是生成一个结束标记,即使标记中有 none
列表属性失败:<p class="a b">
变为 <p class="[a, b]">
通过在处理之前清理 HTML 可以部分解决空白问题。我使用了 bleach,但这可能过于激进。值得注意的是,您必须在使用之前指定一个接受标签列表。
更好的方法是围绕 html.parser.HTMLParser 进行薄包装。
这是我在问题中已经开始的事情,这里的区别是我自动添加生成结束标记。
from html.parser import HTMLParser
class MyHTMLParser(HTMLParser):
def __init__(self):
super().__init__()
self.tags = []
def handle_starttag(self, tag, attrs):
self.tags.append(self.get_starttag_text())
def handle_endtag(self, tag):
self.tags.append(f"</{tag}>")
parser = MyHTMLParser();
parser.feed("""<p > Argh, whitespace and p is not closed </a>""")
parser.tags # ['<p >', '</a>']
这解决了上面提到的问题,但它有一个缺点,它没有查看结束标记的实际文本。如果结束标记中有额外的参数或空格,解析将不会显示它们。
问题:
如何使用 python (3.6) 找到所有开始和结束 HTML 标签的文本。 这需要是准确的文本,保留空格并且可能是非法的 html:
# input
html = """<p>This <a href="book"> book </a > will help you</p attr="e">"""
# desired output
output = ['<p>', '<a href="book">', '</a >', '</p attr="e">']
尝试解决方案:
显然这在 Beautifulsoup 中是不可能的,这个问题:How to get the opening and closing tag in beautiful soup from HTML string? links to html.parser
实现自定义解析器很容易。您可以使用 self.get_starttag_text()
来获取与最后打开的标签对应的文本。但是由于某种原因,没有类似的方法get_endtag_text()
。
这意味着我的解析器产生了这个输出:
class MyHTMLParser(HTMLParser):
def __init__(self):
super().__init__()
self.tags = []
def reset_stored_tags(self):
self.tags = []
def handle_starttag(self, tag, attrs):
self.tags.append(self.get_starttag_text())
def handle_endtag(self, tag):
self.tags.append(self.get_endtag_text())
def handle_startendtag(self, data):
self.tags.append(self.get_starttag_text())
# input
input_doc = """<p>This <a href="book"> book </a> will help you</p>"""
parser = MyHTMLParser()
parser.feed(input_doc)
print(parser.tags)
# ['<p>', '<a href="book">', '<a href="book">', '<a href="book">']
handle_endtag
的 tag
参数只是一个字符串 "a"
或 "p"
,而不是可以提供整个标签的自定义数据类型。
您可以使用递归并迭代 soup.contents
属性:
from bs4 import BeautifulSoup as soup
html = """<p>This <a href="book"> book </a> will help you</p>"""
def attrs(_d):
if _d.name != '[document]':
_attrs = ' '.join(f'{a}="{b}"' for a, b in getattr(_d, 'attrs', {}).items())
yield f'<{_d.name}>' if not _attrs else f'<{_d.name} {_attrs}>'
for i in _d.contents:
if not isinstance(i, str):
yield from attrs(i)
if _d.name != '[document]':
yield f'</{_d.name}>'
print(list(attrs(soup(html, 'html.parser'))))
输出:
['<p>', '<a href="book">', '</a>', '</p>']
编辑:对于无效的HTML,可以使用re
:
import re
html = """<p>This <a href="book"> book </a > will help you</p attr="e">"""
new_results = re.findall('\<[a-zA-Z]+.*?\>|\</[a-zA-Z]+.*?\>', html)
输出:
['<p>', '<a href="book">', '</a >', '</p attr="e">']
虽然来自@Ajax1234 的答案包含一些不错的 python + beautifulsoup,但我发现它非常不稳定。主要是因为我需要 html 标签的确切字符串。 该方法找到的每个标记都必须出现在 html 文本中。 这会导致以下问题:
从HTML解析标签名称和属性并插入 一起形成标签
yield f'<{_d.name}>' if not _attrs else f'<{_d.name} {_attrs}>'
的字符串。这消除了标签中的额外空格:<p >
变为<p>
它总是生成一个结束标记,即使标记中有 none
列表属性失败:
<p class="a b">
变为<p class="[a, b]">
通过在处理之前清理 HTML 可以部分解决空白问题。我使用了 bleach,但这可能过于激进。值得注意的是,您必须在使用之前指定一个接受标签列表。
更好的方法是围绕 html.parser.HTMLParser 进行薄包装。 这是我在问题中已经开始的事情,这里的区别是我自动添加生成结束标记。
from html.parser import HTMLParser
class MyHTMLParser(HTMLParser):
def __init__(self):
super().__init__()
self.tags = []
def handle_starttag(self, tag, attrs):
self.tags.append(self.get_starttag_text())
def handle_endtag(self, tag):
self.tags.append(f"</{tag}>")
parser = MyHTMLParser();
parser.feed("""<p > Argh, whitespace and p is not closed </a>""")
parser.tags # ['<p >', '</a>']
这解决了上面提到的问题,但它有一个缺点,它没有查看结束标记的实际文本。如果结束标记中有额外的参数或空格,解析将不会显示它们。