1. モチベーション
- TOEFLという英語の検定試験を受験しているのですが、とにかく難しい英単語が多く出てきて大変なんです。
- そこで英単語帳を買って一生懸命勉強していたのですが、ある時はっと気づいたのです。
- **英単語帳ってどうやって作っているんだ??**試験に出ない単語なんか覚えても意味がない。俺は試験に出る単語だけに時間を割きたいんだ!!
- 中国TPOというTOEFLの過去問があるサイトをスクレイピングして調べてみました。
2. TOEFL
- 英語の試験です。非ネイティブが留学をする際などに課せられることが多く、TOEICよりも難易度は高いです。
- リーディング・リスニング・スピーキング・ライティングの4セクションからなり、各々30点の合計120点満点の試験となります。
3. 中国TPO
- 中国語のサイトに過去に実際に使われたTOEFLの問題が公開されており、これを利用して勉強する受験生も多いと思われます。
- そのサイトへのアクセス方法についてはここでは割愛しますが、2020年7月の現時点で54回分の過去問が公開されています。
4. いざスクレイピング!
- いつもの相棒BeautifulSoupを使っていきます!
root = 'https://toefl.kmf.com'
res = requests.get(root + '/read/ets/new-order/0/0')
time.sleep(0.5)
soup = BeautifulSoup(res.content, 'html.parser')
ingr = soup.find_all('div', class_='default-list-detail js-default-list')
res = requests.get(root + ingr[0].find_all('a')[0].get('href'))
time.sleep(0.5)
soup = BeautifulSoup(res.content, 'html.parser')
doc = soup.find_all('div', class_='i-stem-stem js-translate-new')
doc = doc[0].text.replace('\n','').replace('-',' ').replace('.','. ').lower()
- 1回目のrequestsでは↓のようなページにアクセスしています。ここで各過去問へのURLを確認しています。
- 2回目のrequestsで個別の問題のページにアクセスしています。過去問の文章が完全にきれいではなく、ピリオドの後にスペースがなかったりということがたまにあるため、最後の行でreplaceを使ってこの後のPoS taggingの処理がうまくいくよう少しかたちを整えます。僕はここで.replace('-',' ')をしているので、例えば"state-of-the-art"のような単語は"state","of","the","art"という4つの別々の単語として処理されていくことになります。
5. PoS tagging
- nltkというライブラリを使っていきます。理由は大きく2つ。固有名詞や前置詞、定冠詞などを除外したい。例えば動詞の過去形であれば現在形に直してから以後の処理を進めたい。
lemmatizer = WordNetLemmatizer()
pos_use = ['JJ','JJR','JJS','NN','NNS','RB','RBR','RBS','VB','VBD','VBG','VBN','VBP','VBZ']
lemma_pos = ['n', 'v', 'a', 'r']
def lemma(word):
for lem in lemma_pos:
lem_word = lemmatizer.lemmatize(word, pos=lem)
if word != lem_word:
return lem_word
return word
tokens = nltk.word_tokenize(doc)
tagged = nltk.pos_tag(tokens)
corpus = {}
for word, tag in tagged:
if tag not in pos_use:
continue
lem_word = lemma(word)
if lem_word not in corpus.keys():
corpus[lem_word] = 1
else:
corpus[lem_word] += 1
- pos_useでこれ以降使いたい単語の品詞を指定しています。品詞についてはこちらの記事に分かりやすくまとまっており、参考にさせていただきました。
- lemmaという関数では単語を原型にする処理をしています。lemmatizer.lemmatizeの部分がそれに当たります。この処理の仕方が1番いいのかは正直そこまで自信はありません(笑) ただ、いろいろ調べて挙動を見た上でこれがいいのではないかと思ってはいます。
- 一応lemmatizeの一例を出しておきます。carsをpos='n'で名詞として渡してやると、単数形の'car'を返してくれます。pos='v'で動詞を指定するとエラーではなく、与えた単語をそのまま返します。動詞の現在進行形として'analyzing'をpos='v'で与えると原型の'analyze'を返します。
lemmatizer.lemmatize('cars', pos='n')
=> 'car'
lemmatizer.lemmatize('cars', pos='v')
=> 'cars'
lemmatizer.lemmatize('analyzing', pos='v')
=> 'analyze'
- あとはnltkのword_tokenizeとpos_tagを使ってPoS taggingは完成です。その後のforループでその単語が何回出てきたかを数えます。辞書のkeyが単語名、valueが出現回数となります。
6. Let's 分析!
- ここまできたらあとは1番楽しい部分です!
- 今回はまず、過去に出てきた単語を全てマスターした場合、次のテストの単語をどの程度カバーできるようになるのかを見ていきます。
- 例えば、過去問の1から9までの単語がどれだけ過去問10の単語をカバーできているかを見ていくわけです。
- uniqueとしたものは重複を考慮していません。その回に100語の異なる単語が使われていたとして、その100語がそれ以前の過去問で既に出てきているかを見にいっています。totalは重複を考慮しています。1回の過去問で何度も使われている単語があった場合、それが過去問でも使われているとカバー率は高くなります。
- 20回目くらいを境にどちらも頭打ちになっています。過去問の単語を覚えても毎回10%ちょっとは見たことのない単語に出くわすことを示唆しています。
- uniqueな単語数も見ておきましょう。
- 受験生には厳しい結果となりました。毎回80wordsずつ程度新しい単語が使われていっています。
- それでも一方でカーブが緩くなっていっている点からは頻出の基本的な単語が存在していることが分かりますし、これを抑えるのは必須だと考えられます。
- ざっとですがこの範囲においては、$y=-233+1087x^{1/2}$くらいの式が当てはまりがいいです。unique wordsが10,000語を超えると新しい単語は50語程度になる計算です。
- 最後に具体的な単語を見てみましょう。いかにも頻出といった感じです。
rank | word | frequency |
---|---|---|
1 | be | 3,180 |
2 | have | 839 |
3 | water | 385 |
4 | more | 384 |
5 | not | 374 |
- 単語の出現回数は以下のような分布になります。
7. 最後に
- 今回はここまでですが、もう少し遊べそうなので今後追加でもう少し深く触ってみようと思います。
- 単語も深追いすればキリがないですが、ある程度頻出で抑えるべき単語というのは存在することが確認できました。
- オンラインの英和辞典を使えば今回のリストをスクレイピングで簡単に単語帳にすることができます。
- これの成果があってかどうかは分かりませんが、僕は過去にリーディング満点をとることができました(笑)