Extraction de tableaux et structure
Mis à jour le 29 juillet 2026
Les tableaux, point faible historique de l’OCR
Demandez à Tesseract d’extraire le tableau récapitulatif d’un rapport financier : vous obtiendrez une suite de lignes où les valeurs de la colonne « Total » se retrouvent mêlées à celles de la colonne « Quantité », sans qu’aucun indice ne permette de les rattacher à leur ligne d’origine. Le problème n’est pas la reconnaissance des caractères, qui fonctionne, mais l’absence totale de notion de structure tabulaire : les solutions traditionnelles lisent de gauche à droite et de haut en bas, et perdent au passage les relations entre colonnes et rangées.
OCR 4 aborde la page autrement, en la voyant. C’est cette compréhension visuelle qui lui permet de restituer un tableau comme un tableau, et non comme un texte aplati.
Le paramètre table_format
Le rendu des tableaux se pilote par un unique paramètre passé à l’appel OCR :
import os
from mistralai import Mistral
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
# Format Markdown (recommandé pour la plupart des cas)
ocr_response = client.ocr.process(
model="mistral-ocr-latest",
document={
"type": "document_url",
"document_url": "https://exemple.com/rapport-financier.pdf"
},
table_format="markdown"
)
Trois valeurs sont possibles, et le choix n’est pas cosmétique : il détermine ce que vous pourrez faire du résultat en aval.
Avec null, la valeur par défaut, les tableaux sont intégrés au flux Markdown sans formatage tabulaire spécifique. Ce réglage convient quand vous ne traitez pas de documents tabulaires et que vous cherchez avant tout du texte à indexer.
Avec "markdown", chaque tableau est rendu au format standard avec des pipes, ce qui donne une sortie lisible telle quelle et facile à parser :
| Produit | Quantité | Prix unitaire | Total |
|---------|----------|---------------|-------|
| Widget A | 100 | 12,50 € | 1 250 € |
| Widget B | 50 | 25,00 € | 1 250 € |
| **Total** | **150** | | **2 500 €** |
Avec "html", le tableau est rendu en HTML complet, ce qui préserve les fusions de cellules et la mise en forme avancée que le Markdown ne sait pas exprimer :
<table>
<thead>
<tr>
<th>Produit</th>
<th>Quantité</th>
<th>Prix unitaire</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr>
<td>Widget A</td>
<td>100</td>
<td>12,50 €</td>
<td>1 250 €</td>
</tr>
</tbody>
</table>
En pratique, le Markdown couvre la majorité des besoins : indexation RAG, recherche, affichage dans un éditeur. Basculez en HTML dès que vos tableaux comportent des cellules fusionnées ou que le résultat doit être intégré à une page web. Et gardez null uniquement lorsque les tableaux n’ont aucune importance dans votre workflow — un rapport narratif, une correspondance.
Récupérer les tableaux sous forme de données
Un tableau Markdown reste du texte. Pour le transformer en données manipulables, il faut le repérer dans le flux et le découper. La fonction ci-dessous parcourt les lignes, reconnaît celles qui appartiennent à un tableau, ignore la ligne de séparation et referme le tableau courant dès qu’une ligne ordinaire apparaît :
import os
import re
import json
from mistralai import Mistral
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
def extraire_tableaux_markdown(markdown: str) -> list[list[list[str]]]:
"""Extrait les tableaux Markdown et les convertit en listes."""
tableaux = []
lignes = markdown.split("\n")
tableau_courant = []
dans_tableau = False
for ligne in lignes:
if "|" in ligne and ligne.strip().startswith("|"):
# Ignorer la ligne de séparation (|---|---|)
if re.match(r"^\|[\s\-:|]+\|$", ligne.strip()):
continue
cellules = [
c.strip() for c in ligne.strip().strip("|").split("|")
]
tableau_courant.append(cellules)
dans_tableau = True
else:
if dans_tableau and tableau_courant:
tableaux.append(tableau_courant)
tableau_courant = []
dans_tableau = False
if tableau_courant:
tableaux.append(tableau_courant)
return tableaux
# Extraction OCR avec tableaux Markdown
ocr_response = client.ocr.process(
model="mistral-ocr-latest",
document={
"type": "document_url",
"document_url": "https://exemple.com/facture.pdf"
},
table_format="markdown"
)
# Parcours des pages et extraction des tableaux
for page in ocr_response.pages:
tableaux = extraire_tableaux_markdown(page.markdown)
for i, tableau in enumerate(tableaux):
print(f"Page {page.index + 1}, Tableau {i + 1}:")
print(json.dumps(tableau, indent=2, ensure_ascii=False))
La dernière vérification, hors de la boucle, n’est pas décorative : sans elle, un tableau situé en fin de page — cas fréquent d’un récapitulatif de facture — ne serait jamais ajouté à la liste, puisqu’aucune ligne ordinaire ne vient le clore.
Vers pandas pour l’analyse
Une liste de listes suffit pour un export, pas pour calculer. Le passage au DataFrame se fait en promouvant la première ligne au rang d’en-têtes :
import pandas as pd
def tableaux_vers_dataframes(markdown: str) -> list[pd.DataFrame]:
"""Convertit les tableaux Markdown en DataFrames pandas."""
tableaux = extraire_tableaux_markdown(markdown)
dataframes = []
for tableau in tableaux:
if len(tableau) < 2:
continue
en_tetes = tableau[0]
donnees = tableau[1:]
df = pd.DataFrame(donnees, columns=en_tetes)
dataframes.append(df)
return dataframes
# Utilisation
for page in ocr_response.pages:
dfs = tableaux_vers_dataframes(page.markdown)
for df in dfs:
print(df.to_string())
print()
Le test len(tableau) < 2 écarte les faux positifs : une ligne isolée contenant un pipe, dans un bloc de code ou une adresse, ne produira pas un DataFrame vide et bancal. Gardez en tête que les valeurs restent des chaînes de caractères ; convertir « 1 250 € » en nombre reste à votre charge.
Quand les tableaux se compliquent
Les cellules fusionnées, les en-têtes sur deux niveaux ou les mises en page irrégulières font sortir le Markdown de son domaine d’expression. Passez alors en HTML et laissez BeautifulSoup et pandas faire le travail d’analyse :
from bs4 import BeautifulSoup
import pandas as pd
ocr_response = client.ocr.process(
model="mistral-ocr-latest",
document={
"type": "document_url",
"document_url": "https://exemple.com/rapport-complexe.pdf"
},
table_format="html"
)
for page in ocr_response.pages:
soup = BeautifulSoup(page.markdown, "html.parser")
tables = soup.find_all("table")
for table in tables:
df = pd.read_html(str(table))[0]
print(df)
Cette approche vous dispense d’écrire un parseur : pd.read_html gère les rowspan et colspan que votre découpage manuel aurait laissés de côté. Le prix à payer est une dépendance supplémentaire et une sortie un peu plus verbeuse — arbitrage qui vaut largement la peine sur des rapports financiers ou des tableaux réglementaires.
Points clés à retenir
- Le paramètre
table_formatcontrôle le rendu des tableaux (null, markdown, html) - Le format Markdown convient à la majorité des cas d’usage
- Le format HTML préserve les cellules fusionnées et la mise en forme avancée
- Les tableaux extraits peuvent être convertis en DataFrames pandas pour l’analyse
- OCR 4 reconstruit fidèlement la structure tabulaire, même sur des mises en page complexes