$ pip install pyzbar pillow
or
$ uv init
$ uv add pyzbar pillow
$ sudo apt-get install libzbar0
parse code from jpg file
import os
import sys
import csv
import shutil
from PIL import Image, ImageOps
from pyzbar.pyzbar import decode
# Output filename
CSV_FILENAME = "invoices.csv"
def parse_invoice_string(record):
"""
Parses the raw QR string into a dictionary.
"""
record = record.strip()
record = record.split(":")[0]
if len(record) < 53:
return None
try:
invoice_code = record[0:10]
date_minguo = record[10:17]
price_hex = record[29:37]
seller_buy_combined = record[37:53]
buyer_id = seller_buy_combined[0:8]
seller_id = seller_buy_combined[8:16]
year = int(date_minguo[0:3]) + 1911
month = date_minguo[3:5]
day = date_minguo[5:7]
date_str = f'{year}/{month}/{day}'
price = int(price_hex, 16)
return {
"invoice_code": invoice_code,
"seller_id": seller_id,
"buyer_id": buyer_id,
"date": date_str,
"price": price
}
except Exception as e:
return None
def save_to_csv(data):
"""
Appends a single invoice record to the CSV file.
"""
file_exists = os.path.isfile(CSV_FILENAME)
with open(CSV_FILENAME, mode='a', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile)
if not file_exists:
writer.writerow(["統一發票碼", "賣方統編", "日期", "價錢"])
writer.writerow([
data['invoice_code'],
data['seller_id'],
data['date'],
data['price']
])
def scan_single_image(image_path):
"""
處理單張圖片,回傳是否成功找到並解析出至少一張發票。
"""
try:
img = Image.open(image_path)
img = ImageOps.exif_transpose(img)
decoded_objects = decode(img)
qr_objects = [obj for obj in decoded_objects if obj.type == 'QRCODE']
if not qr_objects:
return False
qr_objects.sort(key=lambda obj: obj.rect.left)
valid_count = 0
for obj in qr_objects:
raw_data = obj.data.decode('utf-8')
result = parse_invoice_string(raw_data)
if result:
print(f" ✅ 成功解析: {result['invoice_code']} | ${result['price']}")
save_to_csv(result)
valid_count += 1
return valid_count > 0
except Exception as e:
print(f" ❌ 處理發生錯誤: {e}")
return False
def process_directory(target_dir):
"""
掃描資料夾內的所有圖片。若失敗則拷貝至 failed 資料夾。
"""
if not os.path.isdir(target_dir):
print(f"❌ 找不到目錄: {target_dir}")
return
failed_dir = os.path.join(target_dir, "failed")
# 建立失敗檔案的存放目錄 (若已存在則忽略)
os.makedirs(failed_dir, exist_ok=True)
valid_extensions = {".jpg", ".jpeg", ".png", ".bmp"}
# 列出目標資料夾下所有的檔案 (排除子資料夾)
files = [f for f in os.listdir(target_dir) if os.path.isfile(os.path.join(target_dir, f))]
processed_count = 0
failed_count = 0
for filename in files:
ext = os.path.splitext(filename)[1].lower()
if ext not in valid_extensions:
continue
image_path = os.path.join(target_dir, filename)
print(f"🔍 Scanning: {filename}...")
success = scan_single_image(image_path)
processed_count += 1
if not success:
print(f" ⚠️ 無法辨識,已拷貝至失敗資料夾。")
shutil.copy2(image_path, os.path.join(failed_dir, filename))
failed_count += 1
print(f"\n📊 掃描完成!共處理 {processed_count} 張圖片,其中 {failed_count} 張無法辨識。")
# --- Main Execution ---
if __name__ == "__main__":
if len(sys.argv) > 1:
target_path = sys.argv[1]
else:
print("請提供要掃描的檔案或資料夾路徑。")
sys.exit(1)
# 根據傳入的參數是資料夾還是檔案,決定處理邏輯
if os.path.isdir(target_path):
process_directory(target_path)
elif os.path.isfile(target_path):
print(f"🔍 Scanning single file: {target_path}...")
success = scan_single_image(target_path)
if not success:
print("⚠️ 無法辨識。")
else:
print(f"❌ 找不到指定的路徑: {target_path}")
=================================================================
$ tree 01/
01/
├── drive-download-20260129T092115Z-3-001.zip
├── Notes_123.txt
├── Notes_456.txt
...
...
$ cat 01/Notes_123.txt
YD999900001150109209600000064000000691231132055556666s4oOFzB5IMqpbir5l6eEIQ==:*****
parse_invoice.py
import os
import sys
import csv
def parse_invoice_folder(folder_path, output_file="invoices.csv"):
if not os.path.isdir(folder_path):
print(f"資料夾不存在: {folder_path}")
return
txt_files = [f for f in os.listdir(folder_path) if f.endswith(".txt")]
if not txt_files:
print(f"資料夾內沒有 txt 檔: {folder_path}")
return
with open(output_file, mode='w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(["統一發票碼", "賣方統編", "日期", "價錢"])
for txt_file in txt_files:
file_path = os.path.join(folder_path, txt_file)
with open(file_path, "r", encoding="utf-8") as f:
for line_num, line in enumerate(f, 1):
# 去除 BOM + 前後空格 + 換行
line = line.encode('utf-8').decode('utf-8-sig').strip()
if not line:
continue
# 取冒號前部分
record = line.split(":")[0]
# 檢查長度
if len(record) $lt; 53:
print(f"[跳過] 檔案 {txt_file} 行 {line_num} 字串太短: {record}")
continue
# ✅ 偏移解析
invoice_code = record[0:10] # 統一發票碼
date_minguo = record[10:17] # 發票日期
#price_hex = record[29:37] # 價錢 HEX
price_hex = record[29:37] # 價錢 HEX
seller_buy_combined = record[37:53] # 往後16碼
seller_id = seller_buy_combined[0:8] # 賣方統編
buyer_id = seller_buy_combined[8:16] # 如果需要買方統編
# 日期換算民國→西元
try:
year = int(date_minguo[0:3]) + 1911
month = date_minguo[3:5]
day = date_minguo[5:7]
date_str = f'{year}/{month}/{day}'
except Exception as e:
print(f"[錯誤] 日期解析失敗: {date_minguo} 行 {line_num}")
continue
# 價錢 HEX → 10進位
try:
price = int(price_hex, 16)
except Exception as e:
print(f"[錯誤] 價錢解析失敗: {price_hex} 行 {line_num}")
continue
writer.writerow([invoice_code, buyer_id, f'{date_str}', price])
print(f"CSV 檔案已生成: {output_file}")
if __name__ == "__main__":
if len(sys.argv) != 2:
print("使用方法: python3 parse_invoice.py $lt;資料夾路徑>")
sys.exit(1)
folder_path = sys.argv[1]
parse_invoice_folder(folder_path)
沒有留言:
張貼留言