L'analyse des PDF, y compris du texte, est facile avec Python ... J'ai eu un moment où je pensais de cette façon.

Préface

L'analyse des PDF contenant du texte est facile avec Python ...

image.png

Si des informations de caractère sont incluses, vous pouvez facilement créer un service Web en extrayant des informations de caractère et de table à partir du PDF et en utilisant ces données, le résultat d'une pensée facile appelée «Hyahoi» est le suivant. Je vais.

** J'ai essayé d'utiliser des données PDF de soins médicaux en ligne basés sur la propagation d'une nouvelle infection à coronavirus ** https://qiita.com/mima_ita/items/c0f28323f330c5f59ed8

La découverte la plus importante que j'ai obtenue ici est ** "Ne lisez pas les données PDF sur un ordinateur, c'est quelque chose que les humains lisent" ** </ font>, et quelques Comment gérer le PDF en utilisant Python.

Cette fois, je vais vous expliquer comment gérer le PDF en utilisant ce petit Python. L'environnement expérimental sera Window10 Python 3.7.5 64bit.

Analyse PDF

opérandes et opérateurs

Tous les caractères et graphiques PDF sont constitués d'opérateurs et d'opérateurs, dont les spécifications sont répertoriées ci-dessous. https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf

Il existe différentes bibliothèques utiles pour lire des PDF en Python, mais ici j'utiliserai PyPDF2 pour lire le PDF. La caractéristique de cette bibliothèque est qu'elle est entièrement écrite en Python, il est donc possible de vérifier à quoi ressemble le PDF au niveau de l'opérateur et de l'opérateur.

Vérifions de quel type d'opérateurs et d'opérateurs le simple PDF suivant consiste réellement. http://needtec.sakura.ne.jp/doc/hello.pdf

Le code ci-dessous énumère les opérateurs et les opérateurs.

import PyPDF2
from PyPDF2.pdf import ContentStream

with open("hello.pdf", "rb") as fp:
    pdf = PyPDF2.PdfFileReader(fp)
    for page_no in range(pdf.numPages):
        page = pdf.getPage(page_no)
        content = page['/Contents'].getObject()
        if not isinstance(content, ContentStream):
            content = ContentStream(content, pdf)
        for operands, operator in content.operations:
            print(operands, operator)

Le résultat de cette opération avec le simple PDF ci-dessus est le suivant.

[1, 0, 0, 1, 0, 0] b'cm'
[] b'BT'
['/F1', 12] b'Tf'
[14.4] b'TL'
[] b'ET'
[] b'n'
[10, 10, 200, 200] b're'
[] b'S'
[] b'BT'
[1, 0, 0, 1, 100, 50] b'Tm'
['Hello'] b'Tj'
[] b'T*'
[] b'ET'

Spécifications Les éléments suivants peuvent être analysés en lisant l'annexe A. Résumé de l'opérateur. Je comprends.

Operands Operator Description nombre de pages
x y width height re Coin inférieur gauche(x,y)Ajouter un chemin carré à partir de 133p
- S Tracez une ligne le long du chemin actuel 135p
a b c d e f Tm Spécifie la matrice qui détermine la position du texte.
image.png
249
string Tj Afficher les caractères 250

En d'autres termes, le dessin sera le suivant. (1) Dessinez un carré de largeur: 200 et de hauteur: 200 à partir de (10,10) avec le coin inférieur gauche comme origine. (2) Écrivez le caractère "Hello" de (100, 50)

Cette fois, c'était un exemple simple pour que je puisse le lire, mais dessiner le texte est très gênant, et si je ne comprends pas le comportement des opérateurs de positionnement de texte et des opérateurs d'affichage de texte, je vais extraire les caractères du PDF et leurs positions Et la taille ne peut pas être connue.

Par exemple, le PDF suivant est disponible. http://needtec.sakura.ne.jp/doc/hello2.pdf

Pour le regarder, il n'y a plus que quelques matrices japonaises et tables, mais il est difficile d'interpréter cela de la même manière.

De plus, PyPDF2 a une fonction pour extraire les pages appelée page.extractText (), mais ce sera beaucoup de difficulté pour les utilisateurs non américains. https://github.com/mstamy2/PyPDF2/issues

Analysez les caractères PDF avec PDFMiner

PDFMiner facilite l'extraction des caractères dans le PDF.

Ce qui suit est un exemple pour extraire les caractères du PDF.

from pdfminer.high_level import extract_text
print(extract_text('hello2.pdf'))

De plus, la vraie valeur de PDFMiner n'est pas seulement d'extraire des caractères, mais aussi d'obtenir les coordonnées et la taille des caractères à dessiner. Vous trouverez ci-dessous un exemple de programme qui extrait des caractères PDF spécifiques et leurs coordonnées.

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.converter import PDFPageAggregator
from pdfminer.layout import (
    LAParams,
    LTContainer,
    LTTextLine,
)

def get_objs(layout, results):
    if not isinstance(layout, LTContainer):
        return
    for obj in layout:
        if isinstance(obj, LTTextLine):
            results.append({'bbox': obj.bbox, 'text' : obj.get_text(), 'type' : type(obj)})
        get_objs(obj, results)

def main(path):
    with open(path, "rb") as f:
        parser = PDFParser(f)
        document = PDFDocument(parser)
        if not document.is_extractable:
            raise PDFTextExtractionNotAllowed
        # https://pdfminersix.readthedocs.io/en/latest/api/composable.html#
        laparams = LAParams(
            all_texts=True,
        )
        rsrcmgr = PDFResourceManager()
        device = PDFPageAggregator(rsrcmgr, laparams=laparams)
        interpreter = PDFPageInterpreter(rsrcmgr, device)
        for page in PDFPage.create_pages(document):
            interpreter.process_page(page)
            layout = device.get_result()
            results = []
            print('objs-------------------------')
            get_objs(layout, results)
            for r in results:
                print(r)


main('hello2.pdf')


Le résultat de l'exécution de ce programme en utilisant PDF avec japonais mixte est le suivant.

objs-------------------------
{'bbox': (90.744, 728.1928, 142.2056, 738.7528), 'text': 'Bonjour le monde\n', 'type': <class 'pdfminer.layout.LTTextLineHorizontal'>}
{'bbox': (168.5, 728.1928, 223.8356, 738.7528), 'text': 'Le chat a sonné\n', 'type': <class 'pdfminer.layout.LTTextLineHorizontal'>}
{'bbox': (168.5, 709.7128, 202.8356, 720.2728), 'text': 'je suis Dieu\n', 'type': <class 'pdfminer.layout.LTTextLineHorizontal'>}
{'bbox': (90.744, 691.1128, 146.0456, 701.6728), 'text': 'Dieu est mort\n', 'type': <class 'pdfminer.layout.LTTextLineHorizontal'>}
{'bbox': (168.5, 691.1128, 171.2456, 701.6728), 'text': ' \n', 'type': <class 'pdfminer.layout.LTTextLineHorizontal'>}
{'bbox': (168.5, 672.6328, 255.2756, 683.1928), 'text': 'Aw neo l sf\n', 'type': <class 'pdfminer.layout.LTTextLineHorizontal'>}
{'bbox': (90.744, 709.7128, 93.4896, 720.2728), 'text': ' \n', 'type': <class 'pdfminer.layout.LTTextLineHorizontal'>}
{'bbox': (90.744, 672.6328, 93.4896, 683.1928), 'text': ' \n', 'type': <class 'pdfminer.layout.LTTextLineHorizontal'>}
{'bbox': (85.104, 654.1528, 87.8496, 664.7128), 'text': ' \n', 'type': <class 'pdfminer.layout.LTTextLineHorizontal'>}

Vous pouvez voir que vous obtenez les coordonnées ainsi que le contenu des caractères dans le PDF.

Analyser le tableau en PDF

Il n'y a pas d'opérateurs et d'opérateurs qui représentent des tables dans PDF. Je représente simplement la table en utilisant le dessin carré et le dessin de texte décrits jusqu'à présent. Par conséquent, il n'est pas possible d'analyser les tableaux PDF aussi facilement que d'analyser les tableaux HTML et Excel.

Certaines bibliothèques Python essaient d'analyser le tableau PDF. Cette fois, nous utiliserons camelot, qui est entièrement implémenté en Python.

Voir ci-dessous pour une comparaison de camelot avec d'autres bibliothèques. https://github.com/atlanhq/camelot/wiki/Comparison-with-other-PDF-Table-Extraction-libraries-and-tools

Le résultat de ma comparaison avec tabula-py est le suivant. ** [Convertir le PDF du ministère de la Santé, du Travail et du Bien-être social en CSV ou JSON](https://needtec.sakura.ne.jp/wod07672/2020/04/29/%e5%8e%9a%e7%94%9f%e5 % 8a% b4% e5% 83% 8d% e7% 9c% 81% e3% 81% aepdf% e3% 82% 92csv% e3% 82% 84json% e3% 81% ab% e5% a4% 89% e6% 8f % 9b% e3% 81% 99% e3% 82% 8b /) **

Un simple échantillon de camelot

Extrayons le tableau du PDF utilisé précédemment en utilisant camelot.

import camelot
tables = camelot.read_pdf('hello2.pdf')

for ix in tables[0].df.index:
    print(ix, tables[0].df.loc[ix][0], '|', tables[0].df.loc[ix][1])

résultat

0 Bonjour tout le monde|Le chat a sonné
1  |je suis Dieu
2 Dieu est mort|
3  |Aw neo l sf

De plus, vous pouvez spécifier directement l'URL pour ouvrir le PDF ou l'exporter au format CSV ou JSON. Certains PDF pourront désormais extraire le tableau.

Si l'extraction de table ne fonctionne pas comme prévu

Dans la plupart des cas, les paramètres par défaut fonctionneront, mais lorsque vous analysez réellement le PDF, il peut se comporter de manière inattendue. Dans ce cas, nous vous recommandons de lire une fois le document suivant.

Advanced Usage https://camelot-py.readthedocs.io/en/master/user/advanced.html

[Ajustez les paramètres passés à read_pdf](https://camelot-py.readthedocs.io/en/master/api.html#main-interface] tout en vérifiant ce que cameralot reconnaît dans Visual Debbug. ) Peut fonctionner.

Ajustement des paramètres pour PDFMiner

Bien qu'il soit mentionné dans Tweak layout generation, camelot est en interne PDFMiner. en utilisant. Si le tableau ne peut pas être extrait du PDF par la méthode ci-dessus, il peut être possible de le résoudre en ajustant les paramètres transmis à PDFMiner.

Par exemple, si vous analysez le PDF suivant de la même manière que le code précédent, la deuxième ligne ne peut pas être bien extraite. http://needtec.sakura.ne.jp/doc/hello4.pdf

** Résultat de sortie **

0 1 |Ah ah
1  |2 bons
2 3 |Uuu

C'est le résultat de la reconnaissance que la distance entre «2» et «bon» est trop proche et qu'il s'agit de la même chaîne de caractères. Pour régler ceci:

tables = camelot.read_pdf(
    'hello4.pdf',
    layout_kwargs = {
        'char_margin': 0.25
    }
)

layout_kwargs est un objet de paramètre à passer à [pdfminer.layout] de PDFMiner (https://github.com/obeattie/pdfminer/wiki/pdfminer.layout). char_margin considère que deux blocs de texte plus proches que cette valeur sont contigus. La valeur par défaut est 0,5, ce qui est plus court et moins susceptible d'être considéré comme le même texte.

Le résultat de l'exécution avec ce paramètre est le suivant.

0 1 |Ah ah
1  |2 bons
2 3 |Uuu
--------------------------
0 1 |Ah ah
1 2 |Bien
2 3 |Uuu

Lors de l'inclusion d'une ligne pointillée

Lors du traitement d'un tableau contenant une ligne pointillée avec camelot, la ligne pointillée n'est pas reconnue.

Detect dotted line #370 https://github.com/atlanhq/camelot/issues/370

Par exemple, le PDF suivant en fait partie. ➀ Ligne pointillée verticale https://github.com/atlanhq/camelot/files/3565115/Test.pdf

② Ligne pointillée horizontale https://github.com/mima3/yakusyopdf/blob/master/20200502/%E5%85%B5%E5%BA%AB%E7%9C%8C.pdf

Cette solution peut être traitée par la méthode décrite dans l'article suivant.

・ ** [Traiter la ligne pointillée comme une ligne continue avec camelot](https://needtec.sakura.ne.jp/wod07672/2020/05/03/camelot%e3%81%a7%e7%82%b9%e7% b7% 9a% e3% 82% 92% e5% ae% 9f% e7% b7% 9a% e3% 81% a8% e3% 81% 97% e3% 81% a6% e5% 87% a6% e7% 90% 86% e3% 81% 99% e3% 82% 8b /) **

Pour faire simple, les données d'image traitées par camelot sont traitées de force et la ligne pointillée est remplacée par une ligne continue pour continuer le traitement.

Si tu ne peux rien faire

Il existe des cas où rien ne peut être fait pour créer un PDF. Par exemple, [Pour les données contenant des caractères longs qui s'étendent au-delà de la cellule](https://qiita.com/mima_ita/items/c0f28323f330c5f59ed8#pdf%E3%81%8B%E3%82%89%E3%83%86%E3% 83% BC% E3% 83% 96% E3% 83% AB% E3% 82% 92% E6% 8A% BD% E5% 87% BA% E3% 81% 99% E3% 82% 8B% E9% 9A% 9B% E3% 81% AE% E5% 95% 8F% E9% A1% 8C% E7% 82% B9) et ainsi de suite.

De plus, si vous oubliez de tracer une ligne réglée en premier lieu, cela ne fonctionnera pas correctement.

Mise à jour PDF

La méthode de lecture du PDF a été expliquée jusqu'à la section précédente. Ensuite, réfléchissons brièvement à la mise à jour du PDF.

Créer un nouveau PDF

Il est possible de sortir le contenu du texte et des figures du dessin au format PDF en utilisant reportlab.


from io import BytesIO
from reportlab.pdfgen import canvas

with open('hello.pdf', 'wb') as output_stream:
    buffer = BytesIO()
    c = canvas.Canvas(buffer, pagesize=(300, 300))
    c.rect(10, 10, 200, 200, fill=0)
    c.drawString(100, 50, 'Hello')
    c.showPage()
    c.save()
    buffer.seek(0)
    output_stream.write(buffer.getvalue())

Cette sortie sera le PDF utilisé précédemment. http://needtec.sakura.ne.jp/doc/hello.pdf

Réécrire une page PDF existante

J'ai recherché différentes façons de lire un PDF existant et de réécrire les informations graphiques et le texte sur la page, mais honnêtement, cela me semblait difficile. La méthode présentée ici est une méthode pour ajouter de nouvelles figures et du texte à la page PDF.

・ ** [Remplacez la ligne pointillée du PDF par une ligne continue (PyPDF2 + reportlab)](https://needtec.sakura.ne.jp/wod07672/2020/05/04/pdf%e3%81%ae%e7%82% b9% e7% b7% 9a% e3% 82% 92% e5% ae% 9f% e7% b7% 9a% e3% 81% ab% e3% 81% 8a% e3% 81% 8d% e3% 81% 8b% e3% 81% 88% e3% 82% 8b /) ** ・ ** [Remplacez la ligne pointillée du PDF par une ligne continue (PyMuPDF)](https://needtec.sakura.ne.jp/wod07672/2020/05/04/pdf%e3%81%ae%e7%82%b9% e7% b7% 9a% e3% 82% 92% e5% ae% 9f% e7% b7% 9a% e3% 81% ab% e3% 81% 8a% e3% 81% 8d% e3% 81% 8b% e3% 81% 88% e3% 82% 8bpymupdf /) **

Notez que PyPDF2 n'a pas de fonction de compression, vous devez donc utiliser une autre méthode pour mettre à jour le fichier. Dans mon environnement, 3MB PDF est devenu 440MB.

Résumé

Certains d'entre vous trouveront peut-être facile d'utiliser les données PDF dans l'explication jusqu'à présent. Si vous n'êtes pas autorisé à modifier le PDF d'entrée, considérez-le comme ** Thorn Road ** </ font>. Par exemple, dans Excel, vous pouvez distinguer les cellules même si vous oubliez de dessiner une bordure, mais en PDF, vous ne pouvez pas.

  • Vous pouvez dessiner des lignes pour toutes les cellules en utilisant la méthode utilisée pour remplacer les lignes pointillées dans le PDF par des lignes pleines, mais je ne sais pas si les bordures de ces cellules ont été ou non oubliées. .. ..

Je voudrais conclure cet article en rappelant les choses les plus importantes que j'ai mentionnées au début.

** "Ne lisez pas les données PDF sur un ordinateur, c'est quelque chose que les humains lisent" ** </ font>

Recommended Posts