書籍データの構造化抽出:HTMLからExcelへの変換実践

電子書籍からデータを抽出し、構造化してデータベースに格納するまでのプロセスについて解説します。全体的な流れはEPUB形式の書籍をHTMLに変換し、Excelファイルを経由してデータベースに保存するというものです。

抽出プロセスの全体像

  1. EPUB形式の書籍をHTMLファイルに変換する
  2. HTMLの構造と特徴的なタグや記号(見出しタグ、括弧類など)を分析する
  3. Pythonでパース処理を行い、必要なデータを抽出してExcel形式で出力する

HTML構造の分析

対象となるHTMLファイルを確認すると、処方箋情報が特定の見出しタグ(<h2 class="sect2">)で構造化されていることがわかります。各処方箋は医師名と処方名からなるタイトルを持ち、「組成」「功效」「主治」「用法」「経験」といった項目が括弧書きのラベルで区切られています。

抽出対象の構造例:

<h2>王綿之:加味香蘇散</h2>

【組成】 紫蘇葉5g、陳皮・香附各4g...(成分情報)

【功效】 発汗解表。(効果)

【主治】 四時感冒之風寒表証...(適応症)

【用法】 水煎服、毎日1剤。(使用方法)

【経験】 本方王老多用于治療...(臨床経験)

Pythonによる実装

BeautifulSoupを使用してHTMLを解析し、pandasでデータを整理してExcelに出力します。以下は実装コードの例です。

import pandas as pd
from bs4 import BeautifulSoup
import re

# 定数定義
INPUT_FILE = 'medical_book_sample.html'
OUTPUT_FILE = 'prescriptions_extracted.xlsx'
TARGET_CLASS = 'sect2'

# 抽出対象のラベル定義
LABELS = {
    '【組成】': 'ingredients',
    '【功效】': 'effects', 
    '【主治】': 'indications',
    '【用法】': 'usage',
    '【経験】': 'clinical_notes'
}

def load_html_content(filepath):
    """HTMLファイルを読み込んでBeautifulSoupオブジェクトを返す"""
    with open(filepath, 'r', encoding='utf-8') as file:
        return BeautifulSoup(file.read(), 'html.parser')

def extract_prescription_title(element):
    """h2要素から処方名を抽出(医師名:処方名の形式)"""
    title_text = element.get_text(strip=True)
    return title_text if ':' in title_text else None

def parse_content_block(paragraph):
    """段落からラベルと内容を分離して返す"""
    text = paragraph.get_text(strip=True)
    
    for label_jp, label_en in LABELS.items():
        if label_jp in text:
            content = text.replace(label_jp, '').strip()
            return label_en, content
    
    return None, None

def extract_prescriptions(soup):
    """HTMLから全ての処方情報を抽出"""
    sections = soup.find_all('h2', class_=TARGET_CLASS)
    extracted_data = []
    
    for section in sections:
        # 処方名の取得
        title = extract_prescription_title(section)
        if not title:
            continue
            
        # 処方データの初期化
        prescription = {
            '処方名': title,
            'ingredients': '',
            'effects': '',
            'indications': '',
            'usage': '',
            'clinical_notes': ''
        }
        
        # 次のh2タグまでの段落を処理
        next_element = section.find_next_sibling()
        
        while next_element:
            # 次のセクションに到達したら終了
            if next_element.name == 'h2' and TARGET_CLASS in next_element.get('class', []):
                break
                
            if next_element.name == 'p':
                field_name, content = parse_content_block(next_element)
                if field_name and content:
                    prescription[field_name] = content
                    
            next_element = next_element.find_next_sibling()
        
        extracted_data.append(prescription)
    
    return extracted_data

def save_to_excel(data, output_path):
    """データをExcelファイルに保存"""
    df = pd.DataFrame(data)
    df.to_excel(output_path, index=False, engine='openpyxl')
    return len(data)

# メイン処理
def main():
    soup = load_html_content(INPUT_FILE)
    prescriptions = extract_prescriptions(soup)
    count = save_to_excel(prescriptions, OUTPUT_FILE)
    print(f'抽出完了: {count}件の処方データを{OUTPUT_FILE}に保存しました')

if __name__ == '__main__':
    main()

拡張版:より堅牢な解析

HTML内にspanタグなどの入れ子構造がある場合、より詳細な解析が必要になります。以下は、spanタグ内の強調テキストを考慮した実装です。

import pandas as pd
from bs4 import BeautifulSoup

class PrescriptionExtractor:
    """処方情報抽出クラス"""
    
    FIELD_MAPPING = {
        '【組成】': 'ingredients',
        '【功效】': 'effects',
        '【主治】': 'indications', 
        '【用法】': 'usage',
        '【経験】': 'clinical_notes'
    }
    
    def __init__(self, html_path):
        self.soup = self._load_html(html_path)
        self.results = []
        
    def _load_html(self, path):
        with open(path, 'r', encoding='utf-8') as f:
            return BeautifulSoup(f.read(), 'html.parser')
    
    def _get_label_from_element(self, element):
        """spanタグからラベルを取得"""
        span = element.find('span', class_='emphasis_bold')
        if span:
            return span.get_text(strip=True)
        return None
    
    def _extract_text_content(self, element, label):
        """ラベルを除いたテキスト内容を取得"""
        full_text = element.get_text(strip=True)
        return full_text.replace(label, '').strip()
    
    def process_sections(self):
        """全セクションを処理"""
        sections = self.soup.find_all('h2', class_='sect2')
        
        for section in sections:
            title = section.get_text(strip=True)
            
            # 医師名:処方名の形式のみ処理
            if ':' not in title:
                continue
                
            record = self._create_record(title)
            self._process_paragraphs(section, record)
            self.results.append(record)
            
        return self.results
    
    def _create_record(self, title):
        """新規レコードの初期化"""
        return {
            '処方名': title,
            'ingredients': '',
            'effects': '',
            'indications': '',
            'usage': '',
            'clinical_notes': ''
        }
    
    def _process_paragraphs(self, section, record):
        """段落を処理してレコードに追加"""
        current = section.find_next_sibling()
        
        while current:
            if self._is_next_section(current):
                break
                
            if current.name == 'p':
                self._update_record(current, record)
                
            current = current.find_next_sibling()
    
    def _is_next_section(self, element):
        """次のセクションかどうかを判定"""
        return (element.name == 'h2' and 
                'sect2' in element.get('class', []))
    
    def _update_record(self, paragraph, record):
        """段落からデータを抽出してレコードを更新"""
        label = self._get_label_from_element(paragraph)
        
        if label and label in self.FIELD_MAPPING:
            field = self.FIELD_MAPPING[label]
            record[field] = self._extract_text_content(paragraph, label)
    
    def export_excel(self, output_path):
        """Excelファイルに出力"""
        df = pd.DataFrame(self.results)
        df.to_excel(output_path, index=False, engine='openpyxl')
        return output_path

# 実行例
if __name__ == '__main__':
    extractor = PrescriptionExtractor('medical_book_sample.html')
    data = extractor.process_sections()
    extractor.export_excel('prescriptions_output.xlsx')
    print(f'{len(data)}件の処方を抽出しました')

実行環境の準備

上記コードを実行するには、以下のライブラリが必要です。

pip install pandas openpyxl beautifulsoup4

出力結果

実行後、以下のカラムを持つExcelファイルが生成されます。

  • 処方名:医師名と処方名(例:王綿之:加味香蘇散)
  • ingredients(組成):構成成分と分量
  • effects(功效):薬効・作用
  • indications(主治):適応症・症状
  • usage(用法):服用方法
  • clinical_notes(経験):臨床経験・解説

この方法を応用することで、様々な構造の電子書籍から必要な情報を効率的に抽出し、データベース化することが可能です。

タグ: HTML解析 BeautifulSoup Pandas データ抽出 Python

5月20日 00:38 投稿