저는 2021년에 Adobe 개발자 블로그( "자연어 처리, Adobe PDF 추출 및 심층 PDF 인텔리전스" )용 API 데모를 구축하면서 Diffbot을 처음 발견했습니다.
당시 저는 Diffbot의 API가 얼마나 쉽고 응답 속도가 빠른지에 깊은 인상을 받았습니다. 한동안 API를 보지 않았지만 며칠 전 텍스트 요약에 대한 새로운 지원을 발표했습니다. 저는 이것이 Adobe PDF Extract API 와 결합하면 아주 좋을 것이라고 생각했습니다. 내가 찾은 것은 다음과 같습니다.
먼저, 직접 시도해 보려면 다음이 필요합니다.
좋습니다. 요약 흐름이 어떻게 작동하는지 살펴보겠습니다.
Extract API (죄송합니다. "Adobe PDF Extract API"입니다. 잠깐만요. 이것은 제 블로그이므로 내용을 단축할 수 있습니다!)는 매우 강력합니다. AI를 사용하여 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는 JSON 결과 세트와 선택적으로 테이블 및 이미지가 포함된 zip 파일을 반환합니다. REST API의 한 가지 좋은 점은 JSON에 직접 접근하여 저장할 수 있다는 것입니다.
JSON 결과는 상당히 클 수 있습니다. 3페이지로 구성된 내 소스 PDF(엄청나게 지루한 Adobe 보안 문서)의 경우 결과 JSON의 길이는 4560줄입니다. 내 소스 PDF는 여기에서 찾을 수 있으며, Extract의 원시 출력은 여기에서 찾을 수 있습니다.
여기에 4,500개의 줄을 모두 넣는 대신 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
속성이 있을 때 이를 사용해야 합니다. 실제로 작동하는 모습을 살펴보겠습니다.
앞서 Diffbot API가 사용하기 매우 간단하다고 언급했습니다. 그것을 보여드리겠습니다.
먼저 몇 가지 변수를 설정하고 첫 번째 단계에서 얻은 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}.`);
다음으로 Extract 결과에서 텍스트를 구문 분석해야 합니다.
let text = data.elements.reduce((text, el) => { if(el.Text) text += el.Text + '\n'; return text; },'');
다음으로 Diffbot에 대한 HTTP 요청을 작성합니다. 자세한 내용은 해당 문서를 확인하세요.
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.
3페이지짜리 PDF가 이제 하나의 간단한 단락이 되었습니다. 수백만 개의 문서를 보유한 조직에 이것이 얼마나 유용할지 상상할 수 있습니다. 이를 다른 서비스(앞서 언급한 엔터티 기능 등)와 결합하면 대규모 라이브러리 작업이 훨씬 쉬워집니다.
이 내용을 직접 확인하려면 https://github.com/cfjedimaster/document-services-demos/tree/main/random_demos/extract_diffbot_summary 에서 모든 코드를 다운로드하세요. 제가 말했듯이 여기에 있는 모든 것은 무료로 테스트할 수 있으므로 한번 시도해 보시고 아래 댓글을 통해 여러분의 생각을 알려주세요.