table-extractor

table-extractor

熱門

>

316星標
69分支
更新於 2026/1/31
SKILL.md
唯讀
名稱
table-extractor
描述

>

版本
1.0

表格擷取技能

概述

此技能可使用 camelot(PDF 表格擷取的黃金標準)從 PDF 文件中精確擷取表格。能處理合併儲存格、無框線表格及跨頁版面等複雜表格,準確度高。

使用方式

  1. 提供包含表格的 PDF
  2. 可選擇指定頁碼或表格偵測方法
  3. 我會將表格擷取為 pandas DataFrame

範例提示:

  • 「從這個 PDF 中擷取所有表格」
  • 「取得這份報告第 5 頁的表格」
  • 「從這份文件中擷取無框線表格」
  • 「將 PDF 表格轉換為 Excel 格式」

領域知識

camelot 基礎

import camelot

# 從 PDF 擷取表格
tables = camelot.read_pdf('document.pdf')

# 存取結果
print(f"找到 {len(tables)} 個表格")

# 取得第一個表格的 DataFrame
df = tables[0].df
print(df)

擷取方法

方法 使用情境 說明
lattice 有框線表格 透過線條/框線偵測表格
stream 無框線表格 利用文字位置判斷
# Lattice 方法(預設)— 用於有可見框線的表格
tables = camelot.read_pdf('document.pdf', flavor='lattice')

# Stream 方法 — 用於無框線表格
tables = camelot.read_pdf('document.pdf', flavor='stream')

頁面選擇

# 單頁
tables = camelot.read_pdf('document.pdf', pages='1')

# 多頁
tables = camelot.read_pdf('document.pdf', pages='1,3,5')

# 頁面範圍
tables = camelot.read_pdf('document.pdf', pages='1-5')

# 所有頁面
tables = camelot.read_pdf('document.pdf', pages='all')

進階選項

Lattice 選項
tables = camelot.read_pdf(
    'document.pdf',
    flavor='lattice',
    line_scale=40,              # 線條偵測敏感度
    copy_text=['h', 'v'],       # 跨合併儲存格複製文字
    shift_text=['l', 't'],      # 文字對齊偏移
    split_text=True,            # 在換行處分割文字
    flag_size=True,             # 標記上標/下標
    strip_text='\n',            # 要移除的字元
    process_background=False,   # 處理背景線條
)
Stream 選項
tables = camelot.read_pdf(
    'document.pdf',
    flavor='stream',
    edge_tol=500,               # 邊緣容差
    row_tol=10,                 # 列容差
    column_tol=0,               # 欄容差
    strip_text='\n',            # 要移除的字元
)

表格區域指定

# 從特定區域擷取 (x1, y1, x2, y2)
# 座標從左下角開始,單位為 PDF 點數(72 點 = 1 英吋)
tables = camelot.read_pdf(
    'document.pdf',
    table_areas=['72,720,540,400'],  # 單一區域
)

# 多個區域
tables = camelot.read_pdf(
    'document.pdf',
    table_areas=['72,720,540,400', '72,380,540,200'],
)

欄位指定

# 手動指定欄位位置(適用於 stream 方法)
tables = camelot.read_pdf(
    'document.pdf',
    flavor='stream',
    columns=['100,200,300,400'],  # 欄分隔線的 X 位置
)

處理結果

import camelot

tables = camelot.read_pdf('document.pdf')

for i, table in enumerate(tables):
    # 存取 DataFrame
    df = table.df
    
    # 表格元資料
    print(f"表格 {i+1}:")
    print(f"  頁碼:{table.page}")
    print(f"  準確度:{table.accuracy}")
    print(f"  空白比例:{table.whitespace}")
    print(f"  順序:{table.order}")
    print(f"  形狀:{df.shape}")
    
    # 解析報告
    report = table.parsing_report
    print(f"  報告:{report}")

匯出選項

import camelot

tables = camelot.read_pdf('document.pdf')

# 匯出為 CSV
tables[0].to_csv('table.csv')

# 匯出為 Excel
tables[0].to_excel('table.xlsx')

# 匯出為 JSON
tables[0].to_json('table.json')

# 匯出為 HTML
tables[0].to_html('table.html')

# 匯出所有表格
for i, table in enumerate(tables):
    table.to_excel(f'table_{i+1}.xlsx')

視覺化除錯

import camelot

# 啟用視覺化除錯
tables = camelot.read_pdf('document.pdf')

# 繪製偵測到的表格區域
camelot.plot(tables[0], kind='contour').show()

# 繪製表格上的文字
camelot.plot(tables[0], kind='text').show()

# 繪製偵測到的線條(僅 lattice)
camelot.plot(tables[0], kind='joint').show()
camelot.plot(tables[0], kind='line').show()

# 儲存圖表
fig = camelot.plot(tables[0])
fig.savefig('debug.png')

處理跨頁表格

import camelot
import pandas as pd

def extract_multipage_table(pdf_path, pages='all'):
    """擷取並合併跨越多頁的表格。"""
    
    tables = camelot.read_pdf(pdf_path, pages=pages)
    
    # 依相似結構(欄位)分組表格
    table_groups = {}
    
    for table in tables:
        cols = tuple(table.df.columns)
        if cols not in table_groups:
            table_groups[cols] = []
        table_groups[cols].append(table.df)
    
    # 合併相似表格
    combined = []
    for cols, dfs in table_groups.items():
        if len(dfs) > 1:
            # 合併並去除重複標題列
            combined_df = pd.concat(dfs, ignore_index=True)
            combined.append(combined_df)
        else:
            combined.append(dfs[0])
    
    return combined

最佳實務

  1. 嘗試兩種方法:有框線用 lattice,無框線用 stream
  2. 檢查準確度分數:高於 90% 通常不錯
  3. 使用視覺化除錯:了解擷取結果
  4. 指定區域:適用於含多種表格類型的 PDF
  5. 處理標題:第一列通常需要特殊處理

常見模式

批次表格擷取

import camelot
from pathlib import Path
import pandas as pd

def batch_extract_tables(input_dir, output_dir):
    """從目錄中所有 PDF 擷取表格。"""
    
    input_path = Path(input_dir)
    output_path = Path(output_dir)
    output_path.mkdir(exist_ok=True)
    
    results = []
    
    for pdf_file in input_path.glob('*.pdf'):
        try:
            tables = camelot.read_pdf(str(pdf_file), pages='all')
            
            for i, table in enumerate(tables):
                # 跳過低準確度表格
                if table.accuracy < 80:
                    continue
                
                output_file = output_path / f"{pdf_file.stem}_table_{i+1}.xlsx"
                table.to_excel(str(output_file))
                
                results.append({
                    'source': str(pdf_file),
                    'table': i + 1,
                    'page': table.page,
                    'accuracy': table.accuracy,
                    'output': str(output_file)
                })
        
        except Exception as e:
            results.append({
                'source': str(pdf_file),
                'error': str(e)
            })
    
    return results

自動偵測表格方法

import camelot

def smart_extract_tables(pdf_path, pages='1'):
    """嘗試兩種方法並回傳最佳結果。"""
    
    # 先嘗試 lattice
    lattice_tables = camelot.read_pdf(pdf_path, pages=pages, flavor='lattice')
    
    # 再嘗試 stream
    stream_tables = camelot.read_pdf(pdf_path, pages=pages, flavor='stream')
    
    # 比較並回傳最佳結果
    results = []
    
    if lattice_tables and lattice_tables[0].accuracy > 70:
        results.extend(lattice_tables)
    elif stream_tables:
        results.extend(stream_tables)
    
    return results

範例

範例 1:財務報表擷取

import camelot
import pandas as pd

def extract_financial_tables(pdf_path):
    """從年報中擷取財務表格。"""
    
    # 擷取所有表格
    tables = camelot.read_pdf(pdf_path, pages='all', flavor='lattice')
    
    financial_data = {
        'income_statement': None,
        'balance_sheet': None,
        'cash_flow': None,
        'other_tables': []
    }
    
    for table in tables:
        df = table.df
        text = df.to_string().lower()
        
        # 辨識表格類型
        if 'revenue' in text or 'sales' in text:
            if 'operating income' in text or 'net income' in text:
                financial_data['income_statement'] = df
        elif 'asset' in text and 'liabilities' in text:
            financial_data['balance_sheet'] = df
        elif 'cash flow' in text or 'operating activities' in text:
            financial_data['cash_flow'] = df
        else:
            financial_data['other_tables'].append({
                'page': table.page,
                'data': df,
                'accuracy': table.accuracy
            })
    
    return financial_data

financials = extract_financial_tables('annual_report.pdf')
if financials['income_statement'] is not None:
    print("找到損益表:")
    print(financials['income_statement'])

範例 2:科學資料擷取

import camelot
import pandas as pd

def extract_research_data(pdf_path, pages='all'):
    """從研究論文中擷取資料表格。"""
    
    # 嘗試 lattice 處理有框線表格
    tables = camelot.read_pdf(pdf_path, pages=pages, flavor='lattice')
    
    if not tables or all(t.accuracy < 70 for t in tables):
        # 退而使用 stream 處理無框線
        tables = camelot.read_pdf(pdf_path, pages=pages, flavor='stream')
    
    extracted_data = []
    
    for table in tables:
        df = table.df
        
        # 清理 DataFrame
        # 若第一列看起來像標題,則設為欄位名稱
        if not df.iloc[0].str.contains(r'\d').any():
            df.columns = df.iloc[0]
            df = df[1:]
            df = df.reset_index(drop=True)
        
        extracted_data.append({
            'page': table.page,
            'accuracy': table.accuracy,
            'data': df
        })
    
    return extracted_data

data = extract_research_data('research_paper.pdf')
for i, item in enumerate(data):
    print(f"表格 {i+1}(第 {item['page']} 頁,準確度:{item['accuracy']}%):")
    print(item['data'].head())

範例 3:發票明細項目

import camelot

def extract_invoice_items(pdf_path):
    """從發票中擷取明細項目。"""
    
    # 發票通常有框線表格
    tables = camelot.read_pdf(pdf_path, flavor='lattice')
    
    line_items = []
    
    for table in tables:
        df = table.df
        
        # 尋找包含典型發票欄位的表格
        header_text = ' '.join(df.iloc[0].astype(str)).lower()
        
        if any(term in header_text for term in ['quantity', 'qty', 'amount', 'price', 'description']):
            # 這看起來像是明細項目表格
            df.columns = df.iloc[0]
            df = df[1:]
            
            for _, row in df.iterrows():
                item = {}
                for col in df.columns:
                    col_lower = str(col).lower()
                    value = row[col]
                    
                    if 'desc' in col_lower or 'item' in col_lower:
                        item['description'] = value
                    elif 'qty' in col_lower or 'quantity' in col_lower:
                        item['quantity'] = value
                    elif 'price' in col_lower or 'rate' in col_lower:
                        item['unit_price'] = value
                    elif 'amount' in col_lower or 'total' in col_lower:
                        item['amount'] = value
                
                if item:
                    line_items.append(item)
    
    return line_items

items = extract_invoice_items('invoice.pdf')
for item in items:
    print(item)

範例 4:表格比較

import camelot
import pandas as pd

def compare_pdf_tables(pdf1_path, pdf2_path):
    """比較兩個 PDF 版本之間的表格。"""
    
    tables1 = camelot.read_pdf(pdf1_path)
    tables2 = camelot.read_pdf(pdf2_path)
    
    comparisons = []
    
    # 依形狀和位置比對表格
    for t1 in tables1:
        best_match = None
        best_score = 0
        
        for t2 in tables2:
            if t1.df.shape == t2.df.shape:
                # 計算相似度
                try:
                    similarity = (t1.df == t2.df).mean().mean()
                    if similarity > best_score:
                        best_score = similarity
                        best_match = t2
                except:
                    pass
        
        if best_match:
            comparisons.append({
                'page1': t1.page,
                'page2': best_match.page,
                'similarity': best_score,
                'identical': best_score == 1.0,
                'diff': pd.DataFrame(t1.df != best_match.df)
            })
    
    return comparisons

comparison = compare_pdf_tables('report_v1.pdf', 'report_v2.pdf')

限制

  • 不支援加密 PDF
  • 以圖片為基礎的 PDF 需要 OCR 前處理
  • 非常複雜的合併儲存格可能需要調整參數
  • 旋轉的表格需要前處理
  • 大型 PDF 可能需要逐頁處理

安裝

pip install camelot-py[cv]

# 額外相依套件
# macOS
brew install ghostscript tcl-tk

# Ubuntu
apt-get install ghostscript python3-tk

資源