英語 - CNN ニュースの単語解析

(2025-08-11)

英語の語彙を増やそうとしていますが、語彙を増やすときにやはり英文も読みながらの方がいいと思います。

そこで、難しい文学作品よりもニュース番組の方が英語としては明快なのではないかと考えて、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
);

python によるスクレイピング

100 を上限として CNN ニュース記事から単語を抜き出してデータベースに記録します。

import time
import requests
from bs4 import BeautifulSoup
import spacy
from collections import Counter
import 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.grade
FROM
cnn_news.words w
LEFT JOIN
STEP.wordlists wl ON w.word = wl.word
WHERE
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.url
FROM
cnn_news.articles a
JOIN
cnn_news.article_words aw ON a.id = aw.article_id
JOIN
cnn_news.words w ON aw.word_id = w.id
WHERE
w.word = 'purport';

以下の記事がピックアップされました。

https://edition.cnn.com/2025/08/09/europe/trump-putin-summit-analysis-2-intl
What could Ukraine get back in the “swapping” Trump referred to? Perhaps the tiny slivers of border areas occupied by Russia in Sumy and Kharkiv regions – part of Putin’s purported “buffer zone” – but not much else, realistically.

google で翻訳すると、

トランプ氏が言及した「交換」で、ウクライナは何を取り戻せるだろうか?おそらく、ロシアが占領しているスムィ州とハルキフ州の一部、プーチン大統領が主張する「緩衝地帯」の一部である国境地帯のごく一部は手に入るだろうが、現実的にはそれ以上のものは得られないだろう。

単語がわかっていたとしても私の実力では理解が難しい感じがします。

いろいろと抽出する

2 つのデータベースにまたがって 3 つのテーブルを結合してデータを抽出するのはとても面倒です。

例えば、CNN ニュースに出てくる単語の中で CEFR のリストの B2 にあり、単語テストで不正解になった単語を抽出するのは、

SELECT
w.id,
w.word,
w.frequency,
wl.grade,
wl.word_jp,
wt.evaluation
FROM
cnn_news.words w
JOIN
STEP.wordlists wl
ON w.word = wl.word
JOIN (
SELECT
vocabulary_id,
MAX(created_at) AS latest_date
FROM
STEP.word_tests
GROUP BY
vocabulary_id
) latest
ON wl.id = latest.vocabulary_id
JOIN
STEP.word_tests wt
ON wt.vocabulary_id = latest.vocabulary_id
AND wt.created_at = latest.latest_date
WHERE
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