SmolDocling简明教程
SmolDocling用一种更智能、更具布局意识的方法取代了传统的OCR,这种方法能够理解文档。

如果你花了数小时测试像Tesseract或Textract这样的OCR工具,却发现它们弄乱了表格、跳过了标题或压平了文档结构,你并不孤单。
OCR在处理纯文本时仍然非常有效。但一旦你的文档包含多列布局、代码块、方程式甚至结构化列表时——传统的OCR工具就显得力不从心了。
我们需要的是一种更智能的方法。一种不仅读取文本,还能理解文档的布局、语义和结构的方法。
这就是SmolDocling和Docling库的用武之地。
什么是SmolDocling和Docling?
- SmolDocling 是一个256M参数的视觉语言模型,它将文档页面作为图像输入并输出称为DocTags的语义标记。
- Docling 是一个Python工具包,它利用这些DocTags将其转换为结构化的格式。
SmolDocling用一种更智能、更具布局意识的方法取代了传统的OCR,这种方法能够理解文档。
什么是DocTags?
以下是从SmolDocling处理文档页面后输出的一段示例:
<heading level="1">简介</heading>
<paragraph>SmolDocling是一个用于文档理解的紧凑型视觉语言模型。</paragraph>
<table>
<row><cell>模型</cell><cell>参数量</cell></row>
<row><cell>SmolDocling</cell><cell>256M</cell></row>
</table>
DocTags不仅仅是关于布局——它们保留了语义、阅读顺序和层次结构。这意味着下游工具如Docling可以比任何基于后处理启发式解析器进行更准确的转换。
1、设置说明(推荐Mac M1/M2/M3/M4)
此教程在MacBook M4上进行了测试,并使用了MLX,Apple的Metal加速ML后端。
它仍然可以在其他平台上工作,但您可能需要修改一些模型加载行以直接使用PyTorch或Hugging Face。
1.1 创建Python环境
# 如果尚未安装,请先安装uv
pip install uv
# 创建虚拟环境
uv venv smoldocling-env
source smoldocling-env/bin/activate
1.2 安装所需包
uv pip install gradio mlx-vlm docling-core pillow pdf2image requests
brew install poppler
2、构建应用程序
这将是我们的设置:

2.1 加载PDF或图像(本地或通过URL)
from PIL import Image
from pathlib import Path
from urllib.parse import urlparse
from pdf2image import convert_from_path, convert_from_bytes
import requests
from io import BytesIO
def load_input_resource(input_path):
images = []
if urlparse(input_path).scheme != "":
response = requests.get(input_path, stream=True, timeout=10)
content = BytesIO(response.content)
if content.read(4) == b"%PDF":
content.seek(0)
images.extend(convert_from_bytes(content.read()))
else:
content.seek(0)
images.append(Image.open(content))
else:
path = Path(input_path)
if path.suffix.lower() == ".pdf":
images.extend(convert_from_path(str(path)))
else:
images.append(Image.open(path))
return images
2.2 使用MLX后端加载SmolDocling模型
import mlx.core as mx
from mlx_vlm import load
from mlx_vlm.utils import load_config
def load_model():
mx.set_default_device(mx.gpu)
model_path = "ds4sd/SmolDocling-256M-preview-mlx-bf16"
model, processor = load(model_path)
model.eval()
mx.eval(model.parameters())
config = load_config(model_path)
return model, processor, config
2.3 处理文档
def process_document(file_obj, url_input, export_format):
"""使用SmolDocling处理文档并返回结果。"""
try:
# 加载模型
model, processor, config = load_model()
# 确定输入源
if file_obj is not None:
# 将上传的文件保存到临时位置
temp_dir = tempfile.mkdtemp()
# 获取上传文件的文件名
file_name = getattr(file_obj, 'name', 'uploaded_file')
# 根据Gradio提供的不同类型的文件对象进行处理
temp_path = os.path.join(temp_dir, file_name)
# 不同类型文件对象的处理方式
if hasattr(file_obj, 'read'):
# 如果是具有read方法的文件对象
with open(temp_path, "wb") as f:
f.write(file_obj.read())
else:
# 如果已经是路径
if isinstance(file_obj, str):
temp_path = file_obj
else:
# 对于Gradio的file组件返回的元组(path, name)
temp_path = file_obj if isinstance(file_obj, str) else file_obj.name
input_path = temp_path
elif url_input.strip():
input_path = url_input.strip()
else:
return "请提供文件上传或URL", None, None
# 从输入文件中获取图像
images = load_input_resource(input_path)
if not images:
return "无法从提供的文件或URL提取图像", None, None
# 设置提示
prompt = "将此页转换为docling。"
formatted_prompt = apply_chat_template(processor, config, prompt, num_images=1)
# 处理每张图像并生成输出
all_outputs = []
all_images = []
processing_log = ""
for i, image in enumerate(images):
processing_log += f"正在处理第{i+1}/{len(images)}页...\n\n"
processing_log += "DocTags:\n\n"
output = ""
all_images.append(image)
for token in stream_generate(
model, processor, formatted_prompt, [image], max_tokens=4096, verbose=False
):
output += token.text
if "</doctag>" in token.text:
break
all_outputs.append(output)
processing_log += output + "\n\n"
# 创建DoclingDocument
doctags_doc = DocTagsDocument.from_doctags_and_image_pairs(all_outputs, all_images)
doc = DoclingDocument(name="ProcessedDocument")
doc.load_from_doctags(doctags_doc)
# 根据所选格式导出
if export_format == "Markdown":
result = doc.export_to_markdown()
elif export_format == "HTML":
html_output = tempfile.NamedTemporaryFile(suffix=".html", delete=False)
html_path = Path(html_output.name)
doc.save_as_html(html_path, image_mode=ImageRefMode.EMBEDDED)
with open(html_path, "r") as f:
result = f.read()
elif export_format == "JSON":
doc_dict = doc.export_to_dict()
result = json.dumps(doc_dict, indent=4)
else:
result = "选择的导出格式无效"
# 返回第一张图像作为预览和处理日志
return result, images[0] if images else None, processing_log
except Exception as e:
import traceback
error_details = traceback.format_exc()
return f"处理文档时出错: {str(e)}\n\n详细信息:\n{error_details}", None, error_details
2.4 渲染输出
def render_output(result, export_format):
"""根据导出格式渲染处理结果。"""
if export_format == "Markdown":
# 对于Markdown,显示渲染后的Markdown组件。
return gr.update(value=result, visible=True), gr.update(visible=False), gr.update(visible=False)
elif export_format == "HTML":
# 对于HTML,将其作为嵌入式网页组件渲染。
return gr.update(visible=False), gr.update(value=result, visible=True), gr.update(visible=False)
elif export_format == "JSON":
# 对于JSON,解析为对象以便gr.JSON可以将其渲染为可扩展树。
try:
json_obj = json.loads(result)
except Exception as e:
json_obj = {"error": "无效JSON", "detail": str(e)}
return gr.update(visible=False), gr.update(visible=False), gr.update(value=json_obj, visible=True)
else:
# 回退:隐藏所有渲染视图。
return gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
def prepare_download(result, export_format):
"""准备下载处理后的输出文件。"""
if export_format == "Markdown":
ext = ".md"
elif export_format == "HTML":
ext = ".html"
elif export_format == "JSON": ext = ".json"
else:
ext = ".txt"
# 创建一个临时文件,带正确的文件类型。
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=ext)
temp_file.write(result.encode("utf-8"))
temp_file.close()
# 返回更新的对象以供下载按钮使用。
return gr.update(value=temp_file.name), gr.update(value=temp_file.name)
2.5 Gradio 界面用于一键使用
# 创建 Gradio 接口
with gr.Blocks(title="SmolDocling 文档处理") as app:
# 添加自定义 CSS 以在输出部分设置边框样式。
gr.HTML(
"""
<style>
#raw_output_box, #formatted_output_box {
border: 1px solid #ccc;
padding: 10px;
border-radius: 5px;
}
</style>
"""
)
lang=None
with gr.Row():
with gr.Column(scale=1):
file_input = gr.File(label="上传 PDF 或图像")
url_input = gr.Textbox(label="或输入指向 PDF 或图像的 URL")
export_format = gr.Radio(
choices=["Markdown", "HTML", "JSON"],
label="导出格式",
value="Markdown"
)
submit_button = gr.Button("处理文档", variant="primary")
if export_format == "Markdown":
lang = "markdown"
elif export_format == "HTML":
lang = "html"
elif export_format == "JSON":
lang = "json"
with gr.Column(scale=2):
with gr.Tab("原始输出"):
with gr.Column(elem_id="raw_output_box"):
# 在代码块中显示原始输出。
output_text = gr.Code(label="结构化输出", language=lang, lines=20, max_lines=20)
download_raw = gr.DownloadButton("下载原始输出")
with gr.Tab("文档预览"):
preview_image = gr.Image(label="文档预览", type="pil")
with gr.Tab("日志"):
# 在代码块中显示日志。
log_output = gr.Code(label="处理日志", language="html", lines=20, max_lines=20)
with gr.Tab("格式化输出"):
with gr.Column(elem_id="formatted_output_box"):
rendered_markdown = gr.Markdown(visible=False, label="Markdown 渲染")
rendered_html = gr.HTML(visible=False, label="HTML 渲染")
rendered_json = gr.JSON(visible=False, label="JSON 渲染")
download_formatted = gr.DownloadButton("下载格式化输出")
# 设置事件处理器与链式回调:
submit_button.click(
process_document,
inputs=[file_input, url_input, export_format],
outputs=[output_text, preview_image, log_output]
).then(
render_output,
inputs=[output_text, export_format],
outputs=[rendered_markdown, rendered_html, rendered_json]
).then(
prepare_download,
inputs=[output_text, export_format],
outputs=[download_raw, download_formatted]
)
if __name__ == "__main__":
app.launch()
3、运行应用程序
3.1 使用以下命令运行应用程序。
uv run main.py
应用程序将在 http://127.0.0.1:7860/ 运行。

3.2 使用此处的任何示例图像进行测试
测试图像:
- https://github.com/docling-project/docling-ibm-models/blob/main/tests/test_data/samples/page_with_table.png
- https://github.com/docling-project/docling-ibm-models/blob/main/tests/test_data/samples/page_with_list.png

你可以在 格式化输出 标签下预览转换后的代码。
此外,如果您想检查生成的文档标签,它们将位于 日志 标签下。
4、结束语
你可以在我的 GitHub 存储库中找到完整代码。
无需 OCR。无需布局猜测。只需干净的结构、快速处理和轻量级模型,在 Apple Silicon 上运行得非常出色。
如果你的工作流涉及:
- 学术论文
- 商务报告
- 表单数字化
- 基于文档的 LLM 代理
请尝试 SmolDocling。 :-)
原文链接:Stop Fighting With OCR: Convert Any Document to Markdown, HTML, or JSON Using SmolDocling
汇智网翻译整理,转载请标明出处
