本文详解为何直接用 BeautifulSoup 解析 Habr 文章页的 <p> 标签无法获取预期文本,并揭示其内容实际由前端 JavaScript 动态注入(存储在 window.__INITIAL_STATE__ 中),提供完整、稳定、可复用的解析方案。
本文详解为何直接用 beautifulsoup 解析 habr 文章页的 `
` 标签无法获取预期文本,并揭示其内容实际由前端 javascript 动态注入(存储在 `window.__initial_state__` 中),提供完整、稳定、可复用的解析方案。
Habr(如 habr.com/ru/hubs/gamedev/articles/)采用现代前端框架(如 Vue/React)构建,文章列表与详情内容并非静态 HTML,而是通过客户端 JavaScript 渲染。当你使用 requests.get().content 获取响应后,BeautifulSoup 解析的是初始 HTML 骨架——其中 <div class="article-formatted-body"> 内部看似有 <p> 标签,但真实文本并不在 DOM 中,而被序列化为 JSON 嵌入 <script> 标签内(通常以 window.__INITIAL_STATE__= 开头)。因此,post.find_all('p') 要么返回空标签,要么因作用域错误(如 text 变量未定义)抛出 NameError。
正确做法是:绕过 DOM 解析,直接提取并解析前端状态数据。Habr 将结构化文章数据(含标题、摘要、正文 HTML)存于 window.__INITIAL_STATE__ 全局变量中。我们可通过正则匹配该脚本片段,再用 json.loads() 解析为 Python 字典,最后用 BeautifulSoup 安全提取纯文本。
以下是推荐的完整实现:
import re
import json
import requests
from bs4 import BeautifulSoup
URL = "https://habr.com/ru/hubs/gamedev/articles/"
response = requests.get(URL)
response.raise_for_status() # 确保请求成功
# 从 HTML 中提取 __INITIAL_STATE__ 的 JSON 字符串(注意:需用 .text 而非 .content 以正确解码)
html_text = response.text
match = re.search(r"window\.__INITIAL_STATE__=({.*?});", html_text, re.DOTALL)
if not match:
raise ValueError("Failed to locate __INITIAL_STATE__ in page")
initial_state = json.loads(match.group(1))
# 导航至文章列表数据(路径可能随前端更新微调,当前为:articlesList → articlesList → {id: article})
articles_dict = initial_state.get("articlesList", {}).get("articlesList", {})
if not articles_dict:
raise ValueError("No articles found in __INITIAL_STATE__")
# 按发布时间倒序取最新一篇(Habr 列表页默认最新在前)
latest_article = sorted(
articles_dict.values(),
key=lambda a: a.get("timePublished", ""),
reverse=True
)[0]
# 提取标题(HTML 格式,需清洗)和摘要正文(textHtml 字段含富文本)
title = BeautifulSoup(latest_article["titleHtml"], "html.parser").get_text(strip=True)
lead_text = BeautifulSoup(latest_article["leadData"]["textHtml"], "html.parser").get_text(strip=True)
print("标题:", title)
print("摘要:", lead_text)✅ 关键要点说明:
- 使用 response.text(而非 .content)确保字符编码正确,避免 JSON 解析失败;
- 正则 re.DOTALL 标志支持跨行匹配,{.*?} 非贪婪捕获完整 JSON 对象;
- leadData["textHtml"] 是 Habr 存储文章首段富文本的字段,BeautifulSoup(...).get_text() 安全剥离所有 HTML 标签;
- 切勿依赖 find("article") 或 find_all("p") —— 这些元素在初始 HTML 中为空或占位,真实内容仅存在于 JS 状态中;
- 若需全文而非仅摘要,应查找 bodyHtml 字段(部分文章存在),或通过文章详情页 API 获取(需额外请求)。
此方法稳定、高效,规避了 Selenium 等重量级工具,是解析 Habr、Medium、Dev.to 等 SSR/CSR 混合站点的通用范式。

















