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

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

相关推荐
小虎AI生活6 小时前
反推任意图片的神技能,WorkBuddy和Loomy装上就能用
aigc·ai编程
金銀銅鐵6 小时前
带您看懂字节码指令(二):空循环里有什么
java·后端·ai编程
JAVA前线7 小时前
用《实践论》分析前沿部署工程师:AI时代听见炮声的人
架构·ai编程
投图匠7 小时前
十种不同风格的 CTP接口 框架
开发语言·python
2601_967980118 小时前
深入理解词向量算法及其在Python中的实践
python·word2vec·glove·fasttext·词向量
129Lab9 小时前
电池循环测试数据自动化分析:用Python+AI工具构建一键式寿命评估流水线
python·数据分析·电池测试·数据自动化·寿命评估
treasure1409 小时前
【复古】浅尝Django前后端不分离
前端·css·python·django·bootstrap·html·web
Dmz丶10 小时前
前端转 AI 100 天 Day 44:前端集成实战——用 React 给 Agent 做个正式界面
python
jay神10 小时前
基于 YOLOv8 的手势识别系统(Web 版)
前端·人工智能·python·深度学习·yolo·计算机视觉·计算机毕业设计
DeepVisionary10 小时前
阿里云上线旗舰多模态新模型,不公布跑分且限时14天退役
python·自动化