英語の語彙を増やそうとしていますが、語彙を増やすときにやはり英文も読みながらの方がいいと思います。
そこで、難しい文学作品よりもニュース番組の方が英語としては明快なのではないかと考えて、CNN ニュースの記事から内容をスクレイピングしてデータベース化してみました。
例によって chatGPT に作ってもらいます。
まずはデータベース作成。
CREATE DATABASE cnn_news CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
次にテーブルを作成します。
CREATE TABLE articles ( id INT AUTO_INCREMENT PRIMARY KEY, url VARCHAR(500) UNIQUE NOT NULL, title VARCHAR(255), content TEXT);
CREATE TABLE words ( id INT AUTO_INCREMENT PRIMARY KEY, word VARCHAR(255) UNIQUE NOT NULL, frequency INT NOT NULL DEFAULT 0);
CREATE TABLE article_words ( article_id INT NOT NULL, word_id INT NOT NULL, frequency INT NOT NULL DEFAULT 0, PRIMARY KEY (article_id, word_id), FOREIGN KEY (article_id) REFERENCES articles(id) ON DELETE CASCADE, FOREIGN KEY (word_id) REFERENCES words(id) ON DELETE CASCADE);
100 を上限として CNN ニュース記事から単語を抜き出してデータベースに記録します。
import timeimport requestsfrom bs4 import BeautifulSoupimport spacyfrom collections import Counterimport pymysql
# MySQL接続設定DB_CONFIG = { "host": "localhost", "user": "user", "password": "pass", "database": "cnn_news", "charset": "utf8mb4", 'cursorclass': pymysql.cursors.DictCursor}
nlp = spacy.load("en_core_web_sm")
def fetch_article_urls_from_section(section_url, max_articles=50): article_urls = set() page = 1
while len(article_urls) < max_articles: # CNNはページネーションURL構造が違う可能性があるので必要に応じて調整してください url = f"{section_url}/page/{page}" print(f"Fetching page: {url}") res = requests.get(url) if res.status_code != 200: print("ページ取得失敗:", url) break soup = BeautifulSoup(res.text, "html.parser") links = soup.find_all("a", href=True) count_before = len(article_urls) for a in links: href = a['href'] if href.startswith("/2025/") or href.startswith("https://edition.cnn.com/2025/"): full_url = href if href.startswith("http") else "https://edition.cnn.com" + href article_urls.add(full_url) if len(article_urls) >= max_articles: break if len(article_urls) == count_before: # 新規記事なし=終わり break page += 1 time.sleep(1) return list(article_urls)[:max_articles]
def collect_many_articles(target_count=100): sections = [ "https://edition.cnn.com/politics", "https://edition.cnn.com/world", "https://edition.cnn.com/business", ] collected_urls = set() per_section = target_count // len(sections) + 1 for sec in sections: urls = fetch_article_urls_from_section(sec, max_articles=per_section) collected_urls.update(urls) if len(collected_urls) >= target_count: break return list(collected_urls)[:target_count]
def fetch_article_content(url): res = requests.get(url) if res.status_code != 200: return "" soup = BeautifulSoup(res.text, "html.parser") paragraphs = soup.select("article p") content = "\n".join(p.get_text() for p in paragraphs) return content
def fetch_article_title(url): res = requests.get(url) if res.status_code != 200: return "No Title" soup = BeautifulSoup(res.text, "html.parser") title_tag = soup.find("title") if title_tag: return title_tag.get_text() return "No Title"
def analyze_text(text): doc = nlp(text) words = [token.lemma_.lower() for token in doc if token.is_alpha and not token.is_stop] return Counter(words)
def save_article_and_words(url, title, content, word_freq): conn = pymysql.connect(**DB_CONFIG) try: with conn.cursor() as cursor: # 1. articlesテーブルに登録(URL重複チェック) cursor.execute("SELECT id FROM articles WHERE url=%s", (url,)) res = cursor.fetchone() if res: article_id = res['id'] else: cursor.execute( "INSERT INTO articles (url, title, content) VALUES (%s, %s, %s)", (url, title, content) ) article_id = cursor.lastrowid
# 2. wordsテーブルに単語登録・頻度更新 word_id_map = {} for word, freq in word_freq.items(): cursor.execute("SELECT id, frequency FROM words WHERE word=%s", (word,)) res = cursor.fetchone() if res: wid, current_freq = res['id'], res['frequency'] cursor.execute( "UPDATE words SET frequency=%s WHERE id=%s", (current_freq + freq, wid) ) else: cursor.execute( "INSERT INTO words (word, frequency) VALUES (%s, %s)", (word, freq) ) wid = cursor.lastrowid word_id_map[word] = wid
# 3. article_wordsテーブルに記事別単語頻度を登録・更新 for word, freq in word_freq.items(): wid = word_id_map[word] cursor.execute( "SELECT frequency FROM article_words WHERE article_id=%s AND word_id=%s", (article_id, wid) ) res = cursor.fetchone() if res: current_freq = res['frequency'] cursor.execute( "UPDATE article_words SET frequency=%s WHERE article_id=%s AND word_id=%s", (current_freq + freq, article_id, wid) ) else: cursor.execute( "INSERT INTO article_words (article_id, word_id, frequency) VALUES (%s, %s, %s)", (article_id, wid, freq) ) conn.commit() finally: conn.close()
def main(): print("記事URLを収集中...") article_urls = collect_many_articles(100) print(f"{len(article_urls)}件の記事を収集しました。")
for idx, url in enumerate(article_urls, 1): print(f"[{idx}/{len(article_urls)}] 処理中: {url}") content = fetch_article_content(url) if not content.strip(): print("本文が取得できませんでした。スキップします。") continue title = fetch_article_title(url) word_freq = analyze_text(content) save_article_and_words(url, title, content, word_freq) time.sleep(1) # 負荷軽減のため少し待つ
if __name__ == "__main__": main()
実行してみると、91 個のニュース記事から割と短時間に 6882 個の単語が記録されました。
英単語のデータベースを実際に動かしているのは xserver の mariadb で、たしか utf8mb4_unicode_520_ci だったので、 新しく作ったデータベースの照合順序も変換する必要があります。
ALTER DATABASE cnn_news CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
テーブルもいちいち変換する必要があるようです。
ALTER TABLE cnn_news.articles CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;ALTER TABLE cnn_news.article_words CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;ALTER TABLE cnn_news.words CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
これまで作成した難易度を記録してある別のデータベースと照合して表示してみます。
SELECT w.id, w.word, w.frequency, wl.gradeFROM cnn_news.words wLEFT JOIN STEP.wordlists wl ON w.word = wl.wordWHERE wl.grade IS NOT NULL AND wl.grade <> '' AND wl.grade NOT IN ('A1', 'A2', 'B1')ORDER BY w.frequency DESC;
B2 以上の単語を抽出すると、
英検 1 級がゴロゴロあります。
ここで「purport」という単語が掲載されている記事を探してみます。
SELECT a.id AS article_id, a.title, a.urlFROM cnn_news.articles aJOIN cnn_news.article_words aw ON a.id = aw.article_idJOIN cnn_news.words w ON aw.word_id = w.idWHERE w.word = 'purport';
以下の記事がピックアップされました。
google で翻訳すると、
単語がわかっていたとしても私の実力では理解が難しい感じがします。
2 つのデータベースにまたがって 3 つのテーブルを結合してデータを抽出するのはとても面倒です。
例えば、CNN ニュースに出てくる単語の中で CEFR のリストの B2 にあり、単語テストで不正解になった単語を抽出するのは、
SELECT w.id, w.word, w.frequency, wl.grade, wl.word_jp, wt.evaluationFROM cnn_news.words wJOIN STEP.wordlists wl ON w.word = wl.wordJOIN ( SELECT vocabulary_id, MAX(created_at) AS latest_date FROM STEP.word_tests GROUP BY vocabulary_id) latest ON wl.id = latest.vocabulary_idJOIN STEP.word_tests wt ON wt.vocabulary_id = latest.vocabulary_id AND wt.created_at = latest.latest_dateWHERE wl.grade = 'B2' AND wt.evaluation <> '○'ORDER BY w.frequency DESC;
こんな複雑なクエリを素人が組むとどこか間違えると思いますが、chatGPT はこういうことに関してはほぼ無敵だと思います。
こうして抽出した B2 の重要単語を 100 に絞って記憶しようと思います。 眺めているだけでも少しは違うかも。
単語 | 意味 | 頻度 |
---|---|---|
threaten | 脅す | 22 |
contempt | 軽蔑、侮辱罪 | 17 |
impose | 課す | 16 |
allegation | 申し⽴て、陳述 | 15 |
coalition | 連合 | 14 |
dismiss | 解雇する、退ける、解散させる | 13 |
equity | 公平、公正 | 12 |
mortgage | 抵当,担保 | 9 |
fraud | 詐欺 | 9 |
implication | 含み | 8 |
concession | 譲歩 | 7 |
surge | 急上昇、殺到する | 7 |
assault | 暴⾏ | 6 |
accusation | 告発 | 6 |
revelation | 暴露、発覚 | 6 |
perception | 知覚、理解 | 6 |
rally | 結集する | 6 |
implement | 道具 | 6 |
legislature | 議会、⽴法府 | 6 |
endorse | 承認する、裏書きをする | 6 |
defy | 反抗する | 5 |
provision | 準備、対策、蓄え、食糧 | 5 |
stake | 利害関係 | 5 |
corrupt | 堕落させる | 4 |
ballot | 投票⽤紙、投票 | 4 |
acquisition | 買収、⼊⼿ | 4 |
enforce | 施⾏する、強制する | 4 |
perceive | 知覚する、気がつく | 4 |
obligation | 義務 | 4 |
ramp | 傾斜路 | 4 |
plead | 嘆願する | 4 |
spur | 拍⾞ | 4 |
imperative | 命令法 | 4 |
bestow | 授ける | 3 |
ample | 広々とした、大きい、⼗分な | 3 |
brag | 自慢する、鼻に掛ける | 3 |
outrageous | 恐ろしい、無礼な、凶暴な | 3 |
peer | 同僚 | 3 |
rattle | がたがたと音を立てる | 3 |
sliver | 薄片、細い破片、とげ | 3 |
incur | 被る | 3 |
damn | 酷評する | 3 |
deliberately | 故意に、慎重に、時間と手間をかけて | 3 |
distortion | 歪み | 2 |
assumption | 想定、憶測 | 2 |
linger | いつまでも残る | 2 |
disregard | 無視 | 2 |
audit | 監査 | 2 |
obsess | 取りつく | 2 |
corruption | 汚職、腐敗 | 2 |
outdo | 上回る、しのぐ | 2 |
fierce | 激しい、荒々しい | 2 |
stern | 厳しい | 2 |
dehumanize | 人間性を奪うこと | 2 |
grim | 恐ろしい、ぞっとする | 2 |
prevail | 広がる、勝つ、通用する | 2 |
trample | 踏み付ける | 2 |
necessarily | 必ず | 2 |
slash | かき切る | 2 |
awe | 畏怖 | 1 |
desperately | 必死に | 1 |
deceptive | ⼈をだますような | 1 |
legitimate | 合法の | 1 |
predicament | 苦境 | 1 |
humiliate | 恥をかかせる | 1 |
furiously | 激しく、荒々しく | 1 |
indulge | ⽢やかす、~にふける | 1 |
soar | 舞い上がる | 1 |
infer | 推測する | 1 |
feat | 功績、妙技 | 1 |
adorn | 装飾する | 1 |
contradiction | ⽭盾 | 1 |
agony | 苦悩 | 1 |
mimic | 模倣者 | 1 |
doom | 運命、破滅、死 | 1 |
crease | ひだ、しわ | 1 |
fabulous | 信じられないほど | 1 |
shatter | 粉砕する | 1 |
catalyst | 触媒 | 1 |
reinforce | 補強する | 1 |
disgrace | 恥 | 1 |
strand | より⽷、髪の房 | 1 |
retreat | 退却すること | 1 |
devote | 献身する | 1 |
shed | 流す、発する、落とす、脱ぐ | 1 |
obsession | 執着 | 1 |
despise | 軽蔑する、嫌がる | 1 |
dread | 恐怖、恐れ | 1 |
deduction | 控除 | 1 |
anguish | 苦痛 | 1 |
innate | 先天的な | 1 |
ravage | 略奪、荒廃 | 1 |
slaughter | 屠殺、虐殺 | 1 |
venue | 開催地 | 1 |
sage | 賢⼈、哲⼈ | 1 |
hollow | くぼみ、凹地、⽳ | 1 |
tactile | 触覚の、触知の | 1 |
tyranny | 圧政、残虐さ | 1 |
reckon | 数え上げる、思う | 1 |
bizarre | 奇怪な | 1 |