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

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

来源:互联网 时间:2026-07-28 07:33:10

一、需求与技术选型

这件事的起点其实很单纯:输入一个网页链接,然后把正文干净地提取出来,顺便去掉导航、广告、侧边栏这些乱七八糟的东西,最后输出一个 Markdown 文件。

技术选型上,几个库直接锁定了方向:

  • readability(readability-lxml)

    负责正文去噪,核心引擎
  • markdownify

    把清洗后的 HTML 转成 Markdown,省事
  • BeautifulSoup

    做预清理,先干掉一些明显不需要的标签
  • Playwright

    用来应付那些需要客户端渲染的页面

初版代码实际上是 Doubao-Seed-Evolving 在一次对话中直接生成的,下面提炼的是关键实现路径和一些实际踩过的坑。

二、第一版实现

提示词扔进去,AI 直接给了这么一段核心结构:

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", "na v", "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 不能同时传 convertstrip,否则运行时会直接报错。预清理已经处理了 script 和 style,所以把 strip 删掉就解决了。这个修复也是模型看到报错后给出的。

三、实测三类网站

挑了三类不同类型的网页做测试,结果如实记录:

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

三个网页,一个直接成功,一个丢标题,一个彻底空壳。第一版其实没有预想中那么惨,但真实的世界里,不同网站暴露出的问题组合起来,比预设的场景有意思多了。

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

readability-lxml 的 Document.summary() 不读 og:title,只取 </code>。微信文章的标题格式恰好触发了它的解析 bug,结果就是 <code>[no-title]</code>。</p> <p>修复其实不难:在 <code>extract_markdown</code> 之前加一层语义化标题提取,先做一轮自主判断。</p> <pre><code>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() </code></pre> <h2>五、关键修复二:客户端渲染站点的空壳</h2> <p>头条这类站点,正文由 JS 异步加载,requests 拿到的只是 <code><div id="root"></code> 一个空壳。</p> <p>修复方案:加一个 <code>--render</code> 参数,用 Playwright 无头浏览器等渲染完成再提取。</p> <pre><code>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 </code></pre> <p>另外,微信和头条的图片大多是懒加载,真实地址藏在 <code>data-src</code> 里,需要补一段逻辑把 <code>data-src</code> 回填到 <code>src</code>,否则全文裂图。</p> <h2>六、最终用法</h2> <pre><code>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 </code></pre> <p>静态站不需要额外依赖,动态站加 <code>--render</code> 就行,清晰明了。</p> </div> <!-- 相关阅读 --> <div class="contccBox"> <div class="comtcTitle"> <h2><em class="em1"></em> 相关阅读 </h2> <a href="http://m.haoling.cc/wenzhang_list/53-1.html"> <em class="icon1 em2"></em> </a> <div class="clear"></div> </div> <ul class="corrReadList"> <li> <a href="http://www.haoling.cc/wenzhang/658257/"> <em></em> <span> LoopGain:开源Python AI环路增益监控工具,动态管控智能体迭代循环 </span> </a> </li> <li> <a href="http://www.haoling.cc/wenzhang/660711/"> <em></em> <span> OMPS-N20 L2 NM 甲醛 (HCHO) 总柱扫描轨道 </span> </a> </li> <li> <a href="http://www.haoling.cc/wenzhang/661002/"> <em></em> <span> Python和RPA的区别 </span> </a> </li> <li> <a href="http://www.haoling.cc/wenzhang/665587/"> <em></em> <span> RPA和Python在数据抓取方面有什么不同 </span> </a> </li> <li> <a href="http://www.haoling.cc/wenzhang/667729/"> <em></em> <span> Python语言下网络爬虫的技术特点及应用 </span> </a> </li> </ul> </div> <!-- 相关手游 --> <div class="contccBox"> <div class="comtcTitle"> <h2><em class="em1"></em> 相关下载 </h2> <a href="http://m.haoling.cc/sjyx_list/38-1-1.html"> <em class="icon1 em2"></em> </a> <div class="clear"></div> </div> <ul class="servertList"> <li> <a href="http://m.haoling.cc/xiazai/96840/" class="aImg"> <img src="http://m.haoling.cc/uploadfile/2021/0729/thumb_62_62_162752338962183.png" alt="含羞草实验研所入口"> </a> <dl> <dt> <a href="http://m.haoling.cc/xiazai/96840/"> 含羞草实验研所入口 </a></dt> <dd class="cont1"> 热门软件  |   </dd> <dd class="cont2"> <a href="javascript:void(0)"> 影视播放 </a> <a href="javascript:void(0)"> 视频观看 </a> <a href="javascript:void(0)"> 福利视频 </a> </dd> </dl> <a href="http://m.haoling.cc/xiazai/96840/" class="download"> 下载 </a> <div class="clear"></div> </li> <li> <a href="http://m.haoling.cc/xiazai/237936/" class="aImg"> <img src="http://m.haoling.cc/statics/images/nopic.gif" alt="关于柯南的沙雕网名有哪些"> </a> <dl> <dt> <a href="http://m.haoling.cc/xiazai/237936/"> 关于柯南的沙雕网名有哪些 </a></dt> <dd class="cont1"> 角色扮演  |  1 </dd> <dd class="cont2"> <a href="javascript:void(0)"> 网名 </a> </dd> </dl> <a href="http://m.haoling.cc/xiazai/237936/" class="download"> 下载 </a> <div class="clear"></div> </li> <li> <a href="http://m.haoling.cc/xiazai/101844/" class="aImg"> <img src="http://m.haoling.cc/statics/images/nopic.gif" alt="最新中性名字男女通用网名有哪些"> </a> <dl> <dt> <a href="http://m.haoling.cc/xiazai/101844/"> 最新中性名字男女通用网名有哪些 </a></dt> <dd class="cont1"> 角色扮演  |  1 </dd> <dd class="cont2"> <a href="javascript:void(0)"> 网名 </a> </dd> </dl> <a href="http://m.haoling.cc/xiazai/101844/" class="download"> 下载 </a> <div class="clear"></div> </li> <li> <a href="http://m.haoling.cc/xiazai/94905/" class="aImg"> <img src="http://m.haoling.cc/statics/images/nopic.gif" alt="关于蓝色说唱的网名有哪些"> </a> <dl> <dt> <a href="http://m.haoling.cc/xiazai/94905/"> 关于蓝色说唱的网名有哪些 </a></dt> <dd class="cont1"> 角色扮演  |  1 </dd> <dd class="cont2"> <a href="javascript:void(0)"> 网名 </a> </dd> </dl> <a href="http://m.haoling.cc/xiazai/94905/" class="download"> 下载 </a> <div class="clear"></div> </li> <li> <a href="http://m.haoling.cc/xiazai/31008/" class="aImg"> <img src="http://m.haoling.cc/uploadfile/2021/0330/thumb_62_62_161703918873594.jpg" alt="病娇姐姐的囚禁"> </a> <dl> <dt> <a href="http://m.haoling.cc/xiazai/31008/"> 病娇姐姐的囚禁 </a></dt> <dd class="cont1"> 角色扮演  |  392.1MB </dd> <dd class="cont2"> <a href="javascript:void(0)"> galgame </a> <a href="javascript:void(0)"> 病娇 </a> <a href="javascript:void(0)"> 恋爱 </a> </dd> </dl> <a href="http://m.haoling.cc/xiazai/31008/" class="download"> 下载 </a> <div class="clear"></div> </li> </ul> </div> </div> </div> </main> <script language="JavaScript" src="/api.php?op=count&id=673363&modelid=1"></script> <script type="text/javascript" src="/js/jquery.min.js"></script> <script type="text/javascript" src="/tongji.js"></script> <script src="/js/jquery.cookie.js"></script> <script src="/statics/js/jumbpc.js"></script> <footer class="1 - 673363"> <div class="cnFooter"> <ul class="reuscnt"> <li><a href="/"> 首页 </a></li> <li><a href="#"> 返回顶部 </a></li> </ul> <p> 版权所有 Copyright@2012-2013 haoling.cc </p> <!-- <p> <a href="https://beian.miit.gov.cn/" style="color:#FFFFFF ;text-decoration: none" rel="nofollow">湘ICP备2020018889号</a></p> --> </div> </footer> <script> (function(){ var el = document.createElement("script"); el.src = "https://lf1-cdn-tos.bytegoofy.com/goofy/ttzz/push.js?a9b5572ec2f2f718f384d2d47a74c5cc9d3e5221b39cb9c192000ab90a26ffaec112ff4abe50733e0ff1e1071a0fdc024b166ea2a296840a50a5288f35e2ca42"; el.id = "ttzz"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(el, s); })(window) </script> </body> </html>