用 Doubao-Seed-Evolving + Python 免费写一个网页正文提取工具(实战教程)

本文记录一个完整实践:用 Python 从零实现一个「URL → 干净 Markdown」的网页正文提取工具。重点不是 Hello World,而是真实跑三个网站后遇到的三类坑,以及对应的修复方案。本轮借助 Doubao-Seed-Evolving(字节刚升级、1M 上下文的 Coding 模型)完成初版与两次修复,代码可复现。

一、需求与技术选型

目标:输入一个网页 URL,输出去除导航/广告/侧边栏后的正文 Markdown。

选型:

  • readability(readability-lxml)做正文去噪
  • markdownify 把清洗后的 HTML 转 Markdown
  • BeautifulSoup 做预清理
  • Playwright 处理客户端渲染站点

初版代码由 Doubao-Seed-Evolving 在一次对话里生成,下文是提炼后的关键实现与踩坑。

二、第一版实现

这是我输入提示词后AI给的回答:

核心结构长这样(节选):

python 复制代码
import requests
from bs4 import BeautifulSoup
from readability import Document
import markdownify as md

DEFAULT_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..."

def fetch_url(url, timeout=15, verify_ssl=True):
    if not url.startswith(("http://", "https://")):
        url = "https://" + url
    headers = {"User-Agent": DEFAULT_UA}
    resp = requests.get(url, headers=headers, timeout=timeout, verify=verify_ssl)
    resp.raise_for_status()
    return resp.text

def extract_markdown(html, page_url=""):
    soup = BeautifulSoup(html, "lxml")
    for tag in soup.find_all(["script", "style", "nav", "aside", "footer"]):
        tag.decompose()
    doc = Document(str(soup), url=page_url)
    content_html = doc.summary()
    return md(content_html, heading_style="ATX", convert=["table", "pre"])

注意:第一版有个坑------markdownify 不能同传 convert 和 strip,否则运行时直接报错。预清理已处理 script/style,所以删掉 strip 即可。这个修复也是模型在看到报错后给出的。

三、实测三类网站

我选了 3 个不同类型的网页做测试,如实记录结果:

网站类型 结果 现象
技术博客(SSR) 代码块、语言标注、表格全保留
公众号 ⚠️ 正文 OK,标题变 no-title
头条(CSR) 仅 # no-title,正文空

三个网页,一个直接成功,一个丢标题,一个彻底空壳。第一版没有框架预设里那么惨,但真实的问题比预设的更有意思,不同的网站暴露出不同的短板。

四、关键修复一:标题提取退化

readability-lxml 的 Document.summary() 不读 og:title,只取 <title>。微信文章标题格式会触发其解析 bug,得到 no-title

修复:在 extract_markdown 前加一层语义化标题提取。

python 复制代码
def extract_title(soup, doc):
    og = soup.find("meta", property="og:title")
    if og and og.get("content"):
        return og["content"].strip()
    tw = soup.find("meta", attrs={"name": "twitter:title"})
    if tw and tw.get("content"):
        return tw["content"].strip()
    h1 = soup.find("h1")
    if h1 and h1.get_text(strip=True):
        return h1.get_text(strip=True)
    return doc.short_title()

五、关键修复二:客户端渲染站点的空壳

头条这类站点正文由 JS 异步加载,requests 拿到的只是 <div id="root"> 空壳。

修复:加 --render 参数,用 Playwright 无头浏览器等渲染完成再提取。

python 复制代码
from playwright.sync_api import sync_playwright

def fetch_rendered(url):
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        page = browser.new_page()
        page.goto(url, wait_until="networkidle")
        html = page.content()
        browser.close()
    return html

另:微信/头条图片是懒加载,真实地址在 data-src,需补一段把 data-src 回填到 src,否则全文裂图。

六、最终用法

bash 复制代码
python extract_article.py https://juejin.cn/post/xxx -o article.md
python extract_article.py https://www.toutiao.com/xxx --render -o toutiao.md

静态站不需要额外依赖,动态站加 --render。

写工具的价值不在第一版多漂亮,而在能不能把踩过的坑固化成可复用的能力。

相关推荐
pjj198546 小时前
机器学习-NumPy2
人工智能·python·机器学习
tang777896 小时前
Scrapy框架动态IP自动轮换集成配置教程
爬虫·tcp/ip·scrapy·架构·爬虫代理·动态ip
OPEN-F6 小时前
Python进阶教程:单元测试与代码质量
开发语言·python·单元测试
Logintern096 小时前
装饰器和洋葱模式的区分
开发语言·python·架构
星核0penstarry6 小时前
Copilot 用 GLM:自备密钥和官方内置,到底差在哪?
copilot·运维开发·ai编程·api聚合平台
呆呆敲代码的小Y7 小时前
10 分钟搞懂 cua:开源 AI 操作电脑基础设施,附 Python 沙箱与 Agent 上手代码
人工智能·python·开源·ai agent·awesome·llm应用·cua
工具分享7 小时前
带店托管必用爆单AI选品,一人公司轻松掌握
人工智能·python
l1258657 小时前
# RAG低延迟架构设计:从5秒到500ms的优化全链路
数据库·人工智能·python·langchain
杨丰玮4187 小时前
从零手写Java飞机躲障碍游戏|Swing绘图、鼠标跟随、计时器碰撞检测实战(五)
java·python·游戏·游戏引擎·图形渲染·动画·贴图
frjc7 小时前
阿里云 ACK 环境 Arthas 在线调试指南
开发语言·python