AI Engineering Academy · レッスン

ドキュメントの読み込みとテキスト抽出

Pythonライブラリを使ってPDF、Wordドキュメント、プレーンテキストファイルを読み込み、抽出したテキストを整えて、チャンク化とembeddingに備えます。

レッスン 1/413 ステップ

「ドキュメントの読み込みとテキスト抽出」はCoddyKit上の無料AI Engineering Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAI Engineering Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AI Engineering Academyコースには全4レッスンが含まれています。

文書の読み込みが単純ではない理由

あらゆるRAGパイプラインの最初の段階は、文書から生のテキストを取り出すことです。簡単そうに聞こえますが、実際には驚くほど難しい作業です。PDFファイルでは、テキストが文字として埋め込まれている場合も、画像として埋め込まれている場合も、両方が混在している場合もあります。Word文書には、除去しなければならない書式設定のマークアップが含まれています。HTMLページには、実際のコンテンツとともにナビゲーションメニューや広告も含まれます。堅牢なローダーはこれらすべてのケースに対応し、チャンク分割に適した、整然とした一貫性のあるテキストを生成する必要があります。

pypdfでPDFを読み込む

pypdfは、テキストが埋め込まれたPDFファイルを読み取るための標準的なPythonライブラリです。ページ単位でテキストを抽出し、役立つメタデータである元のページ境界を保持します。ただし、pypdfはスキャンされたPDF(テキストの画像)を読み取れません。これにはOCRが必要です。RAGの引用で正確なページを示せるよう、テキストと一緒に必ずページ番号も抽出してください。

from pypdf import PdfReader

def load_pdf(file_path):
    reader = PdfReader(file_path)
    pages = []
    for page_num, page in enumerate(reader.pages, start=1):
        text = page.extract_text()
        if text and text.strip():  # skip blank pages
            pages.append({
                'text': text,
                'metadata': {
                    'source': file_path,
                    'page': page_num,
                    'doc_type': 'pdf'
                }
            })
    return pages

docs = load_pdf('annual_report.pdf')
print(f'Loaded {len(docs)} non-empty pages')

python-docxでWord文書を読み込む

Microsoft Wordファイル(.docx)は、コンテンツをXML形式で保存します。python-docxライブラリはこのXMLを解析し、段落、表、見出しをPythonオブジェクトとして扱えるようにします。段落のテキストを順番に抽出し、文書構造を保持するために見出しレベルも取得してください。セクション見出しを、それが導入するテキストとともに含めると、チャンク分割と検索の品質を高める貴重なコンテキストになります。

from docx import Document

def load_docx(file_path):
    doc = Document(file_path)
    sections = []
    current_section = {'heading': '', 'text': ''}

    for para in doc.paragraphs:
        if para.style.name.startswith('Heading'):
            if current_section['text'].strip():
                sections.append(current_section.copy())
            current_section = {'heading': para.text, 'text': ''}
        else:
            current_section['text'] += para.text + '\n'

    if current_section['text'].strip():
        sections.append(current_section)

    return [{'text': f"{s['heading']}\n{s['text']}",
             'metadata': {'source': file_path, 'section': s['heading']}}
            for s in sections]

WebページとHTMLの読み込み

HTMLページには、ナビゲーションバー、フッター、Cookieバナー、広告ブロックなどのノイズが大量に含まれています。BeautifulSoupを使ってHTMLを解析し、メインコンテンツ領域だけを抽出してください。<article>、<main>、またはコンテンツ固有のCSSクラスなどの要素を対象にします。テキストを抽出する前にscript、style、navタグを削除し、JavaScriptコードやメニュー項目がチャンクに混入しないようにしてください。

import requests
from bs4 import BeautifulSoup

def load_url(url):
    response = requests.get(url, timeout=10)
    response.raise_for_status()

    soup = BeautifulSoup(response.text, 'html.parser')

    # Remove noise elements
    for tag in soup(['script', 'style', 'nav', 'footer', 'header', 'aside']):
        tag.decompose()

    # Try to find main content
    main = soup.find('article') or soup.find('main') or soup.find('body')
    text = main.get_text(separator='\n', strip=True)

    return [{'text': text, 'metadata': {'source': url, 'doc_type': 'html'}}]

OCRによるスキャンPDFの処理

スキャンしたPDFはページを画像として保存しており、テキストが埋め込まれていません。テキストを抽出するには、光学式文字認識(OCR)が必要です。pytesseractライブラリは、GoogleのTesseract OCRエンジンをラップしています。OCRはテキスト抽出よりも遅く、精度も低いため(良好なスキャンでも文字認識精度は80~95%)、利用できる場合は必ずデジタルPDFを優先してください。低信頼度の抽出結果を確認できるよう、OCRを使用したことをメタデータに記録してください。

import pytesseract
from pdf2image import convert_from_path

def load_scanned_pdf(file_path):
    # Convert PDF pages to PIL images
    pages_as_images = convert_from_path(file_path, dpi=300)
    results = []
    for page_num, img in enumerate(pages_as_images, start=1):
        text = pytesseract.image_to_string(img, lang='eng')
        results.append({
            'text': text,
            'metadata': {
                'source': file_path,
                'page': page_num,
                'ocr': True  # flag for quality review
            }
        })
    return results

あらゆる形式に対応するUnstructuredの利用

unstructuredライブラリは、PDF、Word、Excel、PowerPoint、HTML、メールなどを統一されたAPIで扱える、ドキュメント読み込みの万能ツールです。ヒューリスティックを使ってドキュメント要素(タイトル、本文、表、ヘッダーなど)を識別し、構造化オブジェクトとして返します。これは、コーパスにさまざまな種類のドキュメントが含まれており、形式ごとのパーサーを個別に作成せず、要素単位のメタデータを取得したい場合に特に役立ちます。

from unstructured.partition.auto import partition

def load_any_document(file_path):
    elements = partition(filename=file_path)
    docs = []
    for element in elements:
        docs.append({
            'text': str(element),
            'metadata': {
                'source': file_path,
                'element_type': type(element).__name__,
                'category': element.category
            }
        })
    return docs

# Works on .pdf, .docx, .pptx, .html, .eml, .xlsx
docs = load_any_document('presentation.pptx')

抽出後のテキストクリーニング

抽出したままのテキストがきれいな状態であることはほとんどありません。PDF抽出では、段組みレイアウトによるハイフン付きの改行、余分な空白、各ページで繰り返されるページヘッダー、文字化けした特殊文字が頻繁に発生します。クリーニングパイプラインを適用し、過剰な空白を削除し、改行をまたぐハイフン付きの単語を結合し、パターンマッチングで特定したページヘッダーとフッターを除去し、Unicode文字を正規化してください。テキストをきれいにすると、チャンクの一貫性が大幅に向上します。

import re

def clean_text(text):
    # Rejoin hyphenated line breaks (PDF column artifacts)
    text = re.sub(r'-(\n)([a-z])', r'\2', text)
    # Normalize whitespace
    text = re.sub(r' +', ' ', text)
    text = re.sub(r'\n{3,}', '\n\n', text)
    # Remove page numbers standing alone on a line
    text = re.sub(r'^\d+$', '', text, flags=re.MULTILINE)
    # Strip leading/trailing whitespace from each line
    lines = [line.strip() for line in text.split('\n')]
    return '\n'.join(lines).strip()

データベースとAPIからの読み込み

すべての知識がファイルに保存されているわけではありません。社内データベース、CRMシステム、チケット管理ツール、REST APIも情報源になります。構造化されたデータベースの行は、関連するテーブルを結合し、各フィールドの値を埋め込みモデルが理解できる自然言語の段落に連結して読み込んでください。APIの場合は、ページネーションされた結果を取得し、各レコードをキーと値のペアを持つテキストドキュメントとしてシリアライズしてください。

import psycopg2

def load_from_database(conn_string, query):
    conn = psycopg2.connect(conn_string)
    cursor = conn.cursor()
    cursor.execute(query)
    columns = [desc[0] for desc in cursor.description]
    docs = []
    for row in cursor.fetchall():
        # Convert row to natural language text
        parts = [f'{col}: {val}' for col, val in zip(columns, row) if val]
        text = '\n'.join(parts)
        docs.append({'text': text, 'metadata': {'source': 'database'}})
    conn.close()
    return docs

ドキュメントの出所情報を追跡する

読み込んだすべてのドキュメントには、パイプライン全体を通じて出所情報のメタデータを保持させる必要があります。具体的には、ソースURLまたはファイルパス、ドキュメントのタイトル、作成者、作成日、最終更新日などです。このメタデータは各チャンクとともにベクトルストアへ渡され、LLMの引用情報に表示されます。出所情報がなければ、回答の出典をユーザーに示せず、インデックス内の特定のドキュメントだけを更新することも、ソース属性で検索結果を絞り込むこともできません。

import os
from datetime import datetime

def load_pdf_with_provenance(file_path):
    from pypdf import PdfReader
    reader = PdfReader(file_path)
    stat = os.stat(file_path)
    base_metadata = {
        'source': file_path,
        'title': reader.metadata.get('/Title', os.path.basename(file_path)),
        'author': reader.metadata.get('/Author', 'Unknown'),
        'doc_type': 'pdf',
        'file_size_kb': stat.st_size // 1024,
        'loaded_at': datetime.utcnow().isoformat()
    }
    pages = []
    for i, page in enumerate(reader.pages, 1):
        text = page.extract_text()
        if text and text.strip():
            pages.append({'text': text, 'metadata': {**base_metadata, 'page': i}})
    return pages

大規模なドキュメントコレクションのバッチ読み込み

何千ものドキュメントをインデックス化する場合は、concurrent.futuresを使って並列に読み込み、I/OとCPUのリソースを最大限に活用してください。読み込みに失敗したドキュメントがインデックスから静かに抜け落ちないよう、進捗トラッカーとエラーログを実装します。1つの破損ファイルによって読み込み処理全体が停止しないようにしてください。ドキュメントごとに例外を捕捉し、次のドキュメントへ進みます。

from concurrent.futures import ThreadPoolExecutor, as_completed

def batch_load_documents(file_paths, max_workers=8):
    all_docs = []
    errors = []
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        future_to_path = {
            executor.submit(load_pdf_with_provenance, p): p
            for p in file_paths
        }
        for future in as_completed(future_to_path):
            path = future_to_path[future]
            try:
                docs = future.result()
                all_docs.extend(docs)
                print(f'Loaded {len(docs)} pages from {path}')
            except Exception as e:
                errors.append({'path': path, 'error': str(e)})
    return all_docs, errors

読み込んだコンテンツの品質検証

読み込み後は、意味のあるコンテンツを抽出できているか検証してください。確認項目には、テキストの最小長(50文字未満のチャンクをスキップ)、言語検出(RAGが英語専用の場合は英語以外のページを除外)、文字化けの検出(ASCII以外の文字の割合が高い場合は、OCRの失敗やエンコーディングの問題を示している可能性があります)があります。読み込んだページ数、ページの平均長、エラー率などの統計をログに記録し、読み込み時の問題を早期に発見してください。

def validate_documents(docs, min_length=100):
    valid = []
    skipped = 0
    for doc in docs:
        text = doc['text']
        if len(text) < min_length:
            skipped += 1
            continue
        # Check for high non-ASCII ratio (garbled OCR)
        non_ascii = sum(1 for c in text if ord(c) > 127)
        if non_ascii / max(len(text), 1) > 0.3:
            skipped += 1
            continue
        valid.append(doc)
    print(f'Valid: {len(valid)}, Skipped: {skipped}')
    return valid

理解度チェック

このレッスンで学んだAI Engineeringの概念について、理解度を確認しましょう。

レッスンのまとめ

このレッスンでは、PDFにはpypdf、Wordドキュメントにはpython-docx、HTMLにはBeautifulSoup、スキャンしたドキュメントにはOCRを使う形式別のローダー、複数形式に対応する統一ローダーとしてのunstructuredライブラリ、そしてテキストクリーニング、出所情報のメタデータ、バッチによる並列読み込み、コンテンツ検証などの本番環境でのベストプラクティスについて学びました。次は、取得したテキストをモデルのコンテキストにどのように対応付けるかを決めるチャンク分割戦略に取り組みます。

無料で開始

AI チューターと学ぶ Python — 無料

ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。

コース
30
レッスン
120

よくある質問

「ドキュメントの読み込みとテキスト抽出」レッスンは無料ですか?

はい。「ドキュメントの読み込みとテキスト抽出」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AI Engineering Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Engineering Academyコースには全4レッスンが含まれています。

「ドキュメントの読み込みとテキスト抽出」で何を学びますか?

Pythonライブラリを使ってPDF、Wordドキュメント、プレーンテキストファイルを読み込み、抽出したテキストを整えて、チャンク化とembeddingに備えます。 ブラウザで直接実行するハンズオンコードでAI Engineering Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

AI Engineering Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのAI Engineering Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。

「ドキュメントの読み込みとテキスト抽出」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このAI Engineering Academyレッスンでコードを書いて実行できますか?

はい。すべてのAI Engineering Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. ドキュメントの読み込みとテキスト抽出
  2. チャンク化戦略:固定長・文単位・再帰的分割
  3. インデックス作成:チャンクのEmbeddingと保存
  4. クエリ・検索・生成
← AI Engineering Academyに戻る