
Habr 文章内容实际由 JavaScript 动态注入,存储在 标签内的 window.__INITIAL_STATE__ 全局变量中,直接用 BeautifulSoup 解析 HTML 无法获取真实文本,需通过正则匹配并解析 JSON 数据提取。
habr 文章内容实际由 javascript 动态注入,存储在 `<script>` 标签内的 `window.__initial_state__` 全局变量中,直接用 beautifulsoup 解析 html 无法获取真实文本,需通过正则匹配并解析 json 数据提取。</script>
现代前端网站(如 Habr)普遍采用服务端渲染(SSR)或客户端渲染(CSR)架构,其可见内容往往不直接存在于初始 HTML 的 DOM 结构中,而是由 JavaScript 在运行时动态写入。你遇到的 NameError: name 'text' is not defined 错误,本质源于变量作用域问题(text 在 for 循环内定义,循环结束后不可访问),但更深层的问题是:目标文本根本不在 标签的静态 HTML 中
观察你提供的 HTML 片段:
<div class="article-formatted-body ..."> <p> "Сегодня первой игре из серии DOOM исполняется ровно 30 лет! ..." </p> <p></p> </div>
这段看似包含文本的
实际是占位结构或服务端渲染残留——Habr 使用 React + Redux 架构,真实文章数据被序列化为 JSON,嵌入页面末尾的 <script> 标签中,形如:</script>
<script>
window.__INITIAL_STATE__ = {"articlesList":{"articlesList":{"123456":{"titleHtml":"...", "leadData":{"textHtml":"<p>Сегодня..."}}}}};
</script>
因此,正确做法是:
- 获取原始 HTML 字符串(使用 .text 而非 .content,确保编码正确);
- 用正则提取 __INITIAL_STATE__ 的 JSON 字符串;
- 解析 JSON,定位文章数据路径(如 data["articlesList"]["articlesList"]);
- 对 textHtml 字段再次用 BeautifulSoup 清洗 HTML,提取纯文本。
✅ 完整可运行示例:
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() # 确保请求成功
# 关键:从 script 标签中提取初始化数据
match = re.search(r"window\.__INITIAL_STATE__=({.*?});", response.text, re.DOTALL)
if not match:
raise ValueError("Failed to extract __INITIAL_STATE__")
try:
data = json.loads(match.group(1))
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON in __INITIAL_STATE__: {e}")
# 提取最新文章(按发布时间倒序)
articles = data.get("articlesList", {}).get("articlesList", {})
if not articles:
raise ValueError("No articles found in __INITIAL_STATE__")
latest_article = sorted(
articles.values(),
key=lambda a: a.get("timePublished", ""),
reverse=True
)[0]
# 解析标题与正文 HTML
title = latest_article.get("titleHtml", "").strip()
lead_html = latest_article.get("leadData", {}).get("textHtml", "")
# 使用 BeautifulSoup 安全提取纯文本
soup_lead = BeautifulSoup(lead_html, "html.parser")
clean_text = soup_lead.get_text(strip=True)
print("标题:", title)
print("正文:", clean_text)
⚠️ 注意事项:
- 不要依赖 find("article") 等 DOM 查找:Habr 的首页 HTML 是骨架页,真实内容由 JS 注入,静态解析必然失败;
- 始终校验正则匹配结果和 JSON 结构:Habr 可能调整 __INITIAL_STATE__ 命名或嵌套路径,建议打印 data.keys() 快速调试;
- 添加异常处理与 HTTP 状态检查:生产环境必须捕获网络错误、JSON 解析异常及字段缺失;
- 遵守 robots.txt 与反爬策略:Habr 明确禁止自动化抓取未授权内容,请仅用于学习且控制请求频率。
该方法绕过了浏览器渲染,直击数据源头,是解析现代 SPA(单页应用)网站内容的通用范式。











