返回博客

读不了的 PDF:没装 poppler、文本层还是空的,用 macOS 自带 Swift PDFKit 渲染

要从一份中文简历 PDF 里读项目经历——结果读取工具直接罢工,连环踩了一串「装不上 / 提不出」的坑,最后用 macOS 自带的能力一招解决。

现象

pdftoppm is not installed. Install poppler-utils (e.g. brew install poppler ...)

并且一路 fallback 全军覆没:

  • pdftotext / pdftoppm——没装(poppler),装它太重
  • 系统 python3pypdf / PyPDF2——No module named
  • 系统 python3Quartz(PyObjC)——No module named 'Quartz'
  • osascript -l JavaScript 走 PDFKit 的 ObjC bridge——静默返回空
  • Swift PDFKit 的 doc.string——打印出页数了,但文本是空的
坑:很多中文/扫描 PDF 根本没有可提取的文本层

中文简历、扫描件、设计稿导出的 PDF,常用子集字体且不带 ToUnicode 映射——doc.string 拿到的是空或乱码。这类 PDF 你再怎么折腾文本提取都没用,只能把页面当图片来看

根因

两件事叠一起:① 渲染工具(poppler)没装;② 就算绕过渲染去抽文本,这份 PDF 压根没有干净的文本层。所以正确路线不是「修文本提取」,而是「把页面光栅化成图片,再用视觉理解去读」。

而光栅化根本不需要 poppler——macOS 自带的 PDFKit + AppKit 就能干swift 还能直接跑脚本文件,零安装。

正解

用一段几十行的 Swift 把每页渲染成 PNG,再把 PNG 当图片读:

// /tmp/pdf2png.swift
import Foundation
import PDFKit
import AppKit
let p = CommandLine.arguments[1]
let outDir = CommandLine.arguments[2]
guard let doc = PDFDocument(url: URL(fileURLWithPath: p)) else { print("DOC_NONE"); exit(1) }
let scale: CGFloat = 2.0   // 2x,让小字在 PNG 里看得清
for i in 0..<doc.pageCount {
    guard let page = doc.page(at: i) else { continue }
    let b = page.bounds(for: .mediaBox)
    let img = NSImage(size: NSSize(width: b.width*scale, height: b.height*scale))
    img.lockFocus()
    NSColor.white.set()                       // PDF 页是透明的,先铺白底
    NSRect(x:0,y:0,width:b.width*scale,height:b.height*scale).fill()
    let ctx = NSGraphicsContext.current!.cgContext
    ctx.saveGState(); ctx.scaleBy(x: scale, y: scale)
    page.draw(with: .mediaBox, to: ctx)
    ctx.restoreGState()
    img.unlockFocus()
    guard let tiff = img.tiffRepresentation, let rep = NSBitmapImageRep(data: tiff),
          let png = rep.representation(using: .png, properties: [:]) else { continue }
    try! png.write(to: URL(fileURLWithPath: "\(outDir)/page-\(i+1).png"))
    print("\(outDir)/page-\(i+1).png")
}
mkdir -p /tmp/pdf-pages
swift /tmp/pdf2png.swift "/abs/path/file.pdf" /tmp/pdf-pages

然后逐张读 /tmp/pdf-pages/page-N.png——视觉理解能直接吃下中文文字和版面,哪怕没有文本层。

正解:Swift + PDFKit 渲染 PNG,零安装,绕开文本层缺失

swift 打印出每页的 PNG 路径;读这些 PNG 能拿到连贯的页面内容。小字看不清就把 scale 提到 3.0~4.0。

几个细节

  • swift 直接跑脚本文件,不用建 Xcode 工程,前提是装了 Command Line Tools;PDFKit / AppKit 都是系统框架。
  • 白底要铺:PDF 页面透明,不铺白底,深色主题下渲出来的图会很难辨认。
  • 如果文本层其实存在、你只要文字,doc.string(Swift)比渲染快——但本文这个触发场景它是空的,直接上 PNG。

一句话外卖

读不了的 PDF 别死磕装 poppler 或抽文本——macOS 自带 Swift + PDFKit 把页面渲成 PNG,再当图片视觉读,对没有文本层的中文 PDF 尤其有效。