💨

校異源氏物語テキストDBに対する検索を行うAPIサーバの構築

に公開

概要

校異源氏物語テキストDBに対する検索を行うAPIサーバの構築したので、備忘録です。

https://genji-api.aws.ldas.jp/

背景

以下のページで、『校異源氏物語』のテキストデータをTEI/XMLに準拠した形で公開しています。

https://kouigenjimonogatari.github.io/

このテキストデータをElasticsearchに登録し、コマごとの検索を可能にするAPIを作成します。

使い方

以下のURLで、OpenAPIおよびSwaggerを用いた使い方の説明ページにアクセスできます。

https://genji-api.aws.ldas.jp/

工夫点

検索語の展開

例えば以下のURLは、「夕顔」を検索キーワードとした例です。JSON:APIに準拠した入出力形式としています。

https://genji-api.aws.ldas.jp/search?q=夕顔&page[limit]=20&page[offset]=0&sort=page&filter[expandRepeatMarks]=true&filter[unifyKanjiKana]=true&filter[unifyHistoricalKana]=true&filter[unifyPhoneticChanges]=true&filter[unifyDakuon]=true&filter[vol_str]=04 夕顔

この時、以下のような結果が返却されます。入力したキーワード「夕顔」に対して、バリエーションを生成し、これらに基づく検索を行います。

{
  "data": [],
  "meta": {
    "query": "夕顔",
    "transformedQueries": [
      "夕顔",
      "ゆうかお",
      "ゆふかお",
      "ゆふかほ",
      "ゆうかほ",
      "夕かお",
      "夕かほ",
      "ゆう顔",
      "ゆふ顔"
    ],
    "transformOptions": {
      "expandRepeatMarks": true,
      "unifyKanjiKana": true,
      "unifyHistoricalKana": true,
      "unifyPhoneticChanges": true,
      "unifyDakuon": true
    },
    "filters": {
      "expandRepeatMarks": true,
      "unifyKanjiKana": true,
      "unifyHistoricalKana": true,
      "unifyPhoneticChanges": true,
      "unifyDakuon": true,
      "vol_str": "04 夕顔"
    },
    "sort": "page",
    "limit": 20,
    "offset": 0,
    "total": 7,
    "aggregations": {
      "vol_str": {
        "doc_count_error_upper_bound": 0,
        "sum_other_doc_count": 0,
        "buckets": [
          {
            "key": "04 夕顔",
            "doc_count": 7
          }
        ]
      }
    }
  }
}

その結果、本文中に登場する「ゆふかほ」「夕かほ」「夕顔」を一度に検索することができます。

この検索キーワードの展開については、検索オプションをON/OFFを切り替えられるようにしています。詳細は、上述したSwagger UIでご確認ください。

Elasticsearchには、以下のようなOR検索を投げています。

{
  "query": {
    "bool": {
      "should": [
        {
          "wildcard": {
            "original_text_lines.keyword": "*夕顔*"
          }
        },
        {
          "wildcard": {
            "original_text_lines.keyword": "*ゆうかお*"
          }
        },
        {
          "wildcard": {
            "original_text_lines.keyword": "*ゆふかお*"
          }
        },
        {
          "wildcard": {
            "original_text_lines.keyword": "*ゆふかほ*"
          }
        },
        {
          "wildcard": {
            "original_text_lines.keyword": "*ゆうかほ*"
          }
        },
        {
          "wildcard": {
            "original_text_lines.keyword": "*夕かお*"
          }
        },
        {
          "wildcard": {
            "original_text_lines.keyword": "*夕かほ*"
          }
        },
        {
          "wildcard": {
            "original_text_lines.keyword": "*ゆう顔*"
          }
        },
        {
          "wildcard": {
            "original_text_lines.keyword": "*ゆふ顔*"
          }
        }
      ],
      "minimum_should_match": 1,
      "filter": {
        "terms": {
          "vol_str": [
            "04 夕顔"
          ]
        }
      }
    }
  },
  "size": 20,
  "from": 0,
  "sort": [
    {
      "page": {
        "order": "asc"
      }
    }
  ]
}

変換に使用しているルールは以下で確認することができます。

https://genji-api.aws.ldas.jp/normalization/rules

{
  "data": {
    "type": "normalization-rules",
    "attributes": {
      "rules": {
        "historicalKana": {
          "ゐ": "い",
          "ゑ": "え",
          "を": "お",
          "ワ": "ワ",
          "ヰ": "イ",
          "ヱ": "エ",
          "ヲ": "オ",
          "くゎ": "か",
          "ぐゎ": "が",
          "クヮ": "カ",
          "グヮ": "ガ"
        },
        "dakuon": {
          "が": "か",
          "ぎ": "き",
          "ぐ": "く",
          "げ": "け",
          "ご": "こ",
          "ざ": "さ",
          "じ": "し",
          "ず": "す",
          "ぜ": "せ",
          "ぞ": "そ",
          "だ": "た",
          "ぢ": "ち",
          "づ": "つ",
          "で": "て",
          "ど": "と",
          "ば": "は",
          "び": "ひ",
          "ぶ": "ふ",
          "べ": "へ",
          "ぼ": "ほ",
          "ぱ": "は",
          "ぴ": "ひ",
          "ぷ": "ふ",
          "ぺ": "へ",
          "ぽ": "ほ",
          "ガ": "カ",
          "ギ": "キ",
          "グ": "ク",
          "ゲ": "ケ",
          "ゴ": "コ",
          "ザ": "サ",
          "ジ": "シ",
          "ズ": "ス",
          "ゼ": "セ",
          "ゾ": "ソ",
          "ダ": "タ",
          "ヂ": "チ",
          "ヅ": "ツ",
          "デ": "テ",
          "ド": "ト",
          "バ": "ハ",
          "ビ": "ヒ",
          "ブ": "フ",
          "ベ": "ヘ",
          "ボ": "ホ",
          "パ": "ハ",
          "ピ": "ヒ",
          "プ": "フ",
          "ペ": "ヘ",
          "ポ": "ホ"
        },
        "kanjiKana": {
          "桐壺": "きりつほ",
          "帚木": "ははきぎ",
          "空蝉": "うつせみ",
          "夕顔": "ゆうがお",
          "若紫": "わかむらさき",
          "末摘花": "すえつむはな",
          "紅葉賀": "もみじのが",
          "花宴": "はなのえん",
          "葵": "あおい",
          "賢木": "さかき",
          "花散里": "はなちるさと",
          "須磨": "すま",
          "明石": "あかし",
          "澪標": "みおつくし",
          "蓬生": "よもきふ",
          "関屋": "せきや",
          "絵合": "えあわせ",
          "松風": "まつかせ",
          "薄雲": "うすくも",
          "朝顔": "あさかお",
          "少女": "おとめ",
          "玉鬘": "たまかづら",
          "初音": "はつね",
          "胡蝶": "こちよう",
          "螢": "ほたる",
          "蛍": "ほたる",
          "常夏": "とこなつ",
          "篝火": "かかりひ",
          "野分": "のわき",
          "行幸": "みゆき",
          "藤袴": "ふちはかま",
          "真木柱": "まきはしら",
          "梅枝": "うめかえ",
          "藤裏葉": "ふちのうらは",
          "若菜上": "わかなじょう",
          "若菜下": "わかなげ",
          "若菜": "わかな",
          "柏木": "かしわき",
          "横笛": "よこふえ",
          "鈴虫": "すすむし",
          "夕霧": "ゆうきり",
          "御法": "みのり",
          "幻": "まほろし",
          "匂宮": "におうみや",
          "紅梅": "こうはい",
          "竹河": "たけかわ",
          "橋姫": "はしひめ",
          "椎本": "しいかもと",
          "総角": "あけまき",
          "早蕨": "さわらひ",
          "宿木": "やとりき",
          "東屋": "あすまや",
          "浮舟": "うきふね",
          "蜻蛉": "かけろう",
          "手習": "てならい",
          "夢浮橋": "ゆめのうきはし",
          "雲隠": "くもかくれ",
          "玉": "たま",
          "鬘": "かつら",
          "夕": "ゆう",
          "顔": "かお",
          "紫": "むらさき",
          "紅葉": "もみち",
          "朱雀": "すさく",
          "藤壺": "ふちつほ",
          "惟光": "これみつ",
          "源氏": "げんじ",
          "物語": "ものがたり",
          "紫式部": "むらさきしきぶ",
          "光源氏": "ひかるげんじ",
          "桐壺帝": "きりつぼてい",
          "更衣": "こうい",
          "御息所": "みやすどころ",
          "入道": "にゅうどう",
          "大臣": "だいじん",
          "中宮": "ちゅうぐう",
          "女院": "にょういん",
          "宮": "みや",
          "君": "きみ",
          "上": "うえ",
          "殿": "との",
          "御前": "おまえ",
          "姫君": "ひめぎみ",
          "若君": "わかぎみ",
          "内裏": "だいり",
          "御所": "ごしょ",
          "里": "さと",
          "六条": "ろくじょう",
          "二条": "にじょう",
          "三条": "さんじょう",
          "四条": "しじょう",
          "五条": "ごじょう",
          "七条": "しちじょう",
          "八条": "はちじょう",
          "九条": "くじょう",
          "十条": "じゅうじょう"
        },
        "kanaKanji": {
          "きりつほ": "桐壺",
          "ははきぎ": "帚木",
          "うつせみ": "空蝉",
          "ゆうがお": "夕顔",
          "わかむらさき": "若紫",
          "すえつむはな": "末摘花",
          "もみじのが": "紅葉賀",
          "はなのえん": "花宴",
          "あおい": "葵",
          "さかき": "賢木",
          "はなちるさと": "花散里",
          "すま": "須磨",
          "あかし": "明石",
          "みおつくし": "澪標",
          "よもきふ": "蓬生",
          "せきや": "関屋",
          "えあわせ": "絵合",
          "まつかせ": "松風",
          "うすくも": "薄雲",
          "あさかお": "朝顔",
          "おとめ": "少女",
          "たまかづら": "玉鬘",
          "はつね": "初音",
          "こちよう": "胡蝶",
          "ほたる": "蛍",
          "とこなつ": "常夏",
          "かかりひ": "篝火",
          "のわき": "野分",
          "みゆき": "行幸",
          "ふちはかま": "藤袴",
          "まきはしら": "真木柱",
          "うめかえ": "梅枝",
          "ふちのうらは": "藤裏葉",
          "わかなじょう": "若菜上",
          "わかなげ": "若菜下",
          "わかな": "若菜",
          "かしわき": "柏木",
          "よこふえ": "横笛",
          "すすむし": "鈴虫",
          "ゆうきり": "夕霧",
          "みのり": "御法",
          "まほろし": "幻",
          "におうみや": "匂宮",
          "こうはい": "紅梅",
          "たけかわ": "竹河",
          "はしひめ": "橋姫",
          "しいかもと": "椎本",
          "あけまき": "総角",
          "さわらひ": "早蕨",
          "やとりき": "宿木",
          "あすまや": "東屋",
          "うきふね": "浮舟",
          "かけろう": "蜻蛉",
          "てならい": "手習",
          "ゆめのうきはし": "夢浮橋",
          "くもかくれ": "雲隠",
          "たま": "玉",
          "かつら": "鬘",
          "ゆう": "夕",
          "かお": "顔",
          "むらさき": "紫",
          "もみち": "紅葉",
          "すさく": "朱雀",
          "ふちつほ": "藤壺",
          "これみつ": "惟光",
          "げんじ": "源氏",
          "ものがたり": "物語",
          "むらさきしきぶ": "紫式部",
          "ひかるげんじ": "光源氏",
          "きりつぼてい": "桐壺帝",
          "こうい": "更衣",
          "みやすどころ": "御息所",
          "にゅうどう": "入道",
          "だいじん": "大臣",
          "ちゅうぐう": "中宮",
          "にょういん": "女院",
          "みや": "宮",
          "きみ": "君",
          "うえ": "上",
          "との": "殿",
          "おまえ": "御前",
          "ひめぎみ": "姫君",
          "わかぎみ": "若君",
          "だいり": "内裏",
          "ごしょ": "御所",
          "さと": "里",
          "ろくじょう": "六条",
          "にじょう": "二条",
          "さんじょう": "三条",
          "しじょう": "四条",
          "ごじょう": "五条",
          "しちじょう": "七条",
          "はちじょう": "八条",
          "くじょう": "九条",
          "じゅうじょう": "十条"
        },
        "phoneticChange": {
          "ふ": "う",
          "む": "ん",
          "つ": "っ",
          "は": "わ",
          "へ": "え",
          "を": "お",
          "ひ": "い",
          "く": "う",
          "ぬ": "ん",
          "フ": "ウ",
          "ム": "ン",
          "ツ": "ッ",
          "ハ": "ワ",
          "ヘ": "エ",
          "ヲ": "オ",
          "ヒ": "イ",
          "ク": "ウ",
          "ヌ": "ン"
        }
      },
      "stats": {
        "historicalKanaRules": 11,
        "dakuonRules": 50,
        "kanjiKanaRules": 96,
        "kanaKanjiRules": 95,
        "phoneticChangeRules": 18,
        "totalRules": 270
      },
      "options": {
        "unifyHistoricalKana": "旧字体統一 (ゑ→え、ゐ→い)",
        "unifyDakuon": "濁音統一 (が→か、ず→す)",
        "unifyKanjiKana": "漢字・仮名統一 (玉→たま)",
        "unifyPhoneticChanges": "発音変化統一 (ふ→う、は→わ)"
      },
      "description": {
        "historicalKana": "歴史的仮名遣いを現代仮名遣いに統一",
        "dakuon": "濁音・半濁音を清音に統一",
        "kanjiKana": "漢字を対応する仮名に変換",
        "kanaKanji": "仮名を対応する漢字に変換",
        "phoneticChange": "音韻変化を統一(助詞など)"
      }
    }
  },
  "meta": {
    "version": "1.0.0",
    "lastUpdated": "2025-06-25T07:08:42.608Z"
  }
}

まとめ

不完全なところもあるかと思いますが、原文の表記揺れを吸収する仕組みを含めた検索用APIサーバの構築例について紹介しました。

参考になりましたら幸いです。

Discussion