💨
校異源氏物語テキストDBに対する検索を行うAPIサーバの構築
概要
校異源氏物語テキストDBに対する検索を行うAPIサーバの構築したので、備忘録です。
背景
以下のページで、『校異源氏物語』のテキストデータをTEI/XMLに準拠した形で公開しています。
このテキストデータをElasticsearchに登録し、コマごとの検索を可能にするAPIを作成します。
使い方
以下のURLで、OpenAPIおよびSwaggerを用いた使い方の説明ページにアクセスできます。
https://genji-api.aws.ldas.jp/
工夫点
検索語の展開
例えば以下のURLは、「夕顔」を検索キーワードとした例です。JSON:APIに準拠した入出力形式としています。
この時、以下のような結果が返却されます。入力したキーワード「夕顔」に対して、バリエーションを生成し、これらに基づく検索を行います。
{
"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