使用BeautifulSoup的find_all按标签列表查询时无法获取后续目标
问题描述
执行以下代码:
from bs4 import BeautifulSoup soup = BeautifulSoup("<html><body><p>111</p><p>before<ul><li>222</li></ul>after</p></body></html>", "lxml") soup.find_all(["p", "li"])
得到结果:
[<p>111</p>, <p>before</p>, <li>222</li>]
期望结果中能包含“after”文本,要么作为第二个<p>元素的一部分,要么作为列表的第四个条目。
疑问1:这是预期行为吗?有没有办法获取“after”文本?
另外执行print(soup.prettify())后得到以下结果:
<html> <body> <p> 111 </p> <p> before </p> <ul> <li> 222 </li> </ul> after </body> </html>
此时<ul>和“after”不再属于第二个<p>元素,推测是无效HTML导致的,但仍有疑问:
疑问2:除了丢弃“after”外,有没有办法处理这种情况?
解答
1. 这是预期行为
HTML规范中,<p>是块级元素,不能嵌套其他块级元素(比如<ul>)。lxml作为HTML解析器会自动修正这种无效嵌套:当遇到<p>内部的<ul>时,会直接闭合前面的<p>标签,把<ul>移到<p>外面,原<p>的闭合标签会被忽略,导致“after”变成<body>下的独立文本节点,不在任何<p>或<li>标签内,所以soup.find_all(["p", "li"])无法捕获它。
2. 获取“after”文本的方法
- 提取所有文本节点:直接获取
<body>下的所有非空白文本内容text_nodes = [node for node in soup.body.stripped_strings] print(text_nodes) # 输出: ['111', 'before', '222', 'after'] - 定位兄弟文本节点:找到
<ul>之后的相邻文本节点ul_tag = soup.find("ul") after_text = ul_tag.next_sibling.strip() print(after_text) # 输出: 'after' - 换用html5lib解析器:该解析器对无效HTML的容错性更强,会保留原嵌套结构
先安装依赖:pip install html5lib
执行代码:from bs4 import BeautifulSoup soup = BeautifulSoup("<html><body><p>111</p><p>before<ul><li>222</li></ul>after</p></body></html>", "html5lib") print(soup.find_all(["p", "li"])) # 输出: [<p>111</p>, <p>before<ul><li>222</li></ul>after</p>, <li>222</li>]
3. 保留原结构意图的手动修正(基于lxml)
如果必须用lxml解析,可手动调整DOM结构,把“after”归回原<p>:
from bs4 import BeautifulSoup soup = BeautifulSoup("<html><body><p>111</p><p>before<ul><li>222</li></ul>after</p></body></html>", "lxml") second_p = soup.find_all("p")[1] ul_tag = soup.find("ul") # 提取after文本并添加到第二个p中 after_text = ul_tag.next_sibling.strip() second_p.append(after_text) # 将ul移回第二个p内部 second_p.append(ul_tag) print(soup.find_all(["p", "li"])) # 输出: [<p>111</p>, <p>beforeafter<ul><li>222</li></ul></p>, <li>222</li>]
内容的提问来源于stack exchange,提问作者steffen
相关产品推荐
相关产品推荐

