wikipediaのAPIを使ってゲーム作ってみた#2

# はじめに
こんにちは！
今回はwikipediaみたいにページのあるワードをリンク化していきたいと思います！
前回の記事はこちらです↓

https://qiita.com/ITTON2001/items/cbc317f39874efa2c514

# 作業開始
### 1.リンクが貼られているワードを取得する
ちなみにwikipediaapiには指定されたページ内にある別のページのリンクを取得することができます。

```python
links = page_py.links
```

今回はリンクの名前だけ欲しいので、リンクの名前だけを抽出します。
```python
link_word = []
for title in sorted(links.keys()):
    link_word.append(title)
```
これで、文中のどのワードにリンクが貼られているのかが分かりました！

### 2.文中のリンクが貼られていたワードをリンク化する
さて、ここからが問題です。

1.文中のlink_wordリストにあるワードをすべてリンク化する→Python
2.リンク化されたワードをクリックしたらそのワードのページを生成する→javascript

これをどうにかして実装します。
まず、1.はこんな感じで実装しました。

```python:views.py
result = []
saved_words = []
i = 0
#リストのワードを調査する
for word in link_word:
    #リストのワードがpage_textにあるか調べ、その出現位置を記録
    start_index = page_text.find(word)
    #記録されたとき
    if start_index != -1:
       #出現位置と文字の長さを足すことで対象の単語の終了位置を取得する
       end_index = start_index + len(word)
       #出現位置の所までの文章をbefに入れる
       bef = page_text[:start_index]
       #終了位置から後ろの文章をaftに入れる
       aft = page_text[end_index:]

       #対象のワードをspanタグに変更しbefとaftをつなげた文章をresultリストに追加する
       result.append(bef + '<span style="cursor: pointer;" onclick="handleClick()">' + word + '</span>' + aft)
        # 次のワードの検索に使用するため、更新された文章を調査対象の文章に設定する
        page_text = result[i]  
        #回数の更新
        i += 1

     #ワードの調査が終わったとき
     if result:
        res = result[-1]  # 最後の要素を取得

```

解説していきます！

まずfor文でlink_wordリストのワードを一つずつ取り出して、文中にそのワードがあるか調査し、文中における該当ワードの出現位置を記録します。

```python
#リストのワードを調査する
for word in link_word:
    #リストのワードがpage_textにあるか調べ、その出現位置を記録
    start_index = page_text.find(word)
```
続いて、文中における該当ワードの終了位置を調べます。
```python
#記録されたとき
if start_index != -1:
    #出現位置と文字の長さを足すことで対象の単語の終了位置を代入する
    end_index = start_index + len(word)
```
出現位置までの文章が「該当ワードよりも前にある文章」、終了位置以降の文章が「該当ワードよりも後にある文章」になるので、それぞれ変数に入れときます。
```python
#出現位置の所までの文章をbefに入れる
bef = page_text[:start_index]
#終了位置から後ろの文章をaftに入れる
aft = page_text[end_index:]
```
こうすることで、文章を「該当ワードより前の文章」、「該当ワード」、「該当ワードより後の文章」に分けることができました。

これで、文中から該当ワードを抽出することに成功したので、該当ワードをリンク化し（今回は2.の処理のためにもjavascriptでクリックイベントにするためにspanタグで囲みます）、「該当ワードより前の文章」・「該当ワードより後の文章」と繋げます。
```python
#対象のワードをspanタグに変更しbefとaftをつなげた文章をresultリストに追加する
result.append(bef + '<span style="cursor: pointer;" onclick="handleClick()">' + word + '</span>' + aft)
```

更新した文章は次のワードの変換作業に使うため、調査対象の文章を更新しておきます。
```python
# 次のワードの検索に使用するため、更新された文章を調査対象の文章に設定する
page_text = result[i] 
#回数の更新
i += 1
```

link_wordリストのワードをすべてリスト化することに成功したら、resultリストの最後に記録されている文章が完成した文章になるので、それを取得します。
```python
#ワードの調査が終わったとき
if result:
   res = result[-1]  # 最後の要素を取得
```

これでresを返すことで、リンクを追加したいワードをすべてリンク化した文章を出力できます！

### 3.バグを修正する
ただ、このままだといくつかバグが発生するので直します。

例えば「株式会社みかん会社のみかんです」みたいな文章があったとします。
そして、link_wordリストの中身が[みかん],[株式会社みかん会社]だったとします。
すると、順番的に[みかん]が優先されてしまうため、「株式会社みかん会社」の「みかん」にリンク化され、後ろの「みかん」がリンク化しなくなります。

これではまずいので、リストの中身をワードの長い順にして、ワードが長い方からリンク化していきます。
```python
#リンクのワードを長い順に並べる
sorted_link_word = sorted(link_word, key=len, reverse=True)
```

これで解決！ってわけにはいかないんですよね・・・。

原因は、ここ↓の部分でリスト化した文章を調査対象にしているからです。
```python
# 次のワードの検索に使用するため、更新された文章を調査対象の文章に設定する
page_text = result[i] 
```

つまり、「株式会社みかん会社」をリンク化した後、この更新した部分も調査対象になるので、「株式会社みかん会社」の「みかん」をリンク化してしまいます。
結局、本来リンク化される部分がリンク化されません・・・。

そこで、一回リンク化した部分は調査対象にしないようにしましょう！
まずは、調査するワードを一時的に別のリストに保管し、wordを「[task数字]」に置き換えます。
```python
#リンクのワードを一時的に別の変数に保存
saved_words.append(word) 
#順番を与えたtaskを代わりに置く
word = "[task"+str(i)+"]"
```
こうすること文中のリンク化するワードは全部「[task数字]」になるので調査対象にはならなくなります。
（流石に[task1]みたいなwikipediaのページはないと思うので大丈夫でしょう・・・多分・・・。)

ワードの調査終了後、全部の「[task数字]」を元のリンク化するワードに戻します。
```python
if result:
    res = result[-1]  # 最後の要素を取得    
    #taskの修正作業
    #taskの順番に応じて保存したリンクのワードに置き換えていく
    for i, word in enumerate(saved_words):
        res = res.replace(f"[task{i}]", word)
```
これで、「株式会社みかん会社」の「みかん」をリンク化してしまう問題を解決しました！

これで完璧！というわけには残念ながらいかないのです・・・。

例えば「東京都」のページを開くと、「東京都」には[東京都]がリンク化されていないので、「東京都」の「京都」の部分がリンク化されてしまいます。
これと似たような問題がちらほら確認されます。（「明暗」の「明」がリンク化されて中国の「明王朝」のページに飛ばされる等・・・）

この問題は、僕もまだ解決できていないので、何か良い解決策があったら是非教えてください・・・。

恐らく、いろいろなバグが他にも残っていると思いますが、とりあえずこれで解決できる部分は解決できたかと思います。

最終的にはこんなこんな感じのコードになりました。
```python:views.py
#wikipediaapiの処理
def wikipediaapi(selected_word_receive):
    import wikipediaapi
    # wikipediaapiのデータの言語を日本語化
    wiki_wiki = wikipediaapi.Wikipedia(
    language='ja',
    extract_format=wikipediaapi.ExtractFormat.HTML,
    user_agent='CoolBot/0.0 (https://example.org/coolbot/; coolbot@example.org)'
    )

    # 検索ワード
    page_py = wiki_wiki.page(selected_word_receive)    

    # デフォルト値を設定
    page_title = ''
    page_text = ''
    res = ''

    #検索ワードを発見した時
    if page_py.exists():
        page_title = page_py.title
        page_text = page_py.text

        #リンクが貼られていた言葉のリストを作成する
        links = page_py.links
        link_word = []
        for title in sorted(links.keys()):
            link_word.append(title)
        #print(link_word)

        #リンクのワードを長い順に並べる
        sorted_link_word = sorted(link_word, key=len, reverse=True)
        #print(sorted_link_word)

        #文章生成プログラム
        result = []
        saved_words = []
        i = 0
        #リストのワードを調査する
        for word in sorted_link_word:
            #リストのワードがpage_textにあるか調べ、その出現位置を記録する
            start_index = page_text.find(word)
            #記録されたとき
            if start_index != -1:
                #出現位置と文字の長さを足すことで対象の単語の終了位置を取得する
                end_index = start_index + len(word)
                #出現位置の所までの文章をbefに入れる
                bef = page_text[:start_index]
                #終了位置から後ろの文章をaftに入れる
                aft = page_text[end_index:]

                #リンクのワードを一時別の変数に保存
                saved_words.append(word) 
                #順番を与えたtaskを置く
                word = "[task"+str(i)+"]"

                #wordをspanタグで囲みbefとaftをつなげた文章をresultリストに追加する
                result.append(bef + '<span style="cursor: pointer;" onclick="handleClick()">' + word + '</span>' + aft)
                # 次のワードの検索に使用するため、更新された文章を調査対象の文章に設定する
                page_text = result[i]  
                #回数の更新
                i += 1

        #ワードの調査が終わったとき
        if result:
            res = result[-1]  # 最後の要素を取得
            
            #taskの修正作業
            #taskの順番に応じて保存したリンクのワードに置き換えていく
            for i, word in enumerate(saved_words):
                res = res.replace(f"[task{i}]", word)

    #検索ワードが見つからなかった場合
    else:
        page_text = 'ページが見つかりませんでした。'

    return page_title,page_text,res
```
CSSでspanタグの部分を青色にするとすごくwikipediaっぽくなるかと思います。

# おわりに
ということで、文中のリンク化したいワードをリンク化することができました！

しかし、ここまでだとリンク化したワードをクリックしたところでそのページになるわけではないので、次回はワードをクリックしたらそのワードの記事を閲覧できるようにして、よりwikipediっぽくしていきたいと思います！


今回の実装でもっと良い方法があったり、記事についてもっとこうするべき等があれば、是非教えてください！

github:https://github.com/ITTON2001/WikiWord