夜雨聆风学习资料网

ARTICLE · 998563

终结 PDF 解析噩梦:深度体验高性能 Python 文档智能库 Kreuzberg

终结 PDF 解析噩梦:深度体验高性能 Python 文档智能库 Kreuzberg

这两年大家都在搞大模型,不管你是做 RAG 还是搭企业内部的知识库,第一步永远是绕不开的“文档解析”。

说实话,这活儿听着简单,干起来却极其折磨人。客户丢过来的数据源五花八门:有排版奇葩的 PDF,有陈年的 Word 文档,有直接截图保存的图片,甚至还有乱七八糟的 HTML 网页。

以前我们是怎么做的?处理 PDF 用 pdfplumber 或者 PyPDF2,遇到扫描件只能再硬着头皮上 pytesseract,处理 Word 又得切到 python-docx。最后写出来的代码,充满了无数个 if-else 分支,不仅跑起来慢得像牛车,稍微遇到点特殊编码还会直接报错崩溃。

直到最近,我在重构一个知识库的数据接入模块时,挖到了 Kreuzberg 这个极其硬核的开源库。

为什么说它硬核?

Kreuzberg 的底层是基于 Rust 开发的,这意味着它的解析速度极快。但它最吸引我的,是它在 Python 层面提供了一个极其克制且统一的异步 API。

不管你传入的是什么后缀的文件,对它来说,都是一个“输入”,它只负责给你返回干净的“文本”。

我们先通过一个流程图,直观对比一下传统解析逻辑和 Kreuzberg 的逻辑:

graph TD    A[收到一份待处理文档] --> B{传统解析逻辑: 判断文件类型}    B -- .pdf后缀 --> C[调用 PyPDF2/pdfplumber]    B -- .docx后缀 --> D[调用 python-docx]    B -- 图片格式 --> E[调用 OCR 引擎]    C --> F[各种复杂的错误捕获与重试]    D --> F    E --> F    F --> G[拼凑成最终文本]    A --> H{Kreuzberg 解析逻辑}    H --> I[直接调用 extract_file 接口]    I --> J[底层 Rust 引擎自动识别并处理]    J --> K[返回结构化文本结果]    style B stroke-dasharray: 5 5    style F stroke-dasharray: 5 5

废话不多说,直接上代码

要用它,第一步当然是安装:

pip install kreuzberg

如果你平时处理的大多是本地文件,传统的写法可能要写一堆上下文管理器。而在 Kreuzberg 里,只需要调用它的异步接口。

下面这段代码展示了如何同时处理一个 PDF 和一个 Word 文档:

import asynciofrom pathlib import Pathfrom kreuzberg import extract_fileasync def process_local_documents():    # 假设我们有两个不同格式的文件    pdf_path = Path("sample_report.pdf")    word_path = Path("company_policy.docx")    print("开始解析文档...")    # 异步并发执行,互不阻塞    try:        pdf_result, word_result = await asyncio.gather(            extract_file(pdf_path),            extract_file(word_path)        )        # 返回的结果不仅有文本,还包含了 mime 类型等元数据        print(f"PDF 提取完成,MIME类型: {pdf_result.mime_type}")        print(f"PDF 文本前50个字符: {pdf_result.content[:50]}...")        print(f"\nWord 提取完成,MIME类型: {word_result.mime_type}")        print(f"Word 文本前50个字符: {word_result.content[:50]}...")    except Exception as e:        print(f"解析过程中翻车了: {e}")if __name__ == "__main__":    asyncio.run(process_local_documents())

这段代码看着普通,但在实际业务中非常实用。因为它是原生的 async 函数,当你的知识库需要批量接入成百上千份文档时,你可以直接丢给协程去并发处理,CPU 的利用率能拉到满。

内存里的数据怎么处理?

有时候,我们做的是一个 Web 接口,比如用 FastAPI 接收前端传过来的文件。这时候文件是在内存里的字节流(Bytes),根本没有保存到本地路径。

以前遇到这种情况,还得先写个临时文件到硬盘,解析完了再删掉,非常反人类。Kreuzberg 直接提供了一个 extract_bytes 方法:

import asynciofrom kreuzberg import extract_bytesasync def process_memory_data(file_bytes: bytes, file_name: str):    """    模拟处理来自 Web 框架的文件上传    """    print(f"接收到文件: {file_name},大小: {len(file_bytes)} bytes")    # 直接丢进引擎,不需要落盘    result = await extract_bytes(file_bytes)    # 甚至我们可以根据它自动识别的类型做点业务逻辑    if "pdf" in result.mime_type:        print("这是一个 PDF,准备灌入向量数据库...")    elif "image" in result.mime_type:        print("这是一张图片,文本内容如下:")    print(result.content.strip())    return result.content# 模拟一段读取async def main():    with open("sample_report.pdf", "rb") as f:        raw_bytes = f.read()    await process_memory_data(raw_bytes, "upload_temp.pdf")if __name__ == "__main__":    asyncio.run(main())

直接把内存里的字节流扔进去,它就能在底层完成识别和解析,整个过程一气呵成。

处理更棘手的字符串

如果你抓取的是网页数据,或者拿到的是一段 HTML 源码,里面掺杂了大量没用的标签,清洗起来也很头疼。Kreuzberg 同样准备了 extract_string 接口,对于提取纯净文本来说简直是降维打击:

import asynciofrom kreuzberg import extract_stringasync def clean_html_content():    # 模拟一段从网页抓取下来的脏数据    dirty_html = """    <html>        <body>            <header>导航栏忽略</header>            <div class="main-content">                <h1>这是我们真正需要的新闻标题</h1>                <p>今天发布了一个新版本的 Python 库。</p>                <script>console.log("这堆 JS 代码我们绝对不想要");</script>            </div>        </body>    </html>    """    # 直接提取    result = await extract_string(dirty_html)    print("清洗后的纯净文本:")    print(result.content)if __name__ == "__main__":    asyncio.run(clean_html_content())

这段代码跑完,你会发现所有的 HTML 标签、脚本代码全被干掉了,留下的只有真正的自然语言文本。这对于做 NLP 数据清洗的兄弟来说,能省下多少写正则的时间?

总结一下

在这个万物皆可大模型的时代,语料的质量直接决定了最终输出的质量。Kreuzberg 其实并没有改变文档解析的物理规律,但它把工程复杂度降到了最低。

它就像是一个黑盒,把复杂的格式探测、引擎调度都封装在了底层的 Rust 代码里,暴露给我们的只有极其优雅的异步接口。如果你的团队最近也在做知识库,或者经常需要写爬虫清洗数据,我强烈建议你把它加进你的 requirements.txt 里,绝对能让你少掉很多头发。

编辑:余文彬

审校:余雨馨

相关学习资料