我的博客

分享技术与思想的点滴

PDF 文献去水印

你说得对,感谢指正。我查证了一下,Stirling-PDF 确实没有独立的“一键去水印”按钮。我之前提到的“Remove Watermarks”功能实际上在最新版本中已经被整合或移除了。

对于去水印这个需求,正确的 Docker 方案应该是:


真正的 Docker 去水印方案:OCRmyPDF + 水印遮盖

OCRmyPDF 本身是 OCR 工具,但它有一个隐藏用法:通过 --remove-background 或强制重新渲染,可以淡化/去除半透明水印。

Docker 命令(一条命令跑起来):

# 拉取镜像并运行
docker run -v /你的PDF文件夹:/data jbarlow83/ocrmypdf:latest \
  --remove-background \
  --deskew \
  --clean \
  input.pdf output_clean.pdf

参数解释

  • --remove-background这是关键。它会检测并移除背景层,很多半透明水印(如知网的浅灰色斜字)就是放在背景层的。
  • --deskew:纠偏。
  • --clean:清理临时文件。

效果

  • ✅ 对知网/万方的浅灰色斜体水印:效果极好,基本看不见。
  • ⚠️ 对彩色 Logo 水印:只能淡化,无法根除。
  • ❌ 对左侧竖排预印本文字:无效(因为那是前景文字,不是背景层)。

如果你想要图形界面:推荐 PDF24 或 PDF Candy(非 Docker)

如果坚持用 Docker 图形界面,目前确实没有完美的开源去水印工具。但可以考虑:

工具 部署方式 去水印能力
Stirling-PDF Docker ❌ 无直接功能
Paperless-ngx Docker ❌ 文档管理,无编辑
Mayan EDMS Docker ❌ 同上
OCRmyPDF Docker CLI ✅ 可去背景水印
Ghostscript Linux 自带 ✅ 命令行去水印

回归你的核心需求:MarkItDown 转换前的 PDF 清洗

既然你的最终目的是让 MarkItDown 提取干净正文,而 Docker 方案又不尽如人意,我建议回到 Python 脚本方案

结合你之前的问题,我把所有需求整合成了一个一站式脚本,可以直接用:

#!/usr/bin/env python3
"""
PDF 一站式清洗工具(专为 MarkItDown 转换优化)
功能:
1. 去除左侧竖排预印本编号 (chinaXiv/arXiv)
2. 遮盖/淡化页面水印(知网/万方/期刊)
3. 可选:裁剪页眉页脚
"""

import fitz  # PyMuPDF
import os
import argparse

def clean_pdf_for_markitdown(input_path, output_path=None, 
                              remove_left_text=True,
                              remove_watermark=True,
                              crop_header_footer=False):
    """
    综合清洗 PDF,让 MarkItDown 提取出干净的正文
    """
    if output_path is None:
        base, ext = os.path.splitext(input_path)
        output_path = f"{base}_cleaned{ext}"

    doc = fitz.open(input_path)

    for page_num, page in enumerate(doc, 1):
        page_width = page.rect.width
        page_height = page.rect.height

        # ========== 1. 左侧预印本文字(白块遮盖) ==========
        if remove_left_text:
            left_margin = 70
            clip_rect = fitz.Rect(0, 0, left_margin, page_height)
            words = page.get_text("words", clip=clip_rect)

            preprint_keywords = [
                "chinaXiv", "arXiv", "medRxiv", "bioRxiv", 
                "v1", "v2", "v3", "doi:", "preprint", "Preprint"
            ]

            for word in words:
                word_text = word[4]
                if any(kw.lower() in word_text.lower() for kw in preprint_keywords):
                    x0, y0, x1, y1 = word[:4]
                    shape = page.new_shape()
                    shape.draw_rect(fitz.Rect(x0-5, y0-2, x1+5, y1+2))
                    shape.finish(color=(1, 1, 1), fill=(1, 1, 1), width=0)
                    shape.commit()
                    print(f"  页 {page_num}: 已遮盖预印本标识 '{word_text}'")

        # ========== 2. 水印处理(检测并遮盖) ==========
        if remove_watermark:
            text_dict = page.get_text("dict")

            watermark_keywords = [
                "仅供预览", "试读", "教育用途", "版权所有", "内部资料",
                "Evaluation only", "Trial", "Preview", "Copyright",
                "中国知网", "CNKI", "万方数据", "Wanfang", "维普", "VIP"
            ]

            for block in text_dict.get("blocks", []):
                if "lines" in block:
                    for line in block["lines"]:
                        for span in line["spans"]:
                            span_text = span.get("text", "").strip()
                            font_size = span.get("size", 0)

                            # 水印特征:包含关键词 + 字号很大(通常 > 18)
                            if any(kw in span_text for kw in watermark_keywords) and font_size > 18:
                                bbox = span["bbox"]
                                # 扩大遮盖范围,确保盖住
                                shape = page.new_shape()
                                shape.draw_rect(fitz.Rect(
                                    bbox[0] - 10, bbox[1] - 5,
                                    bbox[2] + 10, bbox[3] + 5
                                ))
                                shape.finish(color=(1, 1, 1), fill=(1, 1, 1), width=0)
                                shape.commit()
                                print(f"  页 {page_num}: 已遮盖水印 '{span_text[:30]}...'")

        # ========== 3. 裁剪页眉页脚(可选) ==========
        if crop_header_footer:
            # 裁剪顶部 60 像素和底部 60 像素
            crop_rect = fitz.Rect(0, 60, page_width, page_height - 60)
            page.set_cropbox(crop_rect)

    doc.save(output_path, garbage=4, deflate=True)
    doc.close()
    print(f"\n✅ 清洗完成: {output_path}")
    return output_path

def batch_process(input_dir=".", output_dir="./cleaned"):
    """批量处理文件夹内所有 PDF"""
    import glob

    if not os.path.exists(output_dir):
        os.makedirs(output_dir)

    pdf_files = glob.glob(os.path.join(input_dir, "*.pdf"))

    for pdf_file in pdf_files:
        filename = os.path.basename(pdf_file)
        output_path = os.path.join(output_dir, filename.replace(".pdf", "_cleaned.pdf"))
        print(f"\n处理: {filename}")
        clean_pdf_for_markitdown(pdf_file, output_path)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="PDF 清洗工具 - 专为 MarkItDown 优化")
    parser.add_argument("input", nargs="?", help="输入 PDF 文件路径")
    parser.add_argument("-o", "--output", help="输出文件路径")
    parser.add_argument("--batch", action="store_true", help="批量处理当前文件夹")
    parser.add_argument("--crop", action="store_true", help="裁剪页眉页脚")

    args = parser.parse_args()

    if args.batch:
        batch_process()
    elif args.input:
        clean_pdf_for_markitdown(args.input, args.output, crop_header_footer=args.crop)
    else:
        print("用法: python clean_pdf.py input.pdf -o output.pdf")
        print("      python clean_pdf.py --batch  # 批量处理当前文件夹")

使用方法

# 1. 安装依赖
pip install PyMuPDF

# 2. 单文件处理
python clean_pdf.py 论文.pdf -o 论文_干净.pdf

# 3. 批量处理当前文件夹所有 PDF
python clean_pdf.py --batch

# 4. 然后用 MarkItDown 转换清洗后的 PDF
markitdown 论文_干净.pdf > 论文.md

总结

你的需求 解决方案
Docker 图形界面去水印 ❌ 目前没有完美方案
Docker 命令行去水印 jbarlow83/ocrmypdf --remove-background
本地 Python 脚本去水印 上述一站式脚本(最推荐)
转换后 MD 清洗 ✅ 正则替换

建议:直接用上面的 Python 脚本,它专门为 MarkItDown 转换场景 设计,比任何 Docker 方案都精准。