L’API Adobe PDF Extract est un outil puissant pour obtenir des informations à partir de vos PDF. Cela inclut la mise en page et le style de votre PDF, les données tabulaires au format CSV facile à utiliser, les images et le texte brut. Tout bien considéré, le texte brut peut être l’aspect le moins intéressant de l’API. Une possibilité utile est de prendre le texte brut et de le fournir aux moteurs de recherche (voir Utilisation de PDF avec Jamstack – Ajout de recherche avec extraction de texte). Mais une autre possibilité fascinante de travailler avec le texte implique le traitement du langage naturel ou la PNL.
De manière générale (très largement, voir l’article de Wikipédia pour un contexte plus approfondi), la PNL consiste à comprendre le contenu du texte. Les assistants vocaux en sont un excellent exemple concret. Ce qui rend les appareils Alexa et Google Voice si puissants, c’est qu’ils n’entendent pas seulement ce que vous dites, mais ils comprennent le intention de ce que tu as dit. Ceci est différent du texte brut.
Si je dis « Je travaille pour Adobe », c’est une déclaration différente que de dire « Je vis dans une maison en adobe ». Comprendre les différences entre le même mot nécessite l’apprentissage automatique, l’intelligence artificielle et d’autres mots que seules les personnes vraiment intelligentes comprennent.
Les organisations qui traitent les fichiers PDF entrants ou qui traitent un grand historique de documents existants peuvent utiliser une combinaison de l’API PDF Extract et de la PNL pour mieux connaître le contenu de leurs PDF.
Dans cet article, nous vous guiderons à travers un exemple qui montre que ces deux fonctionnalités puissantes fonctionnent ensemble. Notre application de démonstration numérisera un répertoire de PDF. Pour chaque PDF, il va d’abord extraire le texte du PDF. Ensuite, il amènera ce texte à une API NLP. Dans les deux opérations, nous pouvons enregistrer les résultats pour un traitement plus rapide ultérieurement.
Pour notre API NLP, nous utiliserons un service de Diffbot. Diffbot a plusieurs API, mais nous allons nous concentrer sur leur service en langage naturel. Leur API est assez simple à utiliser mais fournit une mine de données, notamment :
- Entités, ou fondamentalement, sujets d’un document.
- Le type de ces entités, par exemple, un nom est une entité et un type est une personne.
- Faits révélés dans le document, « La fondation d’une entreprise untel était Joe So and So. »
- Le sentiment (à quel point quelque chose est négatif ou positif).
L’API de Diffbot peut également être entraînée avec des données personnalisées afin de mieux analyser votre entrée. Consultez leurs documents pour plus d’informations et leur démarrage rapide est un excellent exemple. Ils offrent un essai de deux semaines et ne nécessitent pas de carte de crédit pour s’inscrire.
Voici un exemple simple d’appel de leur API à l’aide de Node.js :
let fields="entities,sentiment,facts,records,categories";
let token = 'your private token here';
let url = `https://nl.diffbot.com/v1/?fields=${fields}&token=${token}`;
// the text variable is what you want to parse
let body = [{
content:text,
lang:'en',
format:'plain text'
}];
let req = await fetch(url, {
method:'POST',
body:JSON.stringify(body),
headers: { 'Content-Type':'application/json' }
});
let result = await req.json();
Très bien, construisons donc notre démo. Nous avons déjà un dossier de PDF, donc mon processus général sera :
- Obtenez une liste de PDF.
- Pour chacun, voyez si j’ai déjà le texte. Étant donné un PDF nommé catsrule.pdf, je vais chercher catsrule.txt.
- Si je ne le fais pas, utilisez notre API d’extraction pour obtenir le texte.
- Pour chacun, voyez si j’ai déjà obtenu les résultats de la PNL. Étant donné un PDF nommé catsrule.pdf, je vais chercher catsrule.json.
Voici le code qui représente cette logique, moins les implémentations réelles de l’une ou l’autre API (j’ai également supprimé les instructions requises) :
const DIFFBOT_KEY = process.env.DIFFBOT_KEY;
const INPUT = './pdfs/';
(async () => {
let files = await glob(INPUT + '*.pdf');
console.log(`Going to process ${files.length} PDFs.n`);
for(file of files) {
console.log(`Checking ${file} to see if it has a txt or json file.`);
// ToDo: This would fail with foo.pdf.pdf. Don't do that.
let txtFile = file.replace('.pdf', '.txt');
let jsonFile = file.replace('.pdf', '.json');
let textExists = await exists(txtFile);
let text;
if(!textExists) {
console.log(`We do not have ${txtFile} and need to make it.`);
text = await getText(file);
await fs.writeFile(txtFile, text);
console.log('I have saved the extracted PDF text.');
} else {
console.log(`The text file ${txtFile} already exists.`);
text = await fs.readFile(txtFile,'utf8');
}
let jsonExists = await exists(jsonFile);
if(!jsonExists) {
console.log(`We do not have ${jsonFile} and need to make it.`);
let data = await getData(text);
await fs.writeFile(jsonFile, JSON.stringify(data));
console.log('I have saved the parsed data from the text.');
} else console.log(`The data file ${jsonFile} already exists.`);
}
})();
En enregistrant les résultats, ce script peut être exécuté plusieurs fois à mesure que de nouveaux fichiers PDF sont ajoutés. Maintenant, regardons nos appels d’API. D’abord, getText
:
async function getText(pdf) {
const credentials = PDFServicesSdk.Credentials
.serviceAccountCredentialsBuilder()
.fromFile('pdftools-api-credentials.json')
.build();
// Create an ExecutionContext using credentials
const executionContext = PDFServicesSdk.ExecutionContext.create(credentials);
// Build extractPDF options
const options = new PDFServicesSdk.ExtractPDF.options.ExtractPdfOptions.Builder()
.addElementsToExtract(
PDFServicesSdk.ExtractPDF.options.ExtractElementType.TEXT
)
.build()
// Create a new operation instance.
const extractPDFOperation = PDFServicesSdk.ExtractPDF.Operation.createNew(),
input = PDFServicesSdk.FileRef.createFromLocalFile(
pdf,
PDFServicesSdk.ExtractPDF.SupportedSourceFormat.pdf
);
extractPDFOperation.setInput(input);
extractPDFOperation.setOptions(options);
let outputZip = './' + nanoid() + '.zip';
let result = await extractPDFOperation.execute(executionContext);
await result.saveAsFile(outputZip);
let zip = new AdmZip(outputZip);
let data = JSON.parse(zip.readAsText('structuredData.json'));
let text = data.elements.filter(e => e.Text).reduce((result, e) => {
return result + e.Text + 'n';
},'');
await fs.unlink(outputZip);
return text;
}
Pour la plupart, cela est tiré directement de nos documents d’API d’extraction. Notre SDK renvoie un fichier ZIP, nous utilisons donc un package NPM (AdmZip) pour extraire le résultat JSON du ZIP. Nous filtrons ensuite les éléments de texte de notre résultat JSON pour créer une grande chaîne de texte honkin. Le résultat net est – étant donné un nom de fichier PDF en entrée, nous récupérons une chaîne de texte.
Maintenant, regardons le code pour exécuter le traitement du langage naturel sur le texte :
async function getData(text) {
let fields="entities,sentiment,facts,records,categories";
let token = DIFFBOT_KEY;
let url = `https://nl.diffbot.com/v1/?fields=${fields}&token=${token}`;
let body = [{
content:text,
lang:'en',
format:'plain text'
}]; let req = wait fetch(url, { method:'POST', body:JSON.stringify(body), headers: { 'Content-Type':'application/json' } }); return wait req.json(); }
Ceci est pratiquement identique à l’exemple précédent. Notez que vous pouvez modifier le fields
valeur pour changer ce que Diffbot fait avec votre texte. Le résultat est un de manière impressionnante grande quantité de données. Tout comme l’API PDF Extract, le JSON peut comporter des centaines de lignes. Si vous voulez voir le résultat brut, vous pouvez regarder un exemple ici. Attention : une fois formaté, cela représente environ soixante-deux mille lignes de données.
Et maintenant? Excellente question ! Comme démonstration rapide, j’ai pensé qu’il serait bien de filtrer les données de la PNL jusqu’à une liste de personnes mentionnées dans le PDF ainsi que des catégories. Pour les gens, j’ai regardé les entités retournées. Voici un exemple :
{
"name": "Sarah Clark",
"confidence": 0.99999547,
"salience": 0.976258,
"sentiment": 0,
"isCustom": false,
"allUris": [],
"allTypes": [
{
"name": "person",
"diffbotUri": "https://diffbot.com/entity/E4aFoJie0MN6dcs_yDRFwXQ",
"dbpediaUri": "http://dbpedia.org/ontology/Person"
}
],
"mentions": [
{
"text": "Sarah Clark",
"beginOffset": 425,
"endOffset": 436,
"confidence": 0.99984574
},
{
"text": "Sarah Clark",
"beginOffset": 38923,
"endOffset": 38934,
"confidence": 0.99999547
}
]
}
Notez la valeur dans allTypes
qui précise qu’il s’agit d’une personne. Voici un exemple d’entité non physique d’une petite entreprise à Washington :
{
"name": "Microsoft",
"confidence": 0.9998447,
"salience": 0,
"sentiment": 0,
"isCustom": false,
"allUris": [],
"allTypes": [
{
"name": "organization",
"diffbotUri": "https://diffbot.com/entity/EN1ClYEdMMQCxB6AWTkT3mA",
"dbpediaUri": "http://dbpedia.org/ontology/Organisation"
}
],
"mentions": [
{
"text": "Microsoft",
"beginOffset": 35999,
"endOffset": 36008,
"confidence": 0.9998447
},
{
"text": "Microsoft",
"beginOffset": 38848,
"endOffset": 38857,
"confidence": 0.9988986
}
]
}
Les catégories sont un peu plus simples car elles n’ont besoin d’aucun filtrage (en dehors de l’unicité) :
"categories": {
"iabv1": [
{
"id": "IAB19",
"name": "Technology & Computing",
"confidence": 0.99088717
}
],
"iabv2": [
{
"id": "596",
"name": "Technology & Computing",
"confidence": 0.99088717
}
]
}
Sachant où trouver des trucs, j’ai écrit un script qui a scanné mes répertoires PDF et pour chaque PDF, j’ai tenté de « rassembler » les données :
const INPUT = './pdfs/';
const OUTPUT = './pdfdata.json';
(async () => {
let result = [];
let files = await glob(INPUT + '*.pdf');
for(file of files) {
console.log(`Checking ${file} to see if it has a json file.`);
let jsonFile = file.replace('.pdf', '.json');
let jsonExists = await exists(jsonFile);
if(jsonExists) {
let json = JSON.parse(await fs.readFile(jsonFile, 'utf8'));
let people = gatherPeople(json);
let categories = gatherCategories(json);
result.push({
pdf: file,
people,
categories
});
} else console.log(`The data file ${jsonFile} didn't exist so we are skipping.`);
}
await fs.writeFile(OUTPUT, JSON.stringify(result));
console.log(`Done and written to ${OUTPUT}.`);
})();
async function exists(p) {
try {
await fs.stat(p);
return true;
} catch(e) {
return false;
}
}
function gatherPeople(data) {
let people = data[0].entities.filter(ent => {
return ent.allTypes.some(type => {
return type.name === 'person';
});
}).map(person => {
return person.name;
});
//https://stackoverflow.com/a/43046408/52160
return [...new Set(people)];
}
function gatherCategories(data) {
let categories = [];
if(data[0].categories['iabv1']) {
data[0].categories.iabv1.forEach(c => {
categories.push(c.name);
});
}
if(data[0].categories['iabv2']) {
data[0].categories.iabv2.forEach(c => {
categories.push(c.name);
});
}
//https://stackoverflow.com/a/43046408/52160
return [...new Set(categories)];
}
Comme précédemment, nous avons supprimé les déclarations requises. Le résultat final de ce script est un fichier JSON avec chaque PDF et une liste de personnes et de catégories. Pour les deux, nous filtrons sur des valeurs uniques. (Cela peut être problématique pour les noms bien sûr, mais je suppose que la probabilité que deux personnes portent le même nom dans un PDF soit faible).
Avec ces données, nous pouvons ensuite créer une application Web pour la charger et la rendre à l’écran :
Le code de l’application Web n’est pas très intéressant, mais si vous voulez le voir, ou tout autre exemple de cet article, vous pouvez le trouver ici.
Si vous voulez essayer cela vous-même, inscrivez-vous pour un essai gratuit de PDF Extract API et passez à la caisse Diffbot…