Echtzeit-Rechnungsdatenextraktion mit PHP und AWS Textract
Nutzen Sie die synchrone Operation AnalyzeExpense von AWS Textract, um aus einem
lokalen Rechnungsbild JSON-Kandidaten zur Prüfung zu gewinnen. Die folgende PHP-CLI erhält
wiederholte Felder, Rechnungspositionen und numerische Konfidenzwerte.
Sie prüft weder die rechnerische Richtigkeit einer Rechnung noch autorisiert sie eine Zahlung.
AWS Textract und das PHP SDK v3 einrichten
Sie benötigen PHP mit cURL, Fileinfo, SimpleXML und GD mit PNG-/JPEG-Unterstützung sowie separat installiertes Composer und Bash für die Shell-Befehle. Dieses Beispiel wurde mit PHP 8.5.11 und Composer 2.10.3 getestet. Verwenden Sie eine gepflegte PHP-8.5-Version; das SDK selbst unterstützt PHP ab 8.1, doch das belegt nicht die Kompatibilität aller Abhängigkeiten dieser Installation.
Konfigurieren Sie die AWS-Zugangsdaten mithilfe der
Zugangsdaten-Provider-Kette des SDK.
Der aufrufende Akteur benötigt in der gewählten Region die Berechtigung
textract:AnalyzeExpense. Dieses Beispiel lädt Bytes hoch und verwendet keinen S3-Bucket,
kein SNS-Topic und keine SQS-Warteschlange. Jede Serviceanfrage kann Kosten verursachen.
Fügen Sie die folgenden Befehle im Verzeichnis ein, in dem Sie ein neues Projekt namens
invoice-ocr erstellen möchten. Die Subshell erhält Ihr aktuelles Verzeichnis
und Ihre Shell-Optionen. Ist das Ziel bereits vorhanden, stoppt die Einrichtung vor der Installation.
Die Paketversionen legen das getestete SDK und den direkt verwendeten Guzzle-Transport fest.
Das lokale Manifest und die entfernten Composer-Pfadüberschreibungen halten die Installation
innerhalb dieses Projekts.
(
for tool in php composer; do
command -v "$tool" >/dev/null || { printf 'Missing prerequisite: %s\n' "$tool" >&2; exit 1; }
done &&
php -r 'foreach (["curl", "fileinfo", "SimpleXML", "gd"] as $extension) { if (!extension_loaded($extension)) { fwrite(STDERR, "Missing PHP extension: $extension\n"); exit(1); } } if (!(imagetypes() & IMG_PNG) || !(imagetypes() & IMG_JPG)) { fwrite(STDERR, "GD needs PNG and JPEG support\n"); exit(1); }' &&
unset COMPOSER COMPOSER_VENDOR_DIR COMPOSER_BIN_DIR &&
mkdir invoice-ocr &&
cd invoice-ocr &&
printf '{"require": {}, "config": {"vendor-dir": "vendor", "bin-dir": "vendor/bin"}}\n' > composer.json &&
composer require aws/aws-sdk-php:3.400.1 guzzlehttp/guzzle:8.2.0 &&
php -r 'require "vendor/autoload.php"; if (!class_exists(Aws\Textract\TextractClient::class) || !class_exists(GuzzleHttp\Handler\CurlHandler::class)) { exit(1); }'
)
Behalten Sie composer.json und composer.lock bei. Falls die
Installation nach dem Erstellen des Verzeichnisses scheitert, beheben Sie die gemeldete fehlende
Voraussetzung oder das Netzwerkproblem. Versuchen Sie die Installation dann in diesem Verzeichnis
erneut, ohne vorhandene Dateien zu entfernen:
(
unset COMPOSER COMPOSER_VENDOR_DIR COMPOSER_BIN_DIR &&
cd invoice-ocr &&
{ [ -f composer.json ] || printf '{"require": {}, "config": {"vendor-dir": "vendor", "bin-dir": "vendor/bin"}}\n' > composer.json; } &&
composer require aws/aws-sdk-php:3.400.1 guzzlehttp/guzzle:8.2.0 &&
php -r 'require "vendor/autoload.php"; if (!class_exists(Aws\Textract\TextractClient::class) || !class_exists(GuzzleHttp\Handler\CurlHandler::class)) { exit(1); }'
)
Die API AnalyzeExpense verstehen
AnalyzeExpense
liefert ExpenseDocuments mit SummaryFields und verschachtelten
LineItemGroups. Die Feldklassifikationen werden abgeleitet: Ein Anbietername kann
fehlen, auch wenn brauchbare Summen und Positionen erkannt werden.
Diese CLI akzeptiert genau ein nicht leeres lokales PNG- oder JPEG-Bild mit höchstens 5.000.000 Bytes, höchstens 5.000 Pixeln je Dimension und insgesamt höchstens 10.000.000 Pixeln. Dies sind Anwendungsgrenzen, die auch angehängte Dateibytes einschließen, keine Aussage über die maximale Nutzlast bei AWS. GD muss das Bild ohne gemeldete Warnung decodieren. Das Decodieren prüft die Lesbarkeit, beweist aber nicht, dass jedes Pixel eines beschädigten Bildes intakt ist. PDF, TIFF, URLs und die Verarbeitung mehrseitiger Dokumente liegen außerhalb des Funktionsumfangs dieser CLI.
Eine synchrone Rechnungs-CLI erstellen
Speichern Sie dieses vollständige Programm als invoice-ocr/invoice.php. Es sendet die
ursprünglichen Bildbytes und gibt JSON erst aus, wenn Analyse und Parsing erfolgreich waren.
Das SDK übernimmt die Serialisierung der Bytes;
codieren Sie die Eingabe nicht selbst als Base64.
<?php
use Aws\Handler\Guzzle\GuzzleHandler;
use Aws\Textract\TextractClient;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\HandlerStack;
if (PHP_SAPI !== 'cli') {
http_response_code(404);
exit;
}
if ($argc !== 2) {
fwrite(STDERR, "Usage: php invoice.php <local-invoice.png|local-invoice.jpg>\n");
exit(1);
}
function parseExpense(array $result): array
{
return array_map(function (array $document): array {
$field = static fn (array $value): array => [
'type' => $value['Type']['Text'] ?? null,
'text' => $value['ValueDetection']['Text'] ?? null,
'confidence' => $value['ValueDetection']['Confidence'] ?? null,
];
$groups = [];
foreach ($document['LineItemGroups'] ?? [] as $group) {
$rows = [];
foreach ($group['LineItems'] ?? [] as $row) {
$rows[] = array_map($field, $row['LineItemExpenseFields'] ?? []);
}
$groups[] = ['index' => $group['LineItemGroupIndex'] ?? null, 'rows' => $rows];
}
return [
'expense_index' => $document['ExpenseIndex'] ?? null,
'summary' => array_map($field, $document['SummaryFields'] ?? []),
'line_item_groups' => $groups,
'requires_review' => true,
];
}, $result['ExpenseDocuments'] ?? []);
}
try {
foreach (['curl', 'fileinfo', 'SimpleXML', 'gd'] as $extension) {
if (!extension_loaded($extension)) {
throw new RuntimeException('Required PHP extension unavailable');
}
}
set_error_handler(static function (int $severity, string $message): never {
throw new RuntimeException('Local image or dependency could not be read');
});
$path = $argv[1];
if (str_contains($path, '://') || !is_file($path) || !is_readable($path)) {
throw new RuntimeException('Use a readable local file');
}
$bytes = file_get_contents($path, false, null, 0, 5_000_001);
if ($bytes === false || $bytes === '' || strlen($bytes) > 5_000_000) {
throw new RuntimeException('Use 1 to 5,000,000 bytes');
}
$mime = (new finfo(FILEINFO_MIME_TYPE))->buffer($bytes);
if (!in_array($mime, ['image/png', 'image/jpeg'], true)) {
throw new RuntimeException('Use PNG or JPEG');
}
$size = getimagesizefromstring($bytes);
if ($size === false || $size[0] > 5_000 || $size[1] > 5_000
|| $size[0] * $size[1] > 10_000_000) {
throw new RuntimeException('Image dimensions exceed the application bounds');
}
$image = imagecreatefromstring($bytes);
if ($image === false) {
throw new RuntimeException('Image cannot be decoded');
}
unset($image);
require __DIR__ . '/vendor/autoload.php';
$textract = new TextractClient([
'region' => getenv('AWS_REGION') ?: 'us-east-1',
'version' => '2018-06-27',
'http_handler' => new GuzzleHandler(new Client([
'handler' => HandlerStack::create(new CurlHandler()),
])),
'http' => ['connect_timeout' => 5, 'timeout' => 30],
'retries' => 2,
]);
$result = $textract->analyzeExpense(['Document' => ['Bytes' => $bytes]]);
$documents = parseExpense($result->toArray());
if ($documents === []) {
throw new RuntimeException('No expense documents returned');
}
echo json_encode($documents, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR), "\n";
} catch (Throwable $error) {
fwrite(STDERR, "Invoice analysis failed. Check local file readability, PNG/JPEG decoding, byte and pixel bounds, PHP extensions, SDK installation, credentials, region, and AnalyzeExpense permission.\n");
exit(1);
}
Legen Sie Ihr Rechnungsbild im Projekt ab. Führen Sie dann die folgenden Befehle im Verzeichnis
mit invoice-ocr aus:
(cd invoice-ocr && php invoice.php ./invoice.png)
Übergeben Sie für JPEG stattdessen ./invoice.jpg. Pfade mit Leerzeichen müssen
in der Shell in Anführungszeichen stehen. Verwenden Sie ./ vor einem
Dateinamen, der mit einem Bindestrich beginnt. Das Programm schreibt nur auf stdout und stderr;
die Eingabe und vorhandene Ergebnisdateien bleiben unverändert. Leiten Sie stdout erst nach
Prüfung des Erfolgs in eine vorherige Ergebnisdatei um: Die Shell kann diese Datei auch dann
leeren, wenn die Analyse scheitert. Rechnungs-JSON kann sensible Daten enthalten und gehört daher
nicht in öffentliche Logs.
Rechnungsdaten extrahieren und strukturieren
Der Parser bildet jeden Kandidaten aus Zusammenfassungen und Rechnungspositionen auf
type, text und confidence ab.
Dabei bleiben die Reihenfolge sowie Dokument- und Gruppengrenzen erhalten. Arrays bewahren
wiederholte Typen, die eine Map mit TOTAL, NAME
oder OTHER als Schlüssel überschreiben würde. Fehlende Eigenschaften
bleiben null; die Konfidenz ist der Messwert des Detektors für Werte,
keine Garantie für buchhalterische Richtigkeit.
Beispielausschnitt einer Antwort
Dieses Beispieldokument enthält zwei Kandidaten desselben Typs. Die tatsächlichen Kategorien, Werte und Messwerte variieren. Die Projektion lässt Geometrie, Beschriftungen, Währungsmetadaten und Verweise auf Quellseiten weg.
[
{
"expense_index": 1,
"summary": [
{ "type": "TOTAL", "text": "125.00", "confidence": 97.2 },
{ "type": "TOTAL", "text": "125.00 USD", "confidence": 88.5 }
],
"line_item_groups": [],
"requires_review": true
}
]
Bevor Sie Kandidaten in die Kreditorenbuchhaltung importieren, prüfen Sie das Quellbild, interpretieren Sie Dezimal- und Währungskonventionen, gleichen Sie Summen mit Rechnungspositionen ab und dokumentieren Sie die Prüfentscheidung. Maskieren Sie OCR-Text in einem Dashboard. Wenn Sie für die Prüfung Koordinaten oder Seitenverweise benötigen, bewahren Sie gemäß Ihrer Datenrichtlinie die vollständige SDK-Antwort auf, statt sich auf diese kleine Projektion zu verlassen. Informationen zur Vorverarbeitung und Dokumentenkonvertierung finden Sie bei unserem Service für Dokumentenverarbeitung.
Fehlerbehandlung und Wiederholungsstrategien
Der cURL-Handler erzwingt ein Verbindungs-Timeout von fünf Sekunden und ein Timeout von 30 Sekunden pro Versuch. Die SDK-Einstellung für Wiederholungsversuche erlaubt zwei Wiederholungsversuche nach dem ersten Versuch. Backoff, das Abrufen der Zugangsdaten und die lokale Verarbeitung benötigen zusätzliche Zeit. Diese Einstellungen setzen daher keine Zeitgrenze für den gesamten Befehl. Eine langsame Verbindung kann selbst bei einem kleinen Bild scheitern. Prüfen Sie die Verbindung und wiederholen Sie den Aufruf gezielt; wiederholte Aufrufe können Geld kosten.
Behalten Sie HandlerStack::create bei: Die
Standard-Middleware von Guzzle
wandelt HTTP-Fehler in abgelehnte Promises um, die das SDK interpretieren kann. Wenn Sie diesen
Stack entfernen, ändert sich die Behandlung von Servicefehlern. Bei einer abgelehnten Anfrage
endet das Programm mit einem Exit-Code ungleich null, leerem stdout und einer bereinigten
Diagnosemeldung. Eine leere Datei, die durch Shell-Umleitung entsteht, ist kein erfolgreiches
Rechnungsergebnis.
Kurzübersicht zur Fehlerbehebung
| Symptom | Nächste Prüfung |
|---|---|
| Einrichtung vor Projekterstellung gestoppt | Alle erforderlichen Programme und PHP-Erweiterungen prüfen |
| Composer nach Projekterstellung gescheitert | Gemeldeten Fehler beheben, dann die Installation wie oben beschrieben erneut versuchen |
| Lokales Bild abgelehnt | Nicht leere Bytes, tatsächlichen PNG-/JPEG-Inhalt, Decodieren und beide Pixelgrenzen prüfen |
| Analyse nach lokalen Prüfungen gescheitert | Zugangsdaten, Region, Netzwerkzugriff und Berechtigung textract:AnalyzeExpense prüfen |
| Ein erwartetes Feld fehlt | Quelle und andere Kandidaten prüfen; Kategorien und Erkennungsquote sind nicht garantiert |
| Werte wirken plausibel | requires_review beibehalten; Konfidenz beweist nicht die Richtigkeit von Summen, Währung oder Zahlung |
Asynchrone Verarbeitung mit SNS + SQS implementieren
Für mehrseitige Rechnungen oder einen Job, der über die Laufzeit seines Aufrufers hinaus bestehen
soll, wählen Sie den
asynchronen Workflow zur Ausgabenanalyse.
StartExpenseAnalysis liest aus S3; GetExpenseAnalysis ruft abgeschlossene
Ergebnisse mit Paginierung ab. Die
Anleitung von AWS zur asynchronen Verarbeitung
beschreibt SNS-Benachrichtigungen zum Abschluss und einen SQS-Consumer. Das ist eine separate
Anwendung: Stellen Sie ihre Ressourcen und Berechtigungen bereit, speichern Sie die Zuordnung
der Jobs zu ihren Eigentümern sowie den Idempotenzzustand dauerhaft und bestätigen Sie Nachrichten
erst nach dauerhafter Verarbeitung. Die synchrone CLI oben implementiert diesen Workflow nicht.
