Przejdź do głównej treści
Wróć do bloga
Inne 8 min czytania

Automatyczna ekstrakcja danych z dokumentów PDF i obrazów w n8n: jak budować przepływy OCR i strukturyzacji treści z AI

10 lipca 2026 Michał Kasprzyk Aktualizacja: 13 lipca 2026

Ekstrakcja danych z PDF i obrazów: kiedy OCR, a kiedy wystarczy tekst

Dokumenty PDF dzielą się na dwie kategorie, które determinują wybór metody ekstrakcji. PDF z osadzonym tekstem (generowane cyfrowo) pozwala na bezpośrednie odczytanie zawartości bez OCR. PDF skanowane oraz zdjęcia dokumentów wymagają rozpoznawania optycznego znaków. Projektując przepływ w n8n, najpierw decyduję o metodzie ekstrakcji na podstawie typu pliku.

Decyzja wygląda tak:

  • PDF cyfrowy → węzeł odczytujący tekst bezpośrednio (np. biblioteka pdf-parse wywołana przez węzeł Code lub dedykowany węzeł Read PDF)
  • PDF skanowany / obraz (JPG, PNG, TIFF) → silnik OCR → tekst → AI do strukturyzacji
  • PDF mieszany (część cyfrowa, część skanowana) → ekstrakcja tekstowa + OCR dla stron bez tekstu → scalenie wyników

W n8n rozróżnienie realizuję węzłem Switch: sprawdzam, czy po bezpośredniej ekstrakcji tekstowej długość wyniku przekracza sensowny próg. Jeśli tekst jest pusty lub fragmentaryczny, kieruję plik do gałęzi OCR.

Wybór silnika OCR pod kątem dokumentów i języka polskiego

Silniki OCR różnią się trafnością rozpoznawania, obsługą języków i kosztem. W przepływach n8n korzystam z trzech głównych opcji:

Tesseract (lokalny) — darmowy, obsługuje język polski po doinstalowaniu pakietu pol.traineddata. Nadaje się do dokumentów o dobrej jakości skanu z wyraźną czcionką. W n8n wywołuję go przez węzeł Execute Command lub własny węzeł n8n-community. Wymaga preprocessing obrazu (progowanie, korekta pochylenia) dla akceptowalnej trafności.

Cloud OCR (Google Vision, Azure Document Intelligence, AWS Textract) — wyższa trafność rozpoznawania, wbudowana obsługa układu dokumentu (layout analysis), tabele i formularze. W n8n łączę się przez węzły HTTP Request z odpowiednim credential. Koszt zależy od liczby stron i wybranego modelu.

Modele multimodalne LLM (GPT-4o, Claude, Gemini) — rozpoznają tekst bezpośrednio z obrazu bez osobnego kroku OCR. Przydatne dla dokumentów o złożonym układzie, ale droższe przy masowym przetwarzaniu i podatne na halucynacje przy gęstych tabelach.

Reguła wyboru w moich przepływach:

  • Proste dokumenty, duże wolumeny → Tesseract + preprocessing
  • Faktury, formularze z tabelami → Azure Document Intelligence lub AWS Textract
  • Nieliczne dokumenty o złożonym układzie → model multimodalny z rygorystyczną walidacją

Architektura przepływu ekstrakcji w n8n

Kompletny przepływ składa się z pięciu etapów:

  1. Pobranie pliku — webhook, monitorowanie folderu (Google Drive, S3), lub węzeł polling
  2. Klasyfikacja i routing — rozpoznanie typu dokumentu i wybranie metody ekstrakcji
  3. Ekstrakcja tekstu — bezpośrednia lub przez OCR
  4. Strukturyzacja AI — prompt LLM z wymuszeniem formatu JSON
  5. Walidacja i zapis — sprawdzenie poprawności struktury, zapis do bazy lub CRM

Każdy etap projektuję jako osobny sub-workflow, co ułatwia testowanie i ponowne wykorzystanie. Główny workflow wywołuje sub-workflow przez węzeł Execute Workflow.

Preprocessing obrazów przed OCR

Jakość OCR zależy od jakości obrazu wejściowego. W n8n realizuję preprocessing w węźle Code z użyciem biblioteki Sharp:

  • Konwersja do skali szarości — redukcja szumu kolorowego
  • Zwiększenie kontrastu — progowanie Otsu lub ręczny próg
  • Korekta pochylenia (deskew) — detekcja kąta nachylenia i obrót
  • Skalowanie DPI — OCR działa najlepiej przy 300 DPI; jeśli obraz ma mniej, skaluję w górę

Przykład logiki w węźle Code:

const sharp = require('sharp');
const inputBuffer = items[0].binary.data;

const processed = await sharp(inputBuffer)
  .grayscale()
  .normalize()
  .threshold(128)
  .toBuffer();

items[0].binary.data = processed;
return items;

Bez tego kroku Tesseract na skanach o niskiej jakości zwraca tekst z dużą liczbą błędów, co obciąża kolejny etap korekty przez AI.

Strukturyzacja tekstu przez LLM: wymuszanie formatu JSON

Surowy tekst z OCR to ciąg znaków bez struktury. Aby przekształcić go w dane użyteczne dla systemu (CRM, ERP, baza), stosuję prompt LLM z wymuszeniem odpowiedzi w formacie JSON.

Przykład promptu do ekstrakcji danych z faktury:

Jesteś systemem ekstrakcji danych z dokumentów.
Na podstawie poniższego tekstu OCR wyciągnij następujące pola i zwróć WYŁĄCZNIE poprawny JSON:

{
  "numer_faktury": "",
  "data_wystawienia": "YYYY-MM-DD",
  "nip_sprzedawcy": "",
  "nazwa_sprzedawcy": "",
  "kwota_netto": 0,
  "kwota_vat": 0,
  "kwota_brutto": 0,
  "waluta": "PLN",
  "pozycje": [
    {"nazwa": "", "ilosc": 0, "cena_jednostkowa": 0, "wartosc": 0}
  ]
}

Tekst OCR:
{{ $json.ocr_text }}

Zasady:
- Jeśli wartość nie występuje w tekście, wstaw null
- Kwoty zapisz jako liczby, nie ciągi znaków
- Daty w formacie ISO
- Nie dodawaj komentarzy, tylko JSON

W n8n konfiguruję węzeł LLM z parametrem response_format: json_object (dla modeli OpenAI) lub odpowiednikiem. Po odpowiedzi parsuję JSON w węźle Code i przechwytuję ewentualne błędy parsowania.

Obsługa błędów parsowania JSON z odpowiedzi LLM

Modele LLM czasami zwracają JSON z błędami: brakujące przecinki, tekst przed lub po bloku JSON, ucięte odpowiedzi. W n8n buduję mechanizm naprawy:

let responseText = $json.response;

// Usuń markdown code fences jeśli model je dodał
responseText = responseText.replace(/```json\n?/g, '').replace(/```/g, '');

// Próba parsowania
try {
  const parsed = JSON.parse(responseText.trim());
  return { json: parsed };
} catch (e) {
  // Próba ekstrakcji JSON z otaczającego tekstu
  const jsonMatch = responseText.match(/\{[\s\S]*\}/);
  if (jsonMatch) {
    try {
      return { json: JSON.parse(jsonMatch[0]) };
    } catch (e2) {
      // Zwróć błąd do gałęzi retry
      throw new Error('JSON parse failed after extraction attempt');
    }
  }
  throw new Error('No JSON found in LLM response');
}

Jeśli parsowanie nie powiedzie się, przepływ trafia do gałęzi retry z węzłem Error Trigger. Konfiguruję maksymalnie 2 ponowne próby z krótkim opóźnieniem. Po wyczerpaniu prób dokument trafia do kolejki do ręcznej weryfikacji.

Walidacja wyekstrahowanych danych

Strukturyzacja JSON to połowa sukcesu. Druga połowa to weryfikacja, czy dane są spójne i sensowne. Buduję reguły walidacji w węźle Code:

Reguły formatu:

  • NIP: dokładnie 10 cyfr (lub 13 z prefiksem kraju)
  • Data: format ISO, data nie z przyszłości
  • Kwoty: typ number, kwota_brutto ≈ kwota_netto + kwota_vat (z tolerancją zaokrągleń)

Reguły spójności biznesowej:

  • Suma wartości pozycji ≈ kwota_netto
  • Waluta z listy dozwolonych (PLN, EUR, USD, GBP)
  • Numer faktury pasuje do wzorca (np. FV/\d{2,4}/\d+)

Przykład walidacji:

const data = $json;
const errors = [];

// Walidacja NIP
if (data.nip_sprzedawcy && !/^\d{10}$/.test(data.nip_sprzedawcy.replace(/[^0-9]/g, ''))) {
  errors.push('NIP sprzedawcy nieprawidłowy');
}

// Walidacja sumy pozycji
if (data.pozycje && data.pozycje.length > 0) {
  const sumaPozycji = data.pozycje.reduce((sum, p) => sum + (p.wartosc || 0), 0);
  if (Math.abs(sumaPozycji - data.kwota_netto) > 0.01) {
    errors.push(`Suma pozycji (${sumaPozycji}) ≠ kwota netto (${data.kwota_netto})`);
  }
}

// Walidacja kwot
const expectedBrutto = data.kwota_netto + data.kwota_vat;
if (Math.abs(expectedBrutto - data.kwota_brutto) > 0.01) {
  errors.push('Kwota brutto niezgodna z netto + VAT');
}

if (errors.length > 0) {
  // Oznacz dokument do weryfikacji ręcznej
  return { json: { ...data, _validation_errors: errors, _requires_review: true } };
}

return { json: { ...data, _validation_errors: [], _requires_review: false } };

Dokumenty z błędami walidacji kieruję do kolejki weryfikacji (np. Google Sheet lub Trello) z dopisaną listą problemów. Dokumenty poprawne trafiają bezpośrednio do systemu docelowego.

Obsługa dokumentów wielostronicowych i tabel

PDF wielostronicowe wymagają podziału na strony przed OCR. W n8n realizuję to w węźle Code z biblioteką pdf-lib:

const pdfLib = require('pdf-lib');
const pdfBytes = items[0].binary.data;
const pdfDoc = await pdfLib.PDFDocument.load(pdfBytes);
const pageCount = pdfDoc.getPageCount();

const pages = [];
for (let i = 0; i < pageCount; i++) {
  const newPdf = await pdfLib.PDFDocument.create();
  const [page] = await newPdf.copyPages(pdfDoc, [i]);
  newPdf.addPage(page);
  const pageBytes = await newPdf.save();
  pages.push({ pageIndex: i, data: Buffer.from(pageBytes) });
}

return pages.map((p, i) => ({
  json: { page_number: i + 1, total_pages: pageCount },
  binary: { data: p.data }
}));

Każdą stronę przetwarzam osobno, co pozwala na:

  • Równoległe wywołania OCR (węzeł Split In Batches z rozmiarem partii)
  • Przypisanie numeru strony do wyniku
  • Obsługę stron w różnych orientacjach (pionowa/pozioma)

Tabele z faktur i formularzy najlepiej ekstrahować przez Azure Document Intelligence lub AWS Textract, które zwracają strukturę komórek. Jeśli korzystam z Tesseract, tabele wymagają dodatkowego promptu AI z instrukcją rekonstrukcji układu kolumnowego.

Human-in-the-loop dla dokumentów o niskiej pewności

Nie każdy dokument da się przetworzyć automatycznie z akceptowalną pewnością. Buduję mechanizm weryfikacji ludzkiej dla przypadków brzegowych:

  1. Próg pewności OCR — jeśli silnik zwraca confidence score poniżej ustalonego progu, dokument trafia do kolejki ręcznej
  2. Błędy walidacji — dokumenty z niepustą listą _validation_errors
  3. Nowe typy dokumentów — pierwszy dokument danego wzorca kieruję do weryfikacji przed automatyzacją

W n8n realizuję to węzłem IF sprawdzającym flagę _requires_review. Dokumenty do weryfikacji wysyłam na Slack lub Microsoft Teams z linkiem do oryginalnego pliku i wyekstrahowanymi danymi. Po akceptacji przez operatora przepływ kontynuuje zapis do systemu docelowego.

Checklist jakości przepływu ekstrakcji

Przed wdrożeniem przepływu na produkcję weryfikuję:

  • Routing PDF cyfrowych vs skanowanych działa poprawnie na próbce minimum 50 dokumentów
  • Preprocessing obrazów poprawia trafność OCR mierzalnie (porównanie przed/po na tej samej próbce)
  • Prompt LLM zwraca poprawny JSON dla co najmniej 90% testowanych dokumentów danego typu
  • Mechanizm naprawy JSON przechwytuje i naprawia typowe błędy formatowania
  • Reguły walidacji biznesowej wyłapują niespójności w kwotach, datach i identyfikatorach
  • Retry logic obsługuje timeouty API i limity zapytań bez utraty dokumentów
  • Dokumenty o niskiej pewności trafiają do kolejki weryfikacji, nie są gubione
  • Sub-workflow są wersjonowane i testowane niezależnie
  • Logi zawierają źródło dokumentu, wynik OCR, odpowiedź LLM i wynik walidacji dla audytu

Koszty i optymalizacja

Ekstrakcja z użyciem OCR i LLM generuje koszty na dwóch etapach: silnik OCR i wywołanie modelu. Strategie optymalizacji:

  • Buforowanie wyników OCR — ten sam dokument nie powinien być poddawany OCR ponownie. Przechowuję wynik w bazie z hashem pliku jako kluczem
  • Routing modeli — proste dokumenty (standardowe faktury) kieruję do tańszego modelu, złożone do modelu o wyższej trafności
  • Batch processing — grupowanie zapytań OCR i LLM zmniejsza narzut na połączenia HTTP
  • Lokalny OCR dla dużych wolumenów — Tesseract nie generuje kosztów API, ale wymaga serwera z odpowiednią mocą obliczeniową

Koszty monitoruję przez węzły Code zliczające tokeny i strony, zapisując wyniki do bazy w celu comiesięcznej analizy.

Integracja wyników z systemami docelowymi

Po walidacji wyekstrahowane dane trafiają do systemu docelowego. W n8n korzystam z gotowych węzłów integracyjnych lub HTTP Request:

  • CRM — tworzenie lub aktualizacja rekordu firmy na podstawie NIP
  • System księgowy — import faktur z mapowaniem pól na schemat docelowego API
  • Baza danych — zapis do PostgreSQL lub Google BigQuery z deduplikacją po numerze dokumentu
  • Google Sheets / Excel — dla procesów wymagających ręcznego dosłuchu

Deduplikację realizuję po numerze dokumentu i NIP sprzedawcy przed zapisem, aby uniknąć podwójnego importu tej samej faktury.


Ekstrakcja danych z dokumentów PDF i obrazów to proces, który projektuję pod konkretne typy dokumentów i wymogi biznesowe. Jeśli potrzebujesz przepływu automatycznej ekstrakcji dopasowanego do Twoich dokumentów i systemów, mogę zaprojektować i wdrożyć rozwiązanie w n8n — od wyboru silnika OCR po integrację z CRM lub systemem księgowym.

👨‍💻

Michał Kasprzyk

Tworzę nowoczesne strony internetowe dla firm z całej Polski. Specjalizuję się w szybkich, bezpiecznych i zoptymalizowanych pod SEO witrynach.

Prowadzę też osobną działalność jako agent ubezpieczeniowy (mkasprzyk.pl) — bez związku z usługami Qualix.

Więcej o mnie

Powiązane artykuły

Inne

Strona one-page czy wielopodstronicowa — jaką strukturę wybrać dla firmy usługowej

Strona one-page czy z podstronami? Sprawdź, jak struktura strony firmy usługowej wpływa na widoczność w Google i zapytania, oraz kiedy jedna strona wystarczy.

Inne

Automatyzacja procesów w małej firmie usługowej z n8n — co zautomatyzować i jak zacząć

Automatyzacja procesów w małej firmie usługowej z n8n — sprawdź, jakie zadania zautomatyzować, jak zaplanować workflow i jak uniknąć częstych błędów.

Inne

Oprogramowanie dedykowane dla firm: kiedy gotowe systemy przestają wystarczać?

Dowiedz się, kiedy oprogramowanie dedykowane dla firm jest lepsze od gotowych systemów SaaS i jak uniknąć vendor lock-in w swoim biznesie.

Potrzebujesz strony internetowej?

Skontaktuj się ze mną, aby omówić Twój projekt. Pierwsza konsultacja jest bezpłatna.

Zamów bezpłatną wycenę
Napisz na WhatsApp