Впервые я обнаружил Diffbot еще в 2021 году, когда создавал демо-версию их API для блога разработчиков Adobe ( «Обработка естественного языка, извлечение Adobe PDF и Deep PDF Intelligence» ).
В то время я был впечатлен тем, насколько простым был API Diffbot, а также как быстро он реагировал. Я давно не заглядывал в их API, но несколько дней назад они объявили о новой поддержке суммирования текста. Я подумал, что было бы здорово объединить это с API Adobe PDF Extract . Вот что я нашел.
Прежде всего, если вы хотите попробовать это самостоятельно, вам понадобится:
Хорошо, давайте посмотрим, как может работать сводный поток.
API извлечения (извините, «Adobe PDF Extract API», подождите, это мой блог, я могу сокращать!) довольно мощный инструмент. Он использует искусственный интеллект для интеллектуального анализа PDF-файла и правильного поиска каждой детали элемента в документе.
Итак, текст, шрифты, цвета, положение и т. д. Он также может находить изображения и табличные данные, что приводит к довольно эффективным вариантам использования. (Хороший пример этого можно найти в моем блоге , где я просматриваю несколько научных журналов для сбора и агрегирования астрономических данных и создания отчетов.)
Для этой демонстрации нам буквально нужен текст. Для этого я буду использовать REST API. «Поток» практически для всех аспектов доступных сервисов PDF:
Обратите внимание, что вы также можете использовать SDK, но я нашел наши REST API настолько простыми, что просто обращаюсь к конечным точкам напрямую. Вот сценарий, который я написал для выполнения процесса извлечения. По сути, это все, что я сказал выше, и указал на исходный PDF-файл в моей локальной файловой системе.
/* This demo is a two step process. This file, step one, handles extracting and storing the JSON from a PDF. */ import 'dotenv/config'; import fs from 'fs'; import { Readable } from 'stream'; import { finished } from 'stream/promises'; const SOURCE_PDF = '../../source_pdfs/boring_adobe_security_doc.pdf'; const REST_API = "https://pdf-services.adobe.io/"; const CLIENT_ID = process.env.CLIENT_ID; const CLIENT_SECRET = process.env.CLIENT_SECRET; async function delay(x) { return new Promise(resolve => { setTimeout(() => resolve(), x); }); } async function getAccessToken(id, secret) { const params = new URLSearchParams(); params.append('client_id', id); params.append('client_secret', secret); let resp = await fetch('https://pdf-services-ue1.adobe.io/token', { method: 'POST', headers: { 'Content-Type':'application/x-www-form-urlencoded' }, body:params }); let data = await resp.json(); return data.access_token; } async function getUploadData(mediaType, token, clientId) { let body = { 'mediaType': mediaType }; body = JSON.stringify(body); let req = await fetch(REST_API+'assets', { method:'post', headers: { 'X-API-Key':clientId, 'Authorization':`Bearer ${token}`, 'Content-Type':'application/json' }, body: body }); let data = await req.json(); return data; } async function uploadFile(url, filePath, mediaType) { let stream = fs.createReadStream(filePath); let stats = fs.statSync(filePath); let fileSizeInBytes = stats.size; let upload = await fetch(url, { method:'PUT', redirect:'follow', headers: { 'Content-Type':mediaType, 'Content-Length':fileSizeInBytes }, duplex:'half', body:stream }); if(upload.status === 200) return; else { throw('Bad result, handle later.'); } } async function pollJob(url, token, clientId) { let status = null; let asset; while(status !== 'done') { let req = await fetch(url, { method:'GET', headers: { 'X-API-Key':clientId, 'Authorization':`Bearer ${token}`, } }); let res = await req.json(); status = res.status; if(status === 'done') { asset = res; } else { await delay(2000); } } return asset; } async function downloadFile(url, filePath) { let res = await fetch(url); const body = Readable.fromWeb(res.body); const download_write_stream = fs.createWriteStream(filePath); return await finished(body.pipe(download_write_stream)); } async function extractJob(asset, token, clientId) { let body = { 'assetID': asset.assetID } let resp = await fetch(REST_API + 'operation/extractpdf', { method: 'POST', headers: { 'Authorization':`Bearer ${token}`, 'X-API-KEY':clientId, 'Content-Type':'application/json' }, body:JSON.stringify(body) }); return resp.headers.get('location'); } let accessToken = await getAccessToken(CLIENT_ID, CLIENT_SECRET); console.log('Got our access token.'); let uploadedAsset = await getUploadData('application/pdf', accessToken, CLIENT_ID); await uploadFile(uploadedAsset.uploadUri, SOURCE_PDF, 'application/pdf'); console.log('Source PDF Uploaded.'); let job = await extractJob(uploadedAsset, accessToken, CLIENT_ID); console.log('Job created. Now to poll it.'); let result = await pollJob(job, accessToken, CLIENT_ID); console.log('Job is done.'); await downloadFile(result.content.downloadUri, 'extract.json'); console.log('All done.');
Хорошо, надеюсь, вы все еще читаете. В общем, я стараюсь избегать публикации таких гигантских блоков кода, но если вы сосредоточитесь на строках в конце, вы увидите, что я просто нажимаю на служебные функции, которые делают то, что я описал выше. Пройдите аутентификацию, попросите загрузить PDF-файл, запустите задание, проверьте его и загрузите результат.
Одно замечание добавлю. Extract возвращает zip-файл, содержащий набор результатов JSON и, при необходимости, таблицы и изображения. Одна приятная особенность REST API заключается в том, что я могу напрямую получить доступ к JSON и просто сохранить его.
Результат JSON может быть весьма огромным. Для моего исходного PDF-файла (невероятно скучного документа безопасности Adobe) из трех страниц результирующий JSON имеет длину 4560 строк. Вы можете найти мой исходный PDF-файл здесь , а необработанный результат Extract здесь .
Вместо того, чтобы размещать здесь все 4,5 тыс. строк, позвольте мне показать фрагмент — два уникальных элемента, найденных API:
{ "Bounds": [ 44.62139892578125, 756.9429931640625, 245.0037841796875, 766.3184967041016 ], "Font": { "alt_family_name": "* Arial", "embedded": true, "encoding": "Identity-H", "family_name": "* Arial", "font_type": "CIDFontType0", "italic": false, "monospaced": false, "name": "*Arial-6539", "subset": false, "weight": 400 }, "HasClip": false, "Lang": "en", "Page": 0, "Path": "//Document/Sect/P", "Text": "Adobe Vendor Security Review Program White Paper ", "TextSize": 8.5, "attributes": { "SpaceAfter": 18 } }, { "Bounds": [ 0.0, 0.0, 630.0, 820.7799987792969 ], "ClipBounds": [ 548.72802734375, 739.1929931640625, 602.5444488525391, 820.7799987792969 ], "Page": 0, "Path": "//Document/Sect/Figure", "attributes": { "BBox": [ 548.9779999999737, 739.4429999999993, 587.61599999998, 790.920999999973 ], "Placement": "Block" } },
В приведенном выше примере вы можете видеть, что первый элемент является текстовым и содержит свойство Text
, а второй — фигуру. Для моей демонстрации мне просто нужно использовать свойство Text
, если оно существует. Давайте посмотрим это в действии.
Ранее я упоминал, что API Diffbot довольно прост в использовании. Позвольте мне это продемонстрировать.
Сначала я настрою несколько переменных и прочитаю JSON, полученный на первом этапе. Чтобы внести ясность: я мог бы сделать все в одном процессе, но на самом деле нет смысла запускать Extract более одного раза. Что круто, так это то, что я действительно могу сделать несколько вызовов результата.
Например, еще одна интересная функция Diffbot — получение объектов из текста, т. е. того, о чем говорит документ (люди, места и т. д.). В любом случае, вот начало:
/* In this file, we take the result from our Extract operation and pass it to Diffbot */ import 'dotenv/config'; import fs from 'fs'; const DIFFBOT_KEY = process.env.DIFFBOT_KEY; const SOURCE_JSON = './extract.json'; const data = JSON.parse(fs.readFileSync(SOURCE_JSON, 'utf8')); console.log(`Read in source data from ${SOURCE_JSON}.`);
Далее мне нужно проанализировать текст из результата извлечения:
let text = data.elements.reduce((text, el) => { if(el.Text) text += el.Text + '\n'; return text; },'');
Затем я создаю HTTP-запрос к Diffbot. Проверьте их документы для получения дополнительной информации.
let fields = 'summary'; let url = `https://nl.diffbot.com/v1/?fields=${fields}&token=${DIFFBOT_KEY}`; let body = [{ content:text, lang:'en', format:'plain text' }]; console.log('Passing text to Diffbot.'); let req = await fetch(url, { method:'POST', body:JSON.stringify(body), headers: { 'Content-Type':'application/json' } }); let result = await req.json();
Вот и все. В качестве последнего шага я просто вывожу его:
console.log(`Summary of PDF:\n${result[0].summary}`);
Учитывая исходный PDF-файл, окончательный результат:
Adobe has a vendor security review program that evaluates vendors that collect, store, process, transmit, or dispose of Adobe data outside of Adobe-controlled physical offices or data center locations. The VSR program includes requirements for vendors to follow when handling sensitive data and assigns a risk level score to vendors based on their compliance with Adobe standards. If a vendor fails the VSR program, Adobe holds discussions with the business owner to understand the details of the vendor's security practices and determine whether or not to continue working with them.
Мой трехстраничный PDF-файл теперь представляет собой один простой абзац. Вы можете себе представить, насколько это будет полезно для организаций с миллионами документов. Объедините это с другими сервисами (например, с функцией сущностей, о которой я упоминал ранее), и это значительно упростит работу с большими библиотеками.
Если вы хотите проверить это самостоятельно, вы можете получить весь код здесь: https://github.com/cfjedimaster/document-services-demos/tree/main/random_demos/extract_diffbot_summary . Как я уже сказал, все здесь можно протестировать бесплатно, так что попробуйте и дайте мне знать, что вы думаете, в комментарии ниже.