В современном цифровом рабочем процессе извлечение текста из PDF-документов остаётся важнейшей задачей для обработки данных, анализа контента и поиска информации. В то время как оптическое распознавание символов (OCR) обычно используется для PDF-файлов на основе изображений, PDF-файлы на основе текста, содержащие встроенные текстовые слои, требуют иного подхода. В этом руководстве подробно рассматривается извлечение текста из PDF-файлов на основе текста с помощью Google Apps Script с упором на собственные методы преобразования без использования OCR.
Технические основы извлечения текста в формате PDF
Понимание слоев текста PDF
В текстовых PDF-файлах символы хранятся как отдельные элементы в структуре файла, в отличие от PDF-файлов на основе изображений, для распознавания текста в которых требуется OCR. В таких документах используются потоки содержимого PDF, содержащие такие операторы, как TJ
(отображение текста с позиционированием) и Tj
(отображение текста), для отображения глифов из встроенных шрифтов. Эта структурная особенность позволяет напрямую извлекать текст без анализа пикселей.
Спецификация PDF (ISO 32000) определяет два основных режима отображения текста:
- Текстовый режим: Управляет выбором шрифта и глифа.
- Графический режим: обрабатывает операции векторной графики.
Извлечение текста направлено на перехват и интерпретацию этих операций в текстовом режиме.
Механизм преобразования Google Диска
При преобразовании PDF-файлов в Google Диске используются компоненты библиотеки Apache PDFBox для анализа потоков PDF-контента. При преобразовании с помощью API Диска:
- Глифы шрифта сопоставляются с эквивалентами в Юникоде
- Операторы позиционирования текста интерпретируются для восстановления макета
- Метаданные и структура документа сохранены в формате Google Docs
Этот процесс происходит на уровне API без необходимости в распознавании текста при работе с текстовыми PDF-файлами, обеспечивая точность распознавания символов более 99% для правильно закодированных документов.
Методология внедрения
Процесс извлечения
Стандартный конвейер извлечения текста состоит из четырех этапов:
- Получение документов
- Извлеките PDF-файл из URL-адреса или хранилища на диске
- Проверка типа MIME (
application/pdf
) - Проверка заголовков файловой структуры
- Преобразование привода
- Преобразование с помощью API в формат Google Docs
- Анализ потока контента и извлечение текстового слоя
- Создание временного документа на Диске пользователя
- Обработка текста
- Доступ к преобразованному тексту документа
- Извлечение необработанного текстового содержимого
- Обработка специальных символов и кодировки
- Очистка ресурсов
- Удаление временных документов
- Управление использованием квот API
Полная реализация кода
function extractTextFromPDF(pdfUrl) {
try {
// Fetch PDF with timeout handling
const blob = UrlFetchApp.fetch(pdfUrl, { muteHttpExceptions: true })
.getBlob()
.setContentTypeFromExtension();
// Verify PDF MIME type
if (blob.getContentType() !== 'application/pdf') {
throw new Error('Invalid MIME type - not a PDF file');
}
// Configure Drive API request
const resource = {
title: blob.getName().replace(/.pdf$/i, '_CONVERTED'),
mimeType: blob.getContentType(),
};
const options = {
convert: true,
supportsAllDrives: false,
ocr: false, // Explicitly disable OCR
};
// Execute conversion
const file = Drive.Files.insert(resource, blob, options);
// Access converted document
const doc = DocumentApp.openById(file.id);
const fullText = doc.getBody().getText();
// Cleanup temporary resources
DriveApp.getFileById(file.id).setTrashed(true);
return fullText;
} catch (error) {
console.error(`Extraction failed: ${error.message}`);
throw error;
}
}
Расширенные Рекомендации по внедрению
Оптимизация производительности
- Пакетная обработка
function batchProcessPDFs(folderId) {
const folder = DriveApp.getFolderById(folderId);
const files = folder.getFilesByType(MimeType.PDF);
while (files.hasNext()) {
const file = files.next();
try {
const text = extractTextFromPDF(file.getUrl());
// Implement text processing logic
} catch (e) {
console.error(`Error processing ${file.getName()}: ${e}`);
}
}
}
- Управление памятью
- Реализовать фрагментарную обработку для больших документов
- Используйте промежуточные текстовые буферы
- Использование частичных ответов Drive API
Шаблоны обработки ошибок
const ERROR_CODES = {
INVALID_URL: 1001,
MIME_TYPE_MISMATCH: 1002,
DRIVE_API_FAILURE: 2001,
DOCUMENT_ACCESS: 3001,
};
function handleError(error) {
switch (error.message) {
case 'Invalid PDF URL format':
return { code: ERROR_CODES.INVALID_URL, message: error.message };
case 'Invalid MIME type - not a PDF file':
return { code: ERROR_CODES.MIME_TYPE_MISMATCH, message: error.message };
default:
return { code: ERROR_CODES.DRIVE_API_FAILURE, message: error.message };
}
}
Проблемы и решения, связанные с конкретным форматом
Обработка сложных макетов
- Извлечение текста из нескольких столбцов
function parseColumns(text) {
const columnThreshold = 40; // Character count threshold
return text.split('\n').reduce((acc, line) => {
if (line.length < columnThreshold && acc.currentColumn) {
acc.columns.push(acc.currentColumn.join(' '));
acc.currentColumn = [];
}
acc.currentColumn.push(line.trim());
return acc;
}, { columns: [], currentColumn: [] });
}
- Реконструкция таблицы
Реализовать алгоритмы постобработки для обнаружения табличных структур с использованием:
- Шаблоны пробелов
- Регулярные выражения
- Статистический анализ распределения символов
Проблемы с кодировкой и шрифтом
- Реализовать обнаружение кодировки с помощью
TextDecoder
- Создание пользовательских карт шрифтов для PDF-файлов, отличных от Юникода
- Обрабатывайте ZapfDingbats и символьные шрифты с помощью таблиц подстановки
Использование API и управление квотами
Квоты Drive API
Operation | Quota Units | Daily Limit |
---|---|---|
Files.insert | 100 | 1,000 |
DocumentApp.openById | 50 | 50,000 |
UrlFetchApp.fetch | 20 | 100,000 |
Лучшие практики:
- Реализовать экспоненциальный откат для ошибок API
- Используйте пакетную обработку в нерабочее время
- Кэширование часто используемых документов
const API_DELAY = 1000; // Base delay in milliseconds
function withRetry(fn, retries = 3) {
return async function (...args) {
let attempt = 0;
while (attempt <= retries) {
try {
return await fn(...args);
} catch (error) {
if (error.message.includes('Quota')) {
Utilities.sleep(API_DELAY * Math.pow(2, attempt));
attempt++;
} else {
throw error;
}
}
}
};
}