habib-pdf-to-json

Polar Sponsor
爱发电 赞助
.NET 9.0

{"answer":"从建筑PDF提取结构化数据。将规格、BOM、进度表及报告转为Excel/CSV/JSON。扫描件用OCR,原生PDF用pdfplumber。"}

PDF 转换为结构化数据转换

功能概述

PDF 转换为结构化数据转换是一项面向实际任务的技能,主要用于Overview. 基于 DDC 方法( 第 2.4 章), 这种技能将非结构化的 PDF 文件转换为结构化格式, 适合分析和整齐。它将相关步骤、工具调用和结果整理方式集中到统一流程中,帮助使用者更快完成目标并减少重复操作。

核心要点

  • 使用时应结合输入条件选择合适的执行方式,核对必要参数、依赖环境与输出内容,并按原始要求处理异常情况。
  • 该技能适合需要稳定复用相关能力的场景,可作为自动化工作流的一部分,也便于后续检查、调整和扩展。
  • 从功能定位来看,该技能强调把分散的操作要求整理成清晰、可复用的处理流程,使用户能够围绕既定目标快速准备输入、选择执行方式并获得结构化结果。

使用与执行

实际使用前应先确认任务范围、数据来源、运行环境、必要权限和关键参数,再依据技能说明逐步执行;若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。

结果检查与注意事项

执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;涉及批量任务时,还应保存进度,避免中断后重复操作。

PDF 转结构化数据转换

概述

基于 DDC 方法论(第 2.4 章),本技能将非结构化的 PDF 文档转换为适合分析与集成的结构化格式。建筑工程会产生大量 PDF 文档——包括技术规范、物料清单(BOM)、进度计划和报告等——这些文档需被提取并处理。

图书参考:《数据转换为结构化形式》

“将数据从非结构化形式转换为结构化形式,既是一门艺术,也是一门科学。该过程常常占据数据工程师工作的大部分时间。”
—《DDC Book》,第 2.4 章

ETL 流程概述

该转换遵循 ETL 模式:

  1. 抽取(Extract):加载 PDF 文档
  2. 转换(Transform):解析并结构化内容
  3. 加载(Load):保存为 CSV、Excel 或 JSON 格式

快速入门

import pdfplumber
import pandas as pd

# 从 PDF 中抽取表格
with pdfplumber.open("construction_spec.pdf") as pdf:
    page = pdf.pages[0]
    table = page.extract_table()
    df = pd.DataFrame(table[1:], columns=table[0])
    df.to_excel("extracted_data.xlsx", index=False)

安装

# 核心库
pip install pdfplumber pandas openpyxl

# 针对扫描版 PDF(OCR)
pip install pytesseract pdf2image
# 同时需安装 Tesseract OCR:https://github.com/tesseract-ocr/tesseract

# 高级 PDF 操作
pip install pypdf

原生 PDF 抽取(pdfplumber)

从 PDF 中抽取全部表格

import pdfplumber
import pandas as pd

def extract_tables_from_pdf(pdf_path):
    """从 PDF 文件中抽取所有表格"""
    all_tables = []

    with pdfplumber.open(pdf_path) as pdf:
        for page_num, page in enumerate(pdf.pages):
            tables = page.extract_tables()
            for table_num, table in enumerate(tables):
                if table and len(table) > 1:
                    # 将首行作为表头
                    df = pd.DataFrame(table[1:], columns=table[0])
                    df['_page'] = page_num + 1
                    df['_table'] = table_num + 1
                    all_tables.append(df)

    if all_tables:
        return pd.concat(all_tables, ignore_index=True)
    return pd.DataFrame()

# 使用示例
df = extract_tables_from_pdf("material_specification.pdf")
df.to_excel("materials.xlsx", index=False)

按布局结构抽取文本

import pdfplumber

def extract_text_with_layout(pdf_path):
    """抽取文本并保留原始布局结构"""
    full_text = []

    with pdfplumber.open(pdf_path) as pdf:
        for page in pdf.pages:
            text = page.extract_text()
            if text:
                full_text.append(text)

    return "\n\n--- Page Break ---\n\n".join(full_text)

# 使用示例
text = extract_text_with_layout("project_report.pdf")
with open("report_text.txt", "w", encoding="utf-8") as f:
    f.write(text)

按坐标区域抽取指定表格

import pdfplumber
import pandas as pd

def extract_table_from_area(pdf_path, page_num, bbox):
    """
    从页面指定区域抽取表格

    参数:
        pdf_path: PDF 文件路径
        page_num: 页面编号(从 0 开始计数)
        bbox: 边界框(x0, top, x1, bottom),单位为点(points;1 英寸 = 72 点)
    """
    with pdfplumber.open(pdf_path) as pdf:
        page = pdf.pages[page_num]
        cropped = page.within_bbox(bbox)
        table = cropped.extract_table()

        if table:
            return pd.DataFrame(table[1:], columns=table[0])
    return pd.DataFrame()

# 使用示例 —— 从指定区域抽取表格
# bbox 格式:(left, top, right, bottom),单位为点(points)
df = extract_table_from_area("drawing.pdf", 0, (50, 100, 550, 400))

扫描版 PDF 处理(OCR)

从扫描版 PDF 中抽取文本

import pytesseract
from pdf2image import convert_from_path
import pandas as pd

def ocr_scanned_pdf(pdf_path, language='eng'):
    """
    使用 OCR 从扫描版 PDF 中抽取文本

    参数:
        pdf_path: 扫描版 PDF 文件路径
        language: Tesseract 语言代码(如 eng、deu、rus 等)
    """
    # 将 PDF 页面转换为图像
    images = convert_from_path(pdf_path, dpi=300)

    extracted_text = []
    for i, image in enumerate(images):
        text = pytesseract.image_to_string(image, lang=language)
        extracted_text.append({
            'page': i + 1,
            'text': text
        })

    return pd.DataFrame(extracted_text)

# 使用示例
df = ocr_scanned_pdf("scanned_specification.pdf", language='eng')
df.to_csv("ocr_results.csv", index=False)

OCR 表格抽取

import pytesseract
from pdf2image import convert_from_path
import pandas as pd
import cv2
import numpy as np

def ocr_table_from_scanned_pdf(pdf_path, page_num=0):
    """使用 OCR 并结合表格检测,从扫描版 PDF 中抽取表格"""
    # 将指定页面转换为图像
    images = convert_from_path(pdf_path, first_page=page_num+1,
                                last_page=page_num+1, dpi=300)
    image = np.array(images[0])

    # 转为灰度图
    gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)

    # 应用二值化阈值处理
    _, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY_INV)

    # 使用自定义配置抽取带表格结构的文本
    custom_config = r'--oem 3 --psm 6'
    text = pytesseract.image_to_string(gray, config=custom_config)

    # 将文本解析为表格结构
    lines = text.strip().split('\n')
    data = [line.split() for line in lines if line.strip()]

    if data:
        # 假设首行为表头
        df = pd.DataFrame(data[1:], columns=data[0] if len(data[0]) > 0 else None)
        return df
    return pd.DataFrame()

# 使用示例
df = ocr_table_from_scanned_pdf("scanned_bom.pdf")
print(df)

面向建筑工程的专用抽取

物料清单(BOM)抽取

import pdfplumber
import pandas as pd
import re

def extract_bom_from_pdf(pdf_path):
    """从建筑工程 PDF 中抽取物料清单(BOM)"""
    all_items = []

    with pdfplumber.open(pdf_path) as pdf:
        for page in pdf.pages:
            tables = page.extract_tables()
            for table in tables:
                if not table or len(table) < 2:
                    continue

                # 查找表头行(匹配常见 BOM 字段关键词)
                header_keywords = ['item', 'description', 'quantity', 'unit', 'material']
                for i, row in enumerate(table):
                    if row and any(keyword in str(row).lower() for keyword in header_keywords):
                        # 找到表头,处理后续数据行
                        headers = [str(h).strip() for h in row]
                        for data_row in table[i+1:]:
                            if data_row and any(cell for cell in data_row if cell):
                                item = dict(zip(headers, data_row))
                                all_items.append(item)
                        break

    return pd.DataFrame(all_items)

# 使用示例
bom = extract_bom_from_pdf("project_bom.pdf")
bom.to_excel("bom_extracted.xlsx", index=False)

项目进度计划抽取

import pdfplumber
import pandas as pd
from datetime import datetime

def extract_schedule_from_pdf(pdf_path):
    """从 PDF 中抽取项目进度计划 / 甘特图数据"""
    with pdfplumber.open(pdf_path) as pdf:
        all_tasks = []

        for page in pdf.pages:
            tables = page.extract_tables()
            for table in tables:
                if not table:
                    continue

                # 寻找类似进度计划的表格
                headers = table[0] if table else []

                # 判断是否为进度计划类表格
                schedule_keywords = ['task', 'activity', 'start', 'end', 'duration']
                if any(kw in str(headers).lower() for kw in schedule_keywords):
                    for row in table[1:]:
                        if row and any(cell for cell in row if cell):
                            task = dict(zip(headers, row))
                            all_tasks.append(task)

    df = pd.DataFrame(all_tasks)

    # 尝试解析日期字段
    date_columns = ['Start', 'End', 'Start Date', 'End Date', 'Finish']
    for col in date_columns:
        if col in df.columns:
            df[col] = pd.to_datetime(df[col], errors='coerce')

    return df

# 使用示例
schedule = extract_schedule_from_pdf("project_schedule.pdf")
print(schedule)

技术规范解析

import pdfplumber
import pandas as pd
import re

def parse_specification_pdf(pdf_path):
    """解析建筑工程技术规范文档"""
    specs = []

    with pdfplumber.open(pdf_path) as pdf:
        full_text = ""
        for page in pdf.pages:
            text = page.extract_text()
            if text:
                full_text += text + "\n"

    # 解析章节(常见规范格式)
    section_pattern = r'(\d+\.\d+(?:\.\d+)?)\s+([A-Z][^\n]+)'
    sections = re.findall(section_pattern, full_text)

    for num, title in sections:
        specs.append({
            'section_number': num,
            'title': title.strip(),
            'level': len(num.split('.'))
        })

    return pd.DataFrame(specs)

# 使用示例
specs = parse_specification_pdf("technical_spec.pdf")
print(specs)

批量处理

批量处理多个 PDF 文件

import pdfplumber
import pandas as pd
from pathlib import Path

def batch_extract_tables(folder_path, output_folder):
    """批量处理文件夹内所有 PDF,并抽取其中的表格"""
    pdf_files = Path(folder_path).glob("*.pdf")
    results = []

    for pdf_path in pdf_files:
        print(f"Processing: {pdf_path.name}")
        try:
            with pdfplumber.open(pdf_path) as pdf:
                for page_num, page in enumerate(pdf.pages):
                    tables = page.extract_tables()
                    for table_num, table in enumerate(tables):
                        if table and len(table) > 1:
                            df = pd.DataFrame(table[1:], columns=table[0])
                            df['_source_file'] = pdf_path.name
                            df['_page'] = page_num + 1

                            # 保存单个表格
                            output_name = f"{pdf_path.stem}_p{page_num+1}_t{table_num+1}.xlsx"
                            df.to_excel(Path(output_folder) / output_name, index=False)
                            results.append(df)
        except Exception as e:
            print(f"Error processing {pdf_path.name}: {e}")

    # 合并输出
    if results:
        combined = pd.concat(results, ignore_index=True)
        combined.to_excel(Path(output_folder) / "all_tables.xlsx", index=False)

    return len(results)

# 使用示例
count = batch_extract_tables("./pdf_documents/", "./extracted/")
print(f"Extracted {count} tables")

抽取后数据清洗

import pandas as pd

def clean_extracted_data(df):
    """清洗 PDF 抽取数据中的常见问题"""
    # 删除完全为空的行
    df = df.dropna(how='all')

    # 清理字符串列首尾空白字符
    for col in df.select_dtypes(include=['object']).columns:
        df[col] = df[col].str.strip()

    # 删除所有单元格均为空字符串的行
    df = df[df.apply(lambda row: any(cell != '' for cell in row), axis=1)]

    # 尝试转换数值列
    for col in df.columns:
        # 尝试转为数值类型
        numeric_series = pd.to_numeric(df[col], errors='coerce')
        if numeric_series.notna().sum() > len(df) * 0.5:  # 若超过 50% 为有效数值,则执行转换
            df[col] = numeric_series

    return df

# 使用示例
df = extract_tables_from_pdf("document.pdf")
df_clean = clean_extracted_data(df)
df_clean.to_excel("clean_data.xlsx", index=False)

导出选项

import pandas as pd
import json

def export_to_multiple_formats(df, base_name):
    """将 DataFrame 导出为多种格式"""
    # Excel
    df.to_excel(f"{base_name}.xlsx", index=False)

    # CSV
    df.to_csv(f"{base_name}.csv", index=False, encoding='utf-8-sig')

    # JSON
    df.to_json(f"{base_name}.json", orient='records', indent=2)

    # JSON Lines(适用于大数据集)
    df.to_json(f"{base_name}.jsonl", orient='records', lines=True)

# 使用示例
df = extract_tables_from_pdf("document.pdf")
export_to_multiple_formats(df, "extracted_data")

速查表

任务 工具 代码
抽取表格 pdfplumber page.extract_table()
抽取文本 pdfplumber page.extract_text()
OCR 扫描版 PDF pytesseract pytesseract.image_to_string(image)
合并 PDF pypdf writer.add_page(page)
转换为图像 pdf2image convert_from_path(pdf)

故障排查

问题 解决方案
未检测到表格 尝试调整表格识别参数:page.extract_table(table_settings={})
列对齐错误 启用可视化调试:page.to_image().draw_rects()
OCR 识别质量差 提高 DPI 设置、预处理图像、选用正确的语言模型
内存不足 逐页处理、处理完成后及时关闭 PDF 对象

参考资料

下一步

  • 参阅 image-to-data 获取图像处理相关内容
  • 参阅 cad-to-data 获取 CAD/BIM 数据抽取相关内容
  • 参阅 etl-pipeline 获取自动化处理工作流相关内容
  • 参阅 data-quality-check 获取抽取数据质量校验相关内容

相关专题

更多
js实现base64编码
js实现base64编码

在JavaScript中实现Base64编码,最直接的方式是使用内置的btoa()函数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2025.10.24

379

9

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

2025.09.10

2179

7

html中如何调用外部js
html中如何调用外部js

在 html 中调用外部 js 可通过以下步骤:创建 js 文件并编写代码。在 html 代码中添加 <script> 标签,设置 src 属性为 js 文件路径。保存并刷新页面加载外部 js。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.11

1712

6

php和js的区别
php和js的区别

php和js的区别:PHP主要用于服务器端编程,而JavaScript主要用于客户端编程;PHP代码在服务器端执行,而JavaScript代码在客户端浏览器中执行。想了解更多php和js的相关内容,可以阅读本专题下面的文章。

2024.03.22

4828

11

JS的Document介绍
JS的Document介绍

在JavaScript中,document对象是一个非常重要的全局对象,它代表整个HTML文档。想了解更多Document的相关内容,可以阅读本专题下面的文章。

2024.03.14

6047

11

js中each函数的用法
js中each函数的用法

在JavaScript中,并没有一个内建的each函数,但jQuery库提供了一个非常有用的$.each() 函数,用于遍历数组或对象。想了解更多each函数的相关内容,可以阅读本专题下面的文章。

2024.03.13

1629

5

js和vue的关系
js和vue的关系

js和vue的关系:1、js作为web开发基石;2、vue.js作为前端框架的崛起;3、js与vue的互补关系;4、js与vue的实践应用。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.03.11

353

5

js弹幕功能实现方法
js弹幕功能实现方法

PHP中文网为大家带来,js实现弹幕功能的方法:1、创建html文件;2、添加html代码架构;3、在body标签中使用div、input、button标签分给页面设计效果显示框、输入框、弹幕提交按钮;4、添加script标签并写入js代码来实现弹幕效果;5、通过浏览器方式查看设计效果即可。希望对大家有所帮助。

2024.03.04

2992

11

js事件冒泡可以解决哪些问题
js事件冒泡可以解决哪些问题

js事件冒泡可以解决的问题:1、简化事件处理逻辑;2、提高事件处理性能;3、实现自定义组件和交互效果;4、控制事件传播方向;5、解决事件覆盖问题;6、实现全局事件监听;7、方便调试和排查问题。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.02.23

2824

4

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WEB前端教程【HTML5+CSS3+JS】
WEB前端教程【HTML5+CSS3+JS】

共101课时 | 20.4万人学习

JS进阶与BootStrap学习
JS进阶与BootStrap学习

共39课时 | 4.6万人学习