【最強の英単語帳爆誕ww】Pythonでエンジニア必須の英単語帳を自動生成-後編




# はじめに

こちらは後編の記事になりますので、まずは[前編](https://qiita.com/hossyan_eng/items/5bf18678763d47f06308)をご覧下さい。

↓ 今回作成した単語データを使用した学習の様子(記事の最後に**ダウンロードリンク**があります！）
![anki_2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/616192/9f98de1c-6e41-98bd-620c-d222cf4913d2.gif)


実行結果だけ見たい！という方はこちら→[実行結果](#実行結果)

# 目次
- 前回の振り返り
- 改善方法について
    - プログラム内で使用する単語が多い
    - case insensitiveになっていない
    - stackoverflowのページにオーバフィットしている
- コードの全体像
- 改善点と追加機能
    - プログラム内で使用する単語が多い
    - case insensitiveになっていない
    - stackoverflowのページにオーバフィットしている
    - 単語の翻訳
    - その他
- [実行結果](#実行結果)
- 結論
- 【重要】英単語帳を作るには
- データのダウンロード

# 前回の振り返り
## 仮説と検証結果
![previously.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/616192/0eb54755-69b5-ddc2-ca5c-c9740e1e08af.jpeg)
[前回](https://qiita.com/hossyan_eng/items/5bf18678763d47f06308)は前編ということで、プログラムで自動生成してしまえば
<h3>もう英単語帳購入の必要はないんじゃないか？</h3>
という仮説の簡単な検証を行なっていきました。
</br>
</br>
前回の実行結果の一部がこちら。

```shell
rank:0 word: ('i', 96)
rank:1 word: ('python', 86)
rank:2 word: ('ago', 50)
rank:3 word: ('bronze', 36)
rank:4 word: ('have', 29)
rank:5 word: ('×', 25)
```

## 問題点
![meeting_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/616192/f658a6ab-b6ef-507e-99fb-46f4ef30b119.jpeg)

[前回](https://qiita.com/hossyan_eng/items/5bf18678763d47f06308)の検証結果として下記3つの問題点があることが見えてきました。
>
1. プログラムのみで使用する単語が存在する
2. case insensitiveになっていない
3. stackoverflowのページにオーバフィットしている

今回はこれらの課題を解決し、
<h3>エンジニアにとって必要な英単語を最効率で学習</h3>
できる英単語帳の作成を目指していきます。


# 改善方法について
## プログラム内で使用する単語が多い
![plain_code.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/616192/c00c69a8-ac72-bf41-bd98-fe0ca8c85dc3.jpeg)


def、jやkなどの単語のことになるのですが、プログラム的に除去していく方法としては以下のようなものが挙げられます。

1. プログラムのみで使用するような単語のリストデータを作成し、フィルタリング
2. 自然言語として意味のなさないものを除去する+2文字以下は単語とみなさない※1
3. 品詞の解読精度を向上させる

1と3は時間がかかるので、**TextBlobの機能が生かせる2を採用**していきます。

まずは、こちらをご覧下さい。

```python

>>from textblob import Word
>>print(Word("list").definitions)
>>['a database containing an ordered array of items (names or topics)',...]
```
こちらで分かる通り、単語に意味が存在する場合`definitions`というアトリビュートを呼ぶと、定義をリストで返してくれます。

同様に'def'の定義を見てみると

```python

>>from textblob import Word
>>print(Word("def").definitions)
>>[]
```
のように、空のリストが返されますので、こちらを利用して本来の英語には存在しない単語を除去していきます。


## case insensitiveになっていない

これは下記のようにstringのメソッドlowerを呼んで、全て小文字にすることで対応していきます。


```python
'string'.lower()
```


## stackoverflowのページにオーバフィットしている
![AI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/616192/3cd3633f-ccc4-4ed4-6507-576d7df37adf.jpeg)

ここが良い単語帳を作成するにのに最も重要なポイントとなりそうです。「エンジニア」をターゲットとすると少し幅が広くてデータをとるのが大変なので、下記2点を行っている人にフォーカスした単語帳を作成することとして5つのwebページをピックアップしてみました。

1. Pythonを使用している
2. AIを使用したバックエンド開発を行っている

1/5 AWS Lamda
https://aws.amazon.com/lambda/
<img width="1414" alt="AWS.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/616192/051ddbbb-76fb-a626-29b0-76a978a864fa.png">



2/5 Python3.8 What's new
https://docs.python.org/3/whatsnew/3.8.html
<img width="1404" alt="python.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/616192/9f5e6cd5-37c7-e59e-c26a-d72337872834.png">



3/5 Docker what is container
https://www.docker.com/resources/what-container
<img width="1348" alt="docker.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/616192/aaffc4c3-cb35-a54f-1244-28924fd32d02.png">


4/5 Wikipedia Artificial intelligence
https://en.wikipedia.org/wiki/Artificial_intelligence
<img width="1384" alt="AI.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/616192/446a76e8-ac5b-a2be-e463-d4d7eb03199d.png">


# コードの全体像
基本的には前回のコードを使いまわしていますが、今回は複数のサイトにアクセスするためのfor文が必要となったので、一部関数の切り出しと修正を行っています。

```python
from enum import Enum, unique
from typing import List, Tuple, Dict
import requests

from bs4 import BeautifulSoup as bs
from textblob import TextBlob, Word
from textblob.exceptions import NotTranslated

URLS = (
    ('stackoverflow', 'https://stackoverflow.com/questions/tagged/python'),
    ('AWS', 'https://aws.amazon.com/lambda/'),
    ('wikipedia AI', 'https://en.wikipedia.org/wiki/Artificial_intelligence'),
    ('Docker', 'https://www.docker.com/resources/what-container'),
    ('Python3.8 doc', 'https://docs.python.org/3/whatsnew/3.8.html'),
)

PARSER = "html.parser"
UNACCEPTABLE_LENGTH_OF_WORD = 2


@unique
class PartOfSpeechToLearn(Enum):
    JJ = 'Adjective'
    VB = 'Verb'
    NN = 'Noun'
    RB = 'Adverb'


def single_page_text_from_url(url: str) -> str:
    res = requests.get(url)
    raw_html = bs(res.text, PARSER)
    texts_without_html: str = raw_html.text
    return texts_without_html


def filter_for_wordbook(morph: TextBlob) -> List[str]:
    word_and_tag: List[Tuple[str, str]] = morph.tags
    part_of_speech_to_learn = tuple(pos.name for pos in PartOfSpeechToLearn)

    filtered_words = []
    for wt in word_and_tag:
        word: str = wt[0].lower()
        part_of_speech: int = wt[1]
        if part_of_speech not in part_of_speech_to_learn:
            continue
        if word in filtered_words:
            continue
        if (
                len(word) <= UNACCEPTABLE_LENGTH_OF_WORD or
                len(Word(word).definitions) == 0
        ):
            continue
        filtered_words.append(word)
    return filtered_words


def show_top_50_common_and_uncommon_words(word_and_count: Dict[str, int]) -> None:
    words_by_descending_order: List[Tuple[str, int]] = sorted(
        word_and_count.items(),
        key=lambda x: x[1],
        reverse=True
    )

    print('the most common words')
    for i, word_and_count in enumerate(words_by_descending_order[:10]):
        word: str = word_and_count[0]
        count: int = word_and_count[1]
        try:
            meaning_jp = str(TextBlob(word).translate(to='ja'))
        except NotTranslated:
            meaning_jp = word
        print(f'rank:{i + 1} word: {word} count: {count} meanings: {meaning_jp}')

    print('the most uncommon words')
    for j, word_and_count in enumerate(words_by_descending_order[:-11:-1]):
        word: str = word_and_count[0]
        count: int = word_and_count[1]
        try:
            meaning_jp = str(TextBlob(word).translate(to='ja'))
        except NotTranslated:
            meaning_jp = word
        print(f'rank:{j + 1} word: {word} count: {count} meanings: {meaning_jp}')


if __name__ == '__main__':
    word_and_count: Dict[str, int] = {}
    for url in URLS:
        print(f'website: {url[0]} url: {url[1]}')

        texts_without_html = single_page_text_from_url(url[1])

        morph = TextBlob(texts_without_html)
        filtered_words = filter_for_wordbook(morph)

        for word in filtered_words:
            count = morph.words.count(word, case_sensitive=False)
            if count == 0:
                continue
            if word not in word_and_count.keys():
                word_and_count[word] = count
            else:
                word_and_count[word] += count

        print(f'length of word_and_count: {len(word_and_count)}')

    show_top_50_common_and_uncommon_words(word_and_count)

```

それでは、前回から改良した点をみていきましょう！

# 改善点と追加機能
## プログラム内で使用する単語が多い
前回内包表記でフィルタリングしていた部分を全て関数に切り出して`if`でフィルターするようにしています。シンプルな処理であれば内包表記がnamespaceの節約と処理効率の面で便利ですが、複数の条件でデータを処理する場合には`if`文を使用する方がシンプルでベターかと思います。
</br>
`if`に関してはネストを避けるために、処理が軽そうなものから順に`continue`していくようにしています。

```python
def filter_for_wordbook(morph: TextBlob) -> List[str]:
    word_and_tag: List[Tuple[str, str]] = morph.tags
    part_of_speech_to_learn = tuple(pos.name for pos in PartOfSpeechToLearn)

    filtered_words = []
    for wt in word_and_tag:
        word = wt[0].lower()
        part_of_speech = wt[1]
        if part_of_speech not in part_of_speech_to_learn:
            continue
        if word in filtered_words:
            continue
        if (
            len(word) <= UNACCEPTABLE_LENGTH_OF_WORD or
            len(Word(word).definitions) == 0
        ):
            continue
        filtered_words.append(word)
    return filtered_words
```

上記コードのここで、プログラムでしか使用しない単語をフィルタリングしています。

```python
if (
    len(word) <= UNACCEPTABLE_LENGTH_OF_WORD or 
    len(Word(word).definitions) == 0
):
    continue
```


## case insensitiveになっていない
先ほどのコードの下記部分で、大文字小文字が違うだけで同じ単語を同一データとして重複を避ける処理を入れています。

```python
for wt in word_and_tag:
    word = wt[0].lower()
    if word in filtered_words:
        continue
```


## stackoverflowのページにオーバフィットしている

先ほど紹介したwebサイトを`tuple`型で`URLS`として定義しています。

```python
URLS = (
    ('stackoverflow', 'https://stackoverflow.com/questions/tagged/python'),
    ('AWS', 'https://aws.amazon.com/lambda/'),
    ('wikipedia AI', 'https://en.wikipedia.org/wiki/Artificial_intelligence'),
    ('Docker', 'https://www.docker.com/resources/what-container'),
    ('Python3.8 doc', 'https://docs.python.org/3/whatsnew/3.8.html'),
)
```

## 単語の翻訳
中身はGoogle翻訳ですが、単語の翻訳もTextBlobライブラリを使用しています。
`NotTranslated`は、`python`という単語のように、翻訳しても`python`となるようなものを翻訳しようとした時に`raise`されるものになります。

```python
try:
    meaning_jp = str(TextBlob(word).translate(to='ja'))
except NotTranslated:
    meaning_jp = word
```

## その他
お気づきになった方もいらっしゃるかもしれませんが、下記のコードで

```python

for word in filtered_words:
    count = morph.words.count(word)
    if count == 0:
        continue
    if word not in word_and_count.keys():
        word_and_count[word] = count
    else:
        word_and_count[word] += count

```

このように、カウントがゼロの場合、次のループへ移行する文があります。

```python
count = morph.words.count(word)
if count == 0:
    continue
```
具体的には`e.g.,`のような文字列を解析した際、トークナイズ時は`e.g.`、品詞毎に分解した際は`e.g`となるため、`e.g.,`のような単語がきた時に`count==0`となるのでこちらのコードを挿入しています。

<img width="190" alt="Screen Shot 2020-04-24 at 15.12.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/616192/01999fe3-c879-39e0-295f-5926f8f86c92.png">

自然言語処理とTextBlobの使い方に関してまだまだ勉強不足ですので、ある程度知見がたまったら別途TextBlobの使い方まとめ、みたいな形で記事を出した際に解説していきたいと思います。

# 実行結果
![surprise.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/616192/3ba87aad-8aa3-ba47-2d8b-5c921decddd4.jpeg)


前回の問題点は無事改良されているのでしょうか................?


実行結果はこちら..................................! 

```
the most common words
rank:1 word: intelligence count: 327 meanings: 知性
rank:2 word: artificial count: 280 meanings: 人工的な
rank:3 word: help count: 190 meanings: 助けて
rank:4 word: python count: 186 meanings: python
rank:5 word: error count: 184 meanings: エラー
rank:6 word: target count: 168 meanings: 目標
rank:7 word: new count: 134 meanings: 新着
rank:8 word: learning count: 129 meanings: 学習する
rank:9 word: machine count: 125 meanings: 機械
rank:10 word: have count: 120 meanings: 持ってる

the most uncommon words
rank:1 word: donate count: 1 meanings: 寄付
rank:2 word: math count: 1 meanings: 数学
rank:3 word: inspect count: 1 meanings: 調べる
rank:4 word: ignore count: 1 meanings: 無視する
rank:5 word: processor count: 1 meanings: プロセッサー
rank:6 word: queue count: 1 meanings: キュー
rank:7 word: depend count: 1 meanings: 依存する
rank:8 word: pretend count: 1 meanings: ふり
rank:9 word: decrease count: 1 meanings: 減少
rank:10 word: allocator count: 1 meanings: アロケータ

```

おおおおおおおおっ！

# 結論
![target_goal.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/616192/f148a81a-f588-9bcc-806a-808050bffebf.jpeg)

まずはもう一度仮説に振り返ってみたいと思います。今回のシリーズでの仮説は
Pythonで自動的に作成すれば、
<h3>もう英単語帳購入の必要はないんじゃないか？</h3>
というものでした。
</br>
</br>
こちらの仮説に対しての結論としては、
<h3>英単語帳は購入しなくても、プログラムで作れる！</h3>
といえそうです！！！※2


# 【重要】市販のような英単語帳を作るには

![tips.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/616192/ad7d5767-6017-5c1e-0483-b35d782efa05.jpeg)

実行結果をみていただければわかりますが、今回抽出できた単語だけでもかなり有用なことが分かります。ただ、**[市販の単語帳](https://hossyan-blog.com/2020/04/17/increase-vacab/)**と同等のものを作るにはインターフェースと3つの追加情報が必要になってきます。

## インターフェース
[市販の単語帳](https://hossyan-blog.com/2020/04/17/increase-vacab/)には、本、もしくはアプリといった問題形式で覚えたり、内容の確認をしやすくするためのインターフェースを備えています。

今回のように自作英単語データを学習に使用したいという方には、私のブログでも紹介していますAnkiアプリが超おすすめです！！素のAtom使ってる人がIDEを使いこなした時ぐらい感動します！

先日Twitterでたまたま見かけたのですが、連続起業家で有名な家入一真さんもAnkiを使用しているようです。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">会食が無い分、毎晩ずっとNGSL3000語、NAWL1000語、英単語を暗記しまくってる。AnkiとQuizlet便利すぎ</p>&mdash; 家入 一真@クラファンのCAMPFIRE (@hbkr) <a href="https://twitter.com/hbkr/status/1248053217931345920?ref_src=twsrc%5Etfw">April 9, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

PC版は無料でインストールできるので、PCだけであれば完全無料で英単語学習ができそうです。※3
[【英語学習に必須】Ankiアプリとは？](https://hossyan-blog.com/2020/04/16/anki-app)
![anki.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/616192/e10955ac-c4e2-307b-cc70-04b81fed7eb1.png)


## 3つの情報
**市販の英単語帳**には通常、下記の３つの情報がほぼ確実に含まれています。

1. 単語の翻訳詳細(品詞、他の意味）
2. 例文
3. 例文の翻訳

1.単語の翻訳詳細(品詞、他の意味）や2.例文に関してはTextBlobの機能を使えばうまく引っ張ってくることができるのですが、例文の翻訳が問題となってきます。
</br>

こちらの記事でも紹介しましたが、Google翻訳を使用して文章を翻訳するとコンテクストの問題やそもそもの翻訳が違うなどの問題がでてくるので、正しい学習ができない可能性があります。
[まだGoogle翻訳で消耗してるの？](https://qiita.com/hossyan_eng/items/a5e7b92a4b23427e864d)


<h2>しかし</h2>
人力であれば、例文の**自然な翻訳**をしていくことはまだ可能です。

もし、[私のTwitter](https://x.com/hosh_it_)のフォロワーが1000を超えたら、今回作成したプログラムをさらに改良し**例文も含めた**、
<h3>エンジニア向けの最強英単語帳</h3>
のデータを配布していきたいと思います。
</br>

X：[ほし|Python](https://x.com/hosh_it_)



注釈
※1 下記のサイトを見た感じだと無視して良さそうです
https://en.wikibooks.org/wiki/Scrabble/Two_Letter_Words
https://en.wiktionary.org/wiki/Category:English_one-letter_words
※2 英単語帳を英単語とその翻訳のデータセットとして定義すれば
※3 Ankiさん、広告費貰えるわけでもないのに激推ししておきましたよ！