Koniec miesiąca. W skrzynce mailowej czeka 30 faktur PDF od różnych dostawców. Każdą trzeba otworzyć, odczytać numer, kwotę, NIP sprzedawcy i przepisać do arkusza. Brzmi jak koszmar? Zbudujmy agenta AI, który zrobi to za nas — automatycznie.
W tym poradniku pokażę Ci krok po kroku, jak stworzyć agenta AI w Pythonie, który:
- łączy się z Twoją skrzynką Gmail,
- znajduje emaile z fakturami,
- pobiera załączniki PDF,
- wyciąga z nich dane (numer, daty, kwoty, NIP-y) za pomocą Claude API,
- zapisuje wszystko do pliku CSV gotowego do otwarcia w Excelu.
Całość wymaga zainstalowania zaledwie trzech zewnętrznych paczek. Reszta to standardowa biblioteka Pythona.
Co będzie potrzebne?
Zanim zaczniemy kodować, przygotuj trzy rzeczy:
- Python 3.11+ — zainstalowany na komputerze (python.org/downloads)
- Klucz API Anthropic — zarejestruj się na console.anthropic.com i wygeneruj klucz API. Nowe konto dostaje darmowe kredyty na start.
- Hasło aplikacji Gmail — potrzebne, żeby nasz skrypt mógł się połączyć ze skrzynką. Jak je uzyskać, opisuję poniżej.
Jak wygenerować hasło aplikacji Gmail?
- Wejdź na myaccount.google.com/security
- Włącz weryfikację dwuetapową (jeśli jeszcze nie masz)
- Przejdź do myaccount.google.com/apppasswords
- Wpisz nazwę (np. „Agent faktur”) i kliknij „Utwórz”
- Skopiuj 16-znakowe hasło — użyjemy go zamiast prawdziwego hasła do Gmaila
Instalacja zależności
Otwórz terminal i zainstaluj trzy paczki:
pip install anthropic pdfplumber pydantic
To wszystko. Reszta (imaplib, email, csv, os, json) jest wbudowana w Pythona.
Następnie ustaw zmienne środowiskowe z Twoimi danymi:
# Linux / macOS:
export ANTHROPIC_API_KEY="sk-ant-twoj-klucz"
export GMAIL_ADDRESS="[email protected]"
export GMAIL_APP_PASSWORD="abcd efgh ijkl mnop"
# Windows (PowerShell):
$env:ANTHROPIC_API_KEY="sk-ant-twoj-klucz"
$env:GMAIL_ADDRESS="[email protected]"
$env:GMAIL_APP_PASSWORD="abcd efgh ijkl mnop"
Budujemy agenta — krok po kroku
Nasz agent będzie działał w czterech krokach — dokładnie tak, jak opisywałem w artykule Co to agenci AI?: obserwuje (czyta emaile), myśli (analizuje tekst faktur), działa (wyciąga dane) i zapisuje wynik.
Krok 1: Połączenie z Gmailem i pobranie faktur PDF
Używamy wbudowanego modułu imaplib, żeby połączyć się z Gmailem przez protokół IMAP. Szukamy emaili ze słowem „faktura” w temacie i pobieramy z nich załączniki PDF.
import imaplib
import email
from email.header import decode_header
import os
IMAP_SERVER = "imap.gmail.com"
EMAIL_ACCOUNT = os.environ["GMAIL_ADDRESS"]
APP_PASSWORD = os.environ["GMAIL_APP_PASSWORD"]
def connect_and_download_pdfs(subject_filter="faktura", save_dir="./invoices"):
"""Łączy się z Gmailem, szuka emaili z fakturami, pobiera PDF-y."""
mail = imaplib.IMAP4_SSL(IMAP_SERVER)
mail.login(EMAIL_ACCOUNT, APP_PASSWORD)
mail.select("INBOX")
# Szukaj emaili z danym słowem w temacie
status, messages = mail.search(None, f'(SUBJECT "{subject_filter}")')
email_ids = messages[0].split() if status == "OK" else []
print(f"Znaleziono {len(email_ids)} emaili.")
os.makedirs(save_dir, exist_ok=True)
downloaded = []
for eid in email_ids:
status, msg_data = mail.fetch(eid, "(RFC822)")
if status != "OK":
continue
msg = email.message_from_bytes(msg_data[0][1])
# Dekoduj temat emaila
subject_raw, enc = decode_header(msg["Subject"])[0]
if isinstance(subject_raw, bytes):
subject_raw = subject_raw.decode(enc or "utf-8")
# Szukaj załączników PDF
for part in msg.walk():
if "attachment" not in str(part.get("Content-Disposition", "")):
continue
filename = part.get_filename()
if not filename:
continue
fname, fenc = decode_header(filename)[0]
if isinstance(fname, bytes):
fname = fname.decode(fenc or "utf-8")
if fname.lower().endswith(".pdf"):
filepath = os.path.join(save_dir, fname)
with open(filepath, "wb") as f:
f.write(part.get_payload(decode=True))
downloaded.append({
"subject": str(subject_raw),
"pdf_path": filepath,
"pdf_filename": fname,
})
print(f" Pobrano: {fname}")
mail.logout()
return downloaded
Krok 2: Ekstrakcja tekstu z PDF
Biblioteka pdfplumber świetnie radzi sobie z fakturami — wyciąga tekst, tabele i zachowuje układ strony. Większość faktur w Polsce (z InFaktu, wFirmy, iFirmy itp.) to cyfrowe PDF-y, więc tekst da się odczytać bez OCR.
import pdfplumber
def extract_text(pdf_path):
"""Wyciąga tekst ze wszystkich stron PDF-a."""
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()
Uwaga: Jeśli faktura jest skanem (zdjęciem), pdfplumber zwróci pusty tekst. W takim przypadku potrzebny byłby OCR (np. pytesseract) — ale to temat na osobny artykuł.
Krok 3: Wyciąganie danych za pomocą Claude API
To serce naszego agenta. Wysyłamy tekst faktury do modelu Claude i prosimy o zwrócenie ustrukturyzowanych danych w formacie JSON. Dzięki mechanizmowi structured outputs dostajemy gwarancję, że odpowiedź będzie dokładnie w tym schemacie, który zdefiniujemy.
Najpierw definiujemy model danych faktury:
import anthropic
import json
from pydantic import BaseModel, Field
from typing import Optional
class InvoiceData(BaseModel):
"""Struktura danych polskiej faktury VAT."""
numer_faktury: str = Field(description="Numer faktury")
data_wystawienia: str = Field(description="Data wystawienia (YYYY-MM-DD)")
data_sprzedazy: Optional[str] = Field(None, description="Data sprzedaży")
sprzedawca_nazwa: str = Field(description="Nazwa sprzedawcy")
sprzedawca_nip: Optional[str] = Field(None, description="NIP sprzedawcy")
nabywca_nazwa: str = Field(description="Nazwa nabywcy")
nabywca_nip: Optional[str] = Field(None, description="NIP nabywcy")
kwota_netto: str = Field(description="Kwota netto, np. 1000.00")
kwota_vat: str = Field(description="Kwota VAT, np. 230.00")
kwota_brutto: str = Field(description="Kwota brutto, np. 1230.00")
waluta: str = Field(default="PLN", description="Waluta")
stawka_vat: Optional[str] = Field(None, description="Stawka VAT, np. 23%")
Teraz funkcja, która wysyła tekst faktury do Claude:
def extract_invoice_data(invoice_text):
"""Wysyła tekst faktury do Claude i odbiera ustrukturyzowane dane."""
client = anthropic.Anthropic() # Klucz API z ANTHROPIC_API_KEY
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
messages=[{
"role": "user",
"content": f"""Przeanalizuj tekst polskiej faktury VAT i wyodrębnij dane.
Kwoty podaj z dwoma miejscami po przecinku. Daty w formacie YYYY-MM-DD.
Jeśli czegoś nie znajdziesz, użyj null.
TEKST FAKTURY:
---
{invoice_text}
---"""
}],
)
return json.loads(response.content[0].text)
Dlaczego Claude Haiku 4.5? Ekstrakcja danych z faktury to dla modelu AI stosunkowo proste zadanie — dane są wprost w tekście, trzeba je tylko znaleźć i ustrukturyzować. Haiku jest szybki i tani: przetworzenie jednej faktury kosztuje około 0,01 zł. Tysiąc faktur to jakieś 10 zł.
Krok 4: Zapis do CSV
import csv
def save_to_csv(rows, output_path="faktury.csv"):
"""Zapisuje listę faktur do pliku CSV (czytelnego w Excelu)."""
fieldnames = [
"numer_faktury", "data_wystawienia", "data_sprzedazy",
"sprzedawca_nazwa", "sprzedawca_nip",
"nabywca_nazwa", "nabywca_nip",
"kwota_netto", "kwota_vat", "kwota_brutto",
"waluta", "stawka_vat", "plik_zrodlowy",
]
# utf-8-sig = UTF-8 z BOM, dzięki czemu Excel poprawnie
# wyświetli polskie znaki (ą, ę, ź...)
with open(output_path, "w", newline="", encoding="utf-8-sig") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction="ignore")
writer.writeheader()
writer.writerows(rows)
print(f"Zapisano {len(rows)} faktur do {output_path}")
Ważna uwaga: Używamy kodowania utf-8-sig zamiast zwykłego utf-8. Bez tego Excel na Windowsie pokaże krzaczki zamiast polskich znaków. To klasyczna pułapka, na którą trafia wielu programistów.
Kompletny skrypt — złóżmy wszystko w całość
Poniżej znajdziesz gotowy do uruchomienia skrypt. Skopiuj go, ustaw zmienne środowiskowe i odpal:
#!/usr/bin/env python3
"""
Agent AI do ekstrakcji faktur z emaila.
Łączy się z Gmailem, pobiera PDF-y, wyciąga dane przez Claude API,
zapisuje do CSV.
Użycie:
export ANTHROPIC_API_KEY="sk-ant-..."
export GMAIL_ADDRESS="[email protected]"
export GMAIL_APP_PASSWORD="abcd efgh ijkl mnop"
python agent_faktur.py
"""
import imaplib
import email
from email.header import decode_header
import os
import json
import csv
import anthropic
import pdfplumber
from pydantic import BaseModel, Field
from typing import Optional
# ── Konfiguracja ─────────────────────────────────────────────
IMAP_SERVER = "imap.gmail.com"
EMAIL_ACCOUNT = os.environ["GMAIL_ADDRESS"]
APP_PASSWORD = os.environ["GMAIL_APP_PASSWORD"]
SEARCH_SUBJECT = "faktura"
SAVE_DIR = "./invoices"
OUTPUT_CSV = "faktury.csv"
# ── Model danych faktury ─────────────────────────────────────
class InvoiceData(BaseModel):
numer_faktury: str = Field(description="Numer faktury")
data_wystawienia: str = Field(description="Data wystawienia (YYYY-MM-DD)")
data_sprzedazy: Optional[str] = Field(None, description="Data sprzedaży")
sprzedawca_nazwa: str = Field(description="Nazwa sprzedawcy")
sprzedawca_nip: Optional[str] = Field(None, description="NIP sprzedawcy")
nabywca_nazwa: str = Field(description="Nazwa nabywcy")
nabywca_nip: Optional[str] = Field(None, description="NIP nabywcy")
kwota_netto: str = Field(description="Kwota netto, np. 1000.00")
kwota_vat: str = Field(description="Kwota VAT, np. 230.00")
kwota_brutto: str = Field(description="Kwota brutto, np. 1230.00")
waluta: str = Field(default="PLN", description="Waluta")
stawka_vat: Optional[str] = Field(None, description="Stawka VAT, np. 23%")
# ── Krok 1: Gmail → PDF-y ───────────────────────────────────
def connect_and_download_pdfs():
print(f"Łączenie z {IMAP_SERVER}...")
mail = imaplib.IMAP4_SSL(IMAP_SERVER)
mail.login(EMAIL_ACCOUNT, APP_PASSWORD)
mail.select("INBOX")
status, messages = mail.search(None, f'(SUBJECT "{SEARCH_SUBJECT}")')
email_ids = messages[0].split() if status == "OK" else []
print(f"Znaleziono {len(email_ids)} emaili z '{SEARCH_SUBJECT}' w temacie.")
os.makedirs(SAVE_DIR, exist_ok=True)
downloaded = []
for eid in email_ids:
status, msg_data = mail.fetch(eid, "(RFC822)")
if status != "OK":
continue
msg = email.message_from_bytes(msg_data[0][1])
subj_raw, enc = decode_header(msg["Subject"])[0]
if isinstance(subj_raw, bytes):
subj_raw = subj_raw.decode(enc or "utf-8")
for part in msg.walk():
if "attachment" not in str(part.get("Content-Disposition", "")):
continue
filename = part.get_filename()
if not filename:
continue
fname, fenc = decode_header(filename)[0]
if isinstance(fname, bytes):
fname = fname.decode(fenc or "utf-8")
if fname.lower().endswith(".pdf"):
filepath = os.path.join(SAVE_DIR, fname)
with open(filepath, "wb") as f:
f.write(part.get_payload(decode=True))
downloaded.append({
"subject": str(subj_raw),
"pdf_path": filepath,
"pdf_filename": fname,
})
print(f" 📄 {fname}")
mail.logout()
return downloaded
# ── Krok 2: PDF → tekst ─────────────────────────────────────
def extract_text(pdf_path):
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()
# ── Krok 3: Tekst → dane (Claude API) ───────────────────────
def extract_invoice_data(invoice_text):
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
messages=[{
"role": "user",
"content": f"""Przeanalizuj tekst polskiej faktury VAT i wyodrębnij dane.
Kwoty z dwoma miejscami po przecinku. Daty YYYY-MM-DD. Brak danych = null.
Odpowiedz TYLKO obiektem JSON zgodnym z podanym schematem.
Schemat:
{json.dumps(InvoiceData.model_json_schema(), indent=2)}
TEKST FAKTURY:
---
{invoice_text}
---"""
}],
)
raw = json.loads(response.content[0].text)
return InvoiceData(**raw)
# ── Krok 4: Dane → CSV ──────────────────────────────────────
def save_to_csv(rows):
fieldnames = [
"numer_faktury", "data_wystawienia", "data_sprzedazy",
"sprzedawca_nazwa", "sprzedawca_nip",
"nabywca_nazwa", "nabywca_nip",
"kwota_netto", "kwota_vat", "kwota_brutto",
"waluta", "stawka_vat", "plik_zrodlowy",
]
with open(OUTPUT_CSV, "w", newline="", encoding="utf-8-sig") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction="ignore")
writer.writeheader()
writer.writerows(rows)
print(f"n✅ Zapisano {len(rows)} faktur do {OUTPUT_CSV}")
# ── Główna logika agenta ─────────────────────────────────────
def main():
# 1. Pobierz PDF-y
pdfs = connect_and_download_pdfs()
if not pdfs:
print("Brak faktur do przetworzenia.")
return
# 2-3. Przetwórz każdy PDF
results = []
for info in pdfs:
print(f"nPrzetwarzam: {info['pdf_filename']}...")
text = extract_text(info["pdf_path"])
if not text:
print(" ⚠️ Pusty tekst (skan?). Pomijam.")
continue
try:
invoice = extract_invoice_data(text)
row = invoice.model_dump()
row["plik_zrodlowy"] = info["pdf_filename"]
results.append(row)
print(f" ✓ {invoice.numer_faktury} → "
f"{invoice.kwota_brutto} {invoice.waluta}")
except Exception as e:
print(f" ✗ Błąd: {e}")
# 4. Zapisz do CSV
if results:
save_to_csv(results)
if __name__ == "__main__":
main()
Uruchomienie
Zapisz skrypt jako agent_faktur.py i uruchom:
python agent_faktur.py
Przykładowy wynik w terminalu:
Łączenie z imap.gmail.com...
Znaleziono 5 emaili z 'faktura' w temacie.
📄 FV_2026_01_042.pdf
📄 FV-123-2026.pdf
📄 faktura_luty.pdf
Przetwarzam: FV_2026_01_042.pdf...
✓ FV/2026/01/042 → 1230.00 PLN
Przetwarzam: FV-123-2026.pdf...
✓ FV-123/2026 → 615.00 PLN
Przetwarzam: faktura_luty.pdf...
✓ 03/02/2026 → 3690.00 PLN
✅ Zapisano 3 faktury do faktury.csv
Plik faktury.csv otworzy się poprawnie w Excelu — z polskimi znakami, kolumnami i wszystkimi danymi.
Ile to kosztuje?
Model Claude Haiku 4.5, którego używamy, jest jednym z najtańszych dostępnych modeli AI:
| Ilość faktur | Przybliżony koszt |
|---|---|
| 1 faktura | ~0,01 zł |
| 100 faktur | ~1 zł |
| 1 000 faktur | ~10 zł |
Typowa faktura to 500–1500 tokenów tekstu. Prompt dodaje około 100 tokenów, a odpowiedź JSON to kolejne 200–400. Przy cenach Haiku ($1 za milion tokenów wejściowych) wychodzi grosze za fakturę.
Praktyczne wskazówki
Bezpieczeństwo
- Nigdy nie wklejaj kluczy API ani haseł do kodu. Używaj zmiennych środowiskowych albo pliku
.envz bibliotekąpython-dotenv. - Hasło aplikacji Gmail daje pełny dostęp do skrzynki. Gdy skończysz testować, możesz je odwołać na myaccount.google.com/apppasswords.
- Nie udostępniaj pliku CSV z danymi faktur — zawiera NIP-y i kwoty.
Na co uważać?
- Skany PDF — jeśli faktura jest zdjęciem (skan z drukarki),
pdfplumbernie wyciągnie tekstu. Potrzebujesz wtedy OCR (np.pytesseract). Większość faktur przesyłanych mailem w Polsce to jednak cyfrowe PDF-y i ten problem nie wystąpi. - Kolizje nazw plików — dwa emaile mogą mieć załącznik o tej samej nazwie „faktura.pdf”. W wersji produkcyjnej warto dodać do nazwy datę lub ID emaila.
- Wielostronicowe faktury — nasz skrypt obsługuje je poprawnie — łączy tekst ze wszystkich stron.
- Halucynacje — model AI może czasem „wymyślić” brakujące dane. Warto zweryfikować wynik, zwłaszcza przy pierwszych uruchomieniach. Z czasem przekonasz się, jak dokładny jest model na Twoich fakturach.
Nie programujesz? Są alternatywy
Jeśli wolisz rozwiązanie bez kodowania, istnieją narzędzia no-code, które potrafią zrobić coś podobnego:
- n8n — darmowe narzędzie open source do automatyzacji. Łączy Gmail, ekstrakcję PDF i AI w wizualnym edytorze przepływów. Wymaga self-hostingu (Docker) lub konta w chmurze (~80 zł/mies.).
- Make (dawniej Integromat) — wizualny kreator automatyzacji. Moduł Gmail + moduł PDF + Google Sheets. Darmowy plan: 1000 operacji/miesiąc.
- Zapier — najprostsze rozwiązanie no-code, ale płatne ($19.99/mies. za wielokrokowe „zapy”).
- InFakt / wFirma / iFirma — polskie programy księgowe, które mają wbudowane funkcje importu faktur z emaila.
Przewaga podejścia z Pythonem? Pełna kontrola. Wiesz dokładnie, co się dzieje z Twoimi danymi, możesz dostosować logikę do swoich potrzeb i nie płacisz za subskrypcję.
Co dalej?
Nasz agent jest świetnym punktem startowym. Oto kilka pomysłów, jak go rozbudować:
- Automatyczny cron — uruchamiaj agenta codziennie o 8:00, żeby przetwarzał nowe faktury.
- OCR dla skanów — dodaj
pytesseractjako fallback, gdypdfplumbernie wyciągnie tekstu. - Google Sheets zamiast CSV — użyj biblioteki
gspread, żeby zapisywać dane bezpośrednio do arkusza w chmurze. - Powiadomienia — wyślij sobie podsumowanie na Slacka lub mailem po przetworzeniu.
- Walidacja NIP — sprawdzaj NIP-y z bazą VIES lub REGON.
Podsumowanie
W kilkudziesięciu linijkach Pythona zbudowaliśmy agenta AI, który automatyzuje żmudne zadanie — ręczne przepisywanie danych z faktur. To prosty, ale praktyczny przykład tego, jak agenci AI mogą odciążyć nas od powtarzalnej pracy.
Kluczowe wnioski:
- Do zbudowania agenta AI nie trzeba setek linii kodu ani specjalistycznej wiedzy.
- Modele językowe (jak Claude) świetnie radzą sobie z ekstrakcją ustrukturyzowanych danych z tekstu.
- Koszt przetworzenia faktur jest znikomy — grosze za sztukę.
- Największą wartością jest czas, który oszczędzasz — zamiast 2 godzin ręcznej pracy, agent przetwarza 100 faktur w kilka minut.
Skopiuj skrypt, dostosuj do swoich potrzeb i przetestuj na kilku fakturach. A jak stworzysz coś ciekawego — daj znać w komentarzu!