用 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。

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

相关推荐
吴佳浩1 小时前
一文讲透AI算力单位:TFLOPS、PFLOPS、TOPS、稀疏算力,到底怎么算、怎么比?
人工智能·ai编程·gpu
猫头虎1 小时前
什么是ZCode for GLM-5.2?
开发语言·人工智能·python·科技·算法·ai编程·ai写作
颜进强1 小时前
LangChain 从入门到实践:用最小案例理解 RAG 的 5 个核心抽象
前端·后端·ai编程
颜进强1 小时前
MCP 从入门到实践:用 TypeScript 实现第一个 MCP Server
前端·后端·ai编程
心运软件2 小时前
Python实战:中国大学排行榜数据采集与可视化大屏
后端·python
长不胖的路人甲2 小时前
什么是赫夫曼树(哈夫曼树 / Huffman Tree)
python·算法·霍夫曼树
魔镜er2 小时前
03-张量
人工智能·pytorch·python
37.2℃9953 小时前
Claude Design哪个公司技术好
python·设计模式
157092511343 小时前
【无标题】
开发语言·python·算法