Je veux extraire toutes les zones de texte et les coordonnées des zones de texte d'un fichier PDF avec PDFMiner.
De nombreux autres messages Stack Overflow expliquent comment extraire tout le texte de manière ordonnée, mais comment puis-je faire l'étape intermédiaire pour obtenir le texte et les emplacements de texte?
Étant donné un fichier PDF, la sortie devrait ressembler à:
489, 41, "Signature"
500, 52, "b"
630, 202, "a_g_i_r"
Les retours à la ligne sont convertis en traits de soulignement dans la sortie finale. C'est la solution de travail minimale que j'ai trouvée.
from pdfminer.pdfparser import PDFParser
from pdfminer.pdfdocument import PDFDocument
from pdfminer.pdfpage import PDFPage
from pdfminer.pdfpage import PDFTextExtractionNotAllowed
from pdfminer.pdfinterp import PDFResourceManager
from pdfminer.pdfinterp import PDFPageInterpreter
from pdfminer.pdfdevice import PDFDevice
from pdfminer.layout import LAParams
from pdfminer.converter import PDFPageAggregator
import pdfminer
# Open a PDF file.
fp = open('/Users/me/Downloads/test.pdf', 'rb')
# Create a PDF parser object associated with the file object.
parser = PDFParser(fp)
# Create a PDF document object that stores the document structure.
# Password for initialization as 2nd parameter
document = PDFDocument(parser)
# Check if the document allows text extraction. If not, abort.
if not document.is_extractable:
raise PDFTextExtractionNotAllowed
# Create a PDF resource manager object that stores shared resources.
rsrcmgr = PDFResourceManager()
# Create a PDF device object.
device = PDFDevice(rsrcmgr)
# BEGIN LAYOUT ANALYSIS
# Set parameters for analysis.
laparams = LAParams()
# Create a PDF page aggregator object.
device = PDFPageAggregator(rsrcmgr, laparams=laparams)
# Create a PDF interpreter object.
interpreter = PDFPageInterpreter(rsrcmgr, device)
def parse_obj(lt_objs):
# loop over the object list
for obj in lt_objs:
# if it's a textbox, print text and location
if isinstance(obj, pdfminer.layout.LTTextBoxHorizontal):
print "%6d, %6d, %s" % (obj.bbox[0], obj.bbox[1], obj.get_text().replace('\n', '_'))
# if it's a container, recurse
Elif isinstance(obj, pdfminer.layout.LTFigure):
parse_obj(obj._objs)
# loop over all pages in the document
for page in PDFPage.create_pages(document):
# read the page into a layout object
interpreter.process_page(page)
layout = device.get_result()
# extract text from this object
parse_obj(layout._objs)
Voici un exemple de copier-coller prêt qui répertorie les coins supérieurs gauche de chaque bloc de texte dans un PDF, et qui je pense devrait fonctionner pour tout PDF qui ne comprend pas " Form XObjects "qui contiennent du texte:
from pdfminer.layout import LAParams, LTTextBox
from pdfminer.pdfpage import PDFPage
from pdfminer.pdfinterp import PDFResourceManager
from pdfminer.pdfinterp import PDFPageInterpreter
from pdfminer.converter import PDFPageAggregator
fp = open('yourpdf.pdf', 'rb')
rsrcmgr = PDFResourceManager()
laparams = LAParams()
device = PDFPageAggregator(rsrcmgr, laparams=laparams)
interpreter = PDFPageInterpreter(rsrcmgr, device)
pages = PDFPage.get_pages(fp)
for page in pages:
print('Processing next page...')
interpreter.process_page(page)
layout = device.get_result()
for lobj in layout:
if isinstance(lobj, LTTextBox):
x, y, text = lobj.bbox[0], lobj.bbox[3], lobj.get_text()
print('At %r is text: %s' % ((x, y), text))
Le code ci-dessus est basé sur l'exemple Performing Layout Analysis dans les documents PDFMiner, plus les exemples de pnj ( https://stackoverflow.com/a/22898159/1709587 ) et Matt Swain ( https://stackoverflow.com/a/25262470/1709587 ). J'ai apporté quelques modifications à ces exemples précédents:
PDFPage.get_pages()
, qui est un raccourci pour créer un document, le vérifier is_extractable
Et le passer à PDFPage.create_pages()
LTFigure
s, car PDFMiner est actuellement incapable de gérer proprement le texte à l'intérieur.LAParams
vous permet de définir certains paramètres qui contrôlent la façon dont les caractères individuels dans le PDF sont regroupés comme par magie dans les lignes et les zones de texte par PDFMiner. Si vous êtes surpris qu'un tel regroupement soit une chose qui doit se produire du tout, c'est justifié dans les documents pdf2txt :
Dans un fichier PDF PDF réel, les portions de texte peuvent être divisées en plusieurs morceaux au milieu de son exécution, selon le logiciel de création. Par conséquent, l'extraction de texte doit épisser des morceaux de texte.
Les paramètres de LAParams
sont, comme la plupart de PDFMiner, non documentés, mais vous pouvez les voir dans le code source ou en appelant help(LAParams)
à votre Python Shell. La signification de certains des paramètres est donnée à https://pdfminer-docs.readthedocs.io/ pdfminer_index.html # pdf2txt-py car ils peuvent également être passés comme arguments à pdf2text
sur la ligne de commande.
L'objet layout
ci-dessus est un LTPage
, qui est un itérable des "objets de mise en page". Chacun de ces objets de mise en page peut être l'un des types suivants ...
LTTextBox
LTFigure
LTImage
LTLine
LTRect
... ou leurs sous-classes. (En particulier, vos zones de texte seront probablement toutes LTTextBoxHorizontal
s.)
Plus de détails sur la structure d'un LTPage
est montré par cette image à partir des documents:
Chacun des types ci-dessus a une propriété .bbox
Qui contient un ( x0 , y0 , x1 , y1 ) Tuple contenant les coordonnées de la gauche, du bas, de la droite et du haut de l'objet respectivement. Les coordonnées y sont données comme la distance par rapport au bas de la page. S'il est plus pratique pour vous de travailler avec l'axe des y allant de haut en bas, vous pouvez les soustraire de la hauteur du .mediabox
De la page:
x0, y0, x1, y1 = some_lobj.bbox
y0 = page.mediabox[3] - y1
y1 = page.mediabox[3] - y0
En plus d'une bbox
, LTTextBox
es ont également une méthode .get_text()
, illustrée ci-dessus, qui renvoie leur contenu texte sous forme de chaîne. Notez que chaque LTTextBox
est une collection de LTChar
s (caractères explicitement dessinés par le PDF, avec un bbox
) et LTAnno
s (espaces supplémentaires que PDFMiner ajoute à la représentation sous forme de chaîne du contenu de la zone de texte en fonction des caractères dessinés à une grande distance; ceux-ci n'ont pas de bbox
).
L'exemple de code au début de cette réponse a combiné ces deux propriétés pour afficher les coordonnées de chaque bloc de texte.
Enfin, il convient de noter que, contrairement à les autres réponses Stack Overflow citées ci-dessus, je ne me donne pas la peine de revenir dans LTFigure
s. Bien que LTFigure
s puisse contenir du texte, PDFMiner ne semble pas capable de regrouper ce texte dans LTTextBox
es (vous pouvez vous essayer sur l'exemple PDF de https://stackoverflow.com/a/27104504/1709587 ) et produit à la place un LTFigure
qui contient directement LTChar
objets. Vous pourriez, en principe, comprendre comment reconstituer ces ensemble dans une chaîne, mais PDFMiner (à partir de la version 20181108) ne peut pas le faire pour vous.
Heureusement, cependant, les fichiers PDF que vous devez analyser n'utilisent pas de formulaire XObjects contenant du texte, et cette mise en garde ne s'appliquera donc pas à vous.