因为网页结构千差万别且大量依赖JS动态渲染,requests+BeautifulSoup仅解析静态HTML,无法处理无规范语义标签、内联脚本注入、反爬跳转及中文编码混乱等问题,导致正文提取漏、错、空。

为什么直接用 requests + BeautifulSoup 提取正文经常失败?
因为网页结构千差万别:广告、导航、侧边栏、评论区、JS 渲染内容混杂在 <div> 里,靠写死 CSS 选择器(比如 <code>article 或 .content)几乎必然漏内容或抓到噪音。更麻烦的是,很多现代站点用 React/Vue 动态渲染,requests 拿到的 HTML 是空壳,根本没正文。
- 真实场景中,约 70% 的目标页面没有规范的
<article></article>或itemprop="articleBody"标记 -
BeautifulSoup解析后调用.get_text()会把页脚版权、弹窗文案、分页导航全吞进来 - 遇到
document.write()或fetch()加载正文时,静态解析完全失效
用 trafilatura 替代手写规则,但得避开它的默认陷阱
trafilatura 是目前 Python 生态里最稳的开源正文提取器,底层融合了 DOM 结构分析、文本密度计算和噪声过滤,比 readability-lxml 兼容性更好,也比 newspaper3k 更轻量。但它开箱即用的配置容易误杀内容。
- 默认启用
favor_precision=True,对短文本(如博客摘要、产品描述)过于保守,常返回空字符串 - 不设
timeout时,遇到反爬跳转或重定向链过长会卡住(尤其中文站点常用 302+JS 跳转) - 若目标页面含大量
<script></script>内联 JSON-LD,需手动关掉include_comments=False,否则可能把 schema 数据当正文
实操建议:
from trafilatura import fetch_url, extract
html = fetch_url("https://example.com/post", timeout=10)
text = extract(html, include_comments=False, favor_precision=False, no_fallback=True)
遇到 JavaScript 渲染页面,playwright 启动无头浏览器是底线方案
当 trafilatura 返回空或明显残缺(比如只有标题没段落),基本可判定正文由 JS 动态注入。这时必须等 DOM 渲染完成再提取——但别用 Selenium,它启动慢、内存泄漏多;playwright 启动快、API 简洁,且支持自动等待 networkidle 状态。
立即学习“Python免费学习笔记(深入)”;
- 务必加
wait_until="networkidle",而非"domcontentloaded",否则可能拿到未加载完的骨架屏 - 提取前先删掉常见干扰节点:
page.evaluate("document.querySelectorAll('header, footer, nav, .ads').forEach(el => el.remove())") - 避免用
page.content()全量获取 HTML,改用page.inner_text("main")或page.text_content("article")直接拿文本,减少噪声
示例关键片段:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto("https://spa-site.com/article", wait_until="networkidle")
page.evaluate("document.querySelectorAll('header, aside, .taboola, #comments').forEach(e => e.remove())")
text = page.inner_text("main") or page.inner_text("article") or page.inner_text("body > *:not(script):not(style)")
中文页面要额外处理编码与标点,否则乱码或断句错乱
trafilatura 和 playwright 默认按 UTF-8 解码,但不少国内小站仍用 GBK 或 GB2312,且 HTML meta 声明常被忽略或写错。更隐蔽的问题是:中文标点(如「」、『』、—、……)在正则清洗时容易被误删,导致语义断裂。
- 抓取前先用
chardet.detect()查原始编码,再用html.encode(detected_encoding).decode("utf-8")强制转码 - 提取后别急着
.replace("\n", " "),先保留段落换行,用re.sub(r"([。!?;])\s+", r"\1\n", text)按句末标点智能分段 - 警惕
trafilatura对「引号嵌套」的误切——它会把他说:“你去‘北京’吗?”拆成三段,此时需关掉strip_html_tags=True改用纯文本后处理
真正难的不是识别正文,而是让提取结果在不同站点间保持语义连贯性。比如一个带表格的财报页面,trafilatura 会丢掉所有行列结构,而 playwright 抓到的又是带换行的乱序文本——这时候就得根据下游用途(NLP 分析 or PDF 导出)决定是否保留 HTML 片段,而不是执着于“纯文本”。


















