
Habr 文章的实际内容并非直接写在 HTML 的 标签中,而是以 JSON 形式嵌入 标签的 window.__INITIAL_STATE__ 变量里;直接用 BeautifulSoup 解析 DOM 会失败或返回空值。本文详解如何通过正则提取+JSON 解析安全获取真实正文。
habr 文章的实际内容并非直接写在 html 的 `
` 标签中,而是以 json 形式嵌入 `<script>` 标签的 `window.__initial_state__` 变量里;直接用 beautifulsoup 解析 dom 会失败或返回空值。本文详解如何通过正则提取+json 解析安全获取真实正文。</script>
Habr(Хабр)采用服务端渲染(SSR)结合客户端状态管理(如 Vuex/Redux 风格),其文章正文、标题、摘要等关键数据并不以静态 HTML 形式存在,而是序列化为 JavaScript 对象,注入页面 <script> 标签中。因此,即使你成功定位到 <article> 或 <div class="article-formatted-body">,其内部的 <p> 标签往往为空或仅含占位符——这正是你遇到 NameError: name 'text' is not defined(因循环中 text 未在作用域外定义)以及“提取到空内容”的根本原因。</script>
✅ 正确做法是:跳过 DOM 解析,直取前端初始化数据。Habr 将全部文章元数据和富文本内容存于 window.__INITIAL_STATE__ 全局变量中,该变量位于页面源码中形如 <script> window.__INITIAL_STATE__ = {"articlesList":{...}} </script> 的 script 块内。
以下是稳定可靠的提取方案:
import re
import json
import requests
from bs4 import BeautifulSoup
URL = "https://habr.com/ru/hubs/gamedev/articles/"
# 注意:使用 .text 而非 .content,确保获得字符串而非字节流(便于正则匹配)
page_html = requests.get(URL).text
# 使用正则精准捕获 __INITIAL_STATE__ 的 JSON 字符串主体(排除结尾分号和可能的换行)
match = re.search(r"window\.__INITIAL_STATE__=({.*?});", page_html, re.DOTALL)
if not match:
raise ValueError("Failed to locate __INITIAL_STATE__ in page source")
json_data = json.loads(match.group(1))
# 提取最新一篇文章(按发布时间倒序)
articles = json_data["articlesList"]["articlesList"].values()
latest_article = sorted(
articles,
key=lambda a: a["timePublished"],
reverse=True
)[0]
# 解析标题(HTML 字符串 → 纯文本)
title = BeautifulSoup(latest_article["titleHtml"], "html.parser").get_text()
# 解析导语(leadData.textHtml 含完整 HTML 格式正文片段)
lead_html = latest_article["leadData"]["textHtml"]
body_text = BeautifulSoup(lead_html, "html.parser").get_text()
print("标题:", title)
print("正文:", body_text)
⚠️ 关键注意事项:
-
不要依赖 soup.find('p'):Habr 的
是占位符或服务端生成的骨架,真实内容由 JS 动态注入;
- 务必用 .text 获取响应体:.content 返回 bytes,正则匹配易出错;
- re.DOTALL 标志不可省略:确保 . 匹配换行符,因 JSON 可能跨多行;
- 添加异常处理:__INITIAL_STATE__ 结构可能随前端升级微调,建议用 try/except 包裹 json.loads() 和字段访问;
- 遵守 robots.txt 与频率限制:Habr 明确禁止高频爬取,生产环境请添加 time.sleep() 并检查 response.headers['X-RateLimit-Remaining']。
该方法不仅解决当前问题,更代表了一类现代 SPA/SSR 网站(如 Habr、Medium、知乎专栏)的通用解析范式:优先挖掘前端状态数据,而非硬啃渲染后的 DOM。











