BeautifulSoup根据源位置查找标记



我想在一个html文档中找到文本位于某个偏移量内的所有标记,比如100:200。这个偏移量是原始HTML文件的偏移量,所以如果我执行f.read()[100:200],这表示我要查找的文本段。BeautifulSoup让我走到了这一步,因为对于每个标签,我都可以从tag.sourcepos开始。使用这个,我可以得到最接近序列开头的元素。但是,我不知道如何将原始偏移量与元素文本中的偏移量对齐。也许如果我能得到tag本身的长度,我可以使用它,但我看不出有任何方法可以做到这一点。

这里是第二个断言失败的最小尝试:

<html><head><title></title></head><body><span style="white-space: pre; font-family: 'Courier New', Courier;"><br><br><br><br><br><br><br>AAA<br><br><br><br><br></span></body></html>
from bs4 import BeautifulSoup
with open('test.htm') as f:
doc_content = f.read()
soup = BeautifulSoup(doc_content, 'html5lib')
spans = soup.find_all('span')
start = 137
end = 140
found = doc_content[start:end]
print(found)
assert found == 'AAA'
# Find first span before target start
best_span = max(s for s in spans if s.sourcepos < start)
offset = best_span.sourcepos + 1
breakpoint()
found = best_span.encode_contents()[start-offset:end-offset]
found = found.decode('utf8')
print(found)
assert(found == 'AAA')

根本原因似乎是BS4通过在最初不存在的<br>中插入斜杠来"帮助",从而破坏了偏移量。

根本原因似乎是BS4通过在
中插入原来不存在的斜杠来"帮助",从而损坏偏移量。

您可以通过使用encode_contents()formatter='html5'参数来停止此操作。请参阅文档-输出格式化程序。

当你更改这一行时,你的第二个断言通过了:

found = best_span.encode_contents()[start-offset:end-offset]

到此:

found = best_span.encode_contents(formatter='html5')[start-offset:end-offset]

尽管你真的想要拼接一个str,而不是bytes。编码一些utf-8码点需要超过1个字节。您最终会截断代码单元,导致utf-8序列格式错误。所以你想把它移到下一行:

found = best_span.encode_contents(formatter='html5')
found = found.decode('utf8')[start-offset:end-offset]

在这种特殊情况下,它是有效的。但是,当您将formatter设置为html5时,某些字符将被编码为HTML实体,因此输出可能与输入不同。你原来的开始和结束偏移可能是关闭的。

如果你的html格式不完美,解析器会试图修复它,所以输出与输入不一样。请参阅文档-解析器之间的差异。

出于这两个原因,您应该解析原始html,输出它,然后解析输出的。这样,输入应该始终与输出相同。

with open('test.htm') as f:
doc_content = f.read()
soup = BeautifulSoup(doc_content, 'html5lib')
doc_content = soup.encode_contents(formatter='html5').decode('utf-8')
soup = BeautifulSoup(doc_content, 'html5lib')

如果我没有错,你只需要任何HTML文件中的标记。如果是,那么您可以简单地在Python中使用regex。这是的代码

import re
html_code='''<html><head><title></title></head><body><span style="white-space: pre; font-family: 'Courier New', Courier;"><br><br><br><br><br><br><br>AAA<br><br><br><br><br></span></body></html>'''
tags = re.findall(r'<[^>]+>', html_code)
for tag in tags:
if ' 'in tag:
tag_without_att = tag.split(' ')[0]+'>'
else:
tag_without_att = re.sub(r's?w+="[wd]+"', '', tag)
print(tag_without_att)

最新更新