239 lines
7.9 KiB
Python
239 lines
7.9 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
PDF Text Extractor - Udvinder tekst fra både almindelige og scannede PDF'er
|
|
Støtter flere metoder: pdfplumber, PyPDF2 og OCR med tesseract
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import re
|
|
from pathlib import Path
|
|
import json
|
|
|
|
# PDF processing libraries
|
|
import pdfplumber
|
|
import PyPDF2
|
|
import pytesseract
|
|
from pdf2image import convert_from_path
|
|
from PIL import Image
|
|
|
|
def extract_with_pdfplumber(pdf_path):
|
|
"""Udtrækker tekst med pdfplumber - bedst til moderne PDF'er"""
|
|
try:
|
|
text = ""
|
|
with pdfplumber.open(pdf_path) as pdf:
|
|
for page in pdf.pages:
|
|
page_text = page.extract_text()
|
|
if page_text:
|
|
text += page_text + "\n"
|
|
return text.strip()
|
|
except Exception as e:
|
|
print(f"pdfplumber fejl: {e}")
|
|
return None
|
|
|
|
def extract_with_pypdf2(pdf_path):
|
|
"""Udtrækker tekst med PyPDF2 - god backup metode"""
|
|
try:
|
|
text = ""
|
|
with open(pdf_path, 'rb') as file:
|
|
reader = PyPDF2.PdfReader(file)
|
|
for page in reader.pages:
|
|
page_text = page.extract_text()
|
|
if page_text:
|
|
text += page_text + "\n"
|
|
return text.strip()
|
|
except Exception as e:
|
|
print(f"PyPDF2 fejl: {e}")
|
|
return None
|
|
|
|
def extract_with_ocr(pdf_path, language='dan+eng'):
|
|
"""Udtrækker tekst med OCR - til scannede PDF'er"""
|
|
try:
|
|
print(f"Starter OCR behandling af {pdf_path}...")
|
|
|
|
# Konverter PDF til billeder
|
|
images = convert_from_path(pdf_path, dpi=300)
|
|
|
|
text = ""
|
|
for i, image in enumerate(images):
|
|
print(f"Behandler side {i+1}/{len(images)}...")
|
|
|
|
# Kør OCR på billedet
|
|
page_text = pytesseract.image_to_string(image, lang=language)
|
|
if page_text.strip():
|
|
text += f"\n--- SIDE {i+1} ---\n"
|
|
text += page_text + "\n"
|
|
|
|
return text.strip()
|
|
except Exception as e:
|
|
print(f"OCR fejl: {e}")
|
|
return None
|
|
|
|
def clean_text(text):
|
|
"""Rengør og formater den udtrukne tekst"""
|
|
if not text:
|
|
return ""
|
|
|
|
# Fjern excessive whitespace
|
|
text = re.sub(r'\s+', ' ', text)
|
|
|
|
# Fix line breaks
|
|
text = re.sub(r'([.!?])\s+([A-ZÆØÅ])', r'\1\n\2', text)
|
|
|
|
# Clean up
|
|
text = text.strip()
|
|
|
|
return text
|
|
|
|
def analyze_construction_content(text):
|
|
"""Analysér tekstindhold for tømrer/byggerelateret information"""
|
|
if not text:
|
|
return {"found": False, "categories": [], "tasks": []}
|
|
|
|
# Søgeord for forskellige kategorier
|
|
construction_keywords = {
|
|
"fundamenter": ["fundament", "sokkel", "grundmur", "beton", "armering"],
|
|
"bindingsværk": ["stolpe", "rem", "bindingsværk", "spær", "bjælke"],
|
|
"tagarbejde": ["tag", "tagsten", "tegl", "rygning", "tagrender", "nedfald"],
|
|
"gulve": ["gulv", "gulvbjælke", "underlag", "isolering", "gulvbrædder"],
|
|
"vægge": ["væg", "ydervæg", "skillevæg", "isolering", "vindspærre"],
|
|
"døre_vinduer": ["dør", "vindue", "karm", "hængsel", "lås"],
|
|
"trapper": ["trappe", "trin", "vange", "gelænder", "repos"],
|
|
"værktøj": ["hammer", "save", "bor", "skruer", "søm", "målebånd"],
|
|
"materialer": ["træ", "brædder", "lægter", "krydsfiner", "spånplade"],
|
|
"teknikker": ["samling", "boring", "savning", "høvling", "slibning"]
|
|
}
|
|
|
|
found_categories = []
|
|
found_tasks = []
|
|
text_lower = text.lower()
|
|
|
|
for category, keywords in construction_keywords.items():
|
|
category_matches = []
|
|
for keyword in keywords:
|
|
if keyword in text_lower:
|
|
category_matches.append(keyword)
|
|
|
|
if category_matches:
|
|
found_categories.append({
|
|
"category": category,
|
|
"matches": category_matches,
|
|
"count": len(category_matches)
|
|
})
|
|
|
|
# Find potentielle opgaver (linjier der starter med numre eller punkter)
|
|
task_patterns = [
|
|
r'(\d+\.?\s+[A-ZÆØÅ][^.!?]*[.!?])', # Nummererede punkter
|
|
r'(-\s+[A-ZÆØÅ][^.!?]*[.!?])', # Bullet points
|
|
r'([A-ZÆØÅ][^.!?]{20,}[.!?])' # Lange sætninger (potentielle instruktioner)
|
|
]
|
|
|
|
for pattern in task_patterns:
|
|
matches = re.findall(pattern, text)
|
|
for match in matches:
|
|
if len(match) > 30: # Kun længere instruktioner
|
|
found_tasks.append(match.strip())
|
|
|
|
return {
|
|
"found": len(found_categories) > 0,
|
|
"categories": found_categories,
|
|
"tasks": found_tasks[:20], # Max 20 opgaver
|
|
"total_text_length": len(text),
|
|
"total_words": len(text.split())
|
|
}
|
|
|
|
def extract_pdf_text(pdf_path, output_file=None):
|
|
"""Hovedfunktion der prøver alle metoder for at udtrække tekst"""
|
|
|
|
if not os.path.exists(pdf_path):
|
|
print(f"Fil ikke fundet: {pdf_path}")
|
|
return None
|
|
|
|
print(f"Behandler PDF: {pdf_path}")
|
|
|
|
# Prøv pdfplumber først (bedst til normale PDF'er)
|
|
print("Prøver pdfplumber...")
|
|
text = extract_with_pdfplumber(pdf_path)
|
|
method_used = "pdfplumber"
|
|
|
|
# Hvis det ikke virker, prøv PyPDF2
|
|
if not text or len(text.strip()) < 100:
|
|
print("Prøver PyPDF2...")
|
|
text = extract_with_pypdf2(pdf_path)
|
|
method_used = "PyPDF2"
|
|
|
|
# Hvis stadig intet, prøv OCR
|
|
if not text or len(text.strip()) < 100:
|
|
print("Tekst ikke fundet - starter OCR...")
|
|
text = extract_with_ocr(pdf_path)
|
|
method_used = "OCR"
|
|
|
|
if text:
|
|
# Rengør teksten
|
|
text = clean_text(text)
|
|
|
|
# Analyser indhold
|
|
analysis = analyze_construction_content(text)
|
|
|
|
print(f"\n✅ Tekst ekstraheret med {method_used}")
|
|
print(f"📝 Total længde: {len(text)} tegn")
|
|
print(f"📊 Analyse: {'Byggerelateret indhold fundet' if analysis['found'] else 'Intet byggerelateret indhold fundet'}")
|
|
|
|
if analysis['found']:
|
|
print(f"🏗️ Kategorier fundet: {len(analysis['categories'])}")
|
|
print(f"📋 Opgaver fundet: {len(analysis['tasks'])}")
|
|
|
|
# Gem til fil hvis specificeret
|
|
if output_file:
|
|
with open(output_file, 'w', encoding='utf-8') as f:
|
|
f.write("=== PDF TEKST EKSTRAKTION ===\n")
|
|
f.write(f"Kilde: {pdf_path}\n")
|
|
f.write(f"Metode: {method_used}\n")
|
|
f.write(f"Dato: {sys.exc_info()}\n")
|
|
f.write("\n=== TEKST ===\n")
|
|
f.write(text)
|
|
|
|
f.write(f"\n\n=== ANALYSE ===\n")
|
|
f.write(json.dumps(analysis, ensure_ascii=False, indent=2))
|
|
|
|
print(f"💾 Resultat gemt til: {output_file}")
|
|
|
|
return {
|
|
"text": text,
|
|
"method": method_used,
|
|
"analysis": analysis,
|
|
"success": True
|
|
}
|
|
|
|
else:
|
|
print("❌ Kunne ikke udtrække tekst fra PDF")
|
|
return {"success": False, "error": "Ingen tekst fundet"}
|
|
|
|
def main():
|
|
"""Hovedprogram"""
|
|
if len(sys.argv) < 2:
|
|
print("Brug: python pdf_extractor.py <pdf_fil> [output_fil]")
|
|
sys.exit(1)
|
|
|
|
pdf_file = sys.argv[1]
|
|
output_file = sys.argv[2] if len(sys.argv) > 2 else None
|
|
|
|
result = extract_pdf_text(pdf_file, output_file)
|
|
|
|
if result and result.get("success"):
|
|
print("\n" + "="*50)
|
|
print("UDTRUKKET TEKST (første 1000 tegn):")
|
|
print("="*50)
|
|
print(result["text"][:1000] + ("..." if len(result["text"]) > 1000 else ""))
|
|
|
|
if result["analysis"]["found"]:
|
|
print("\n" + "="*50)
|
|
print("FUNDET KATEGORIER:")
|
|
print("="*50)
|
|
for cat in result["analysis"]["categories"]:
|
|
print(f"• {cat['category']}: {', '.join(cat['matches'])}")
|
|
|
|
return result
|
|
|
|
if __name__ == "__main__":
|
|
main() |