HanDicをPythonで利用する例として,文書中に現れる単語の重要度を示す,TF-IDFの計算を行ってみます.TF-IDFについては以下などを参照:
実際にスクリプトを書くに当たっては,以下を参考にしました.必要な部分をだいたい丸写ししています:
Pythonのバージョンは3.7.11,HanDicはhandic-mecab-20210116を用いました.MeCabを扱うためのモジュールmecab-python3ほか,必要なモジュールがインストールされていることが前提です.HanDicはPythonスクリプトのあるディレクトリ下,handic
という名前のディレクトリに保存されているとします.
なお,HanDicに含まれるファイルのうちdicrc
に記述の不備があり,「bos-feature」行の末尾に「,*,*
」を追加しました.
また,HanDicをPythonで利用するで説明したk2jamo.py
も作っておきます.
韓国の新聞社サイトで公開されているRSSフィードを使って,リストにある記事を取得し,名詞類(普通名詞,固有名詞,語根)のTF-IDFを計算します.その上で,リスト中の記事から10本をランダムに選び,TF-IDF上位10語を列挙します.
ここでは例として,경향신문(京郷新聞)のRSSサービスから,「정치(政治)」カテゴリのフィードを利用します.大手の新聞社でRSSサービスを行っているのは以下の通りです:
付記した理由から,ここでは경향신문を取り上げることにします.他のRSSを利用する場合,適宜読み替えてください.
必要なモジュールなどを呼び出します.
import random, requests, MeCab from bs4 import BeautifulSoup from sklearn.feature_extraction.text import TfidfVectorizer import pandas as pd import k2jamo
何をどういう順番で呼び出すのか,お作法がちょっと分からないので適当にやっています.
MeCabのインスタンスを作成します.
tokenizer = MeCab.Tagger('-d handic') tokenizer.parse('')
경향신문の記事URLから該当する記事を取得し,本文(<p class='content_text'>
の内容)を取得します.requests.get()
でうまく記事が取得できなかったので,User Agentを設定してあります.
# 記事と本文の取得 def news_scraping(url): paras = [] ua = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36' headers = {'User-Agent': ua} html = requests.get(url, headers=headers) soup = BeautifulSoup(html.content, 'html.parser') # 경향신문向け articles = soup.select('p.content_text') # 중앙일보向け # articles = soup.select('#article_body > p') for p in articles: paras.append(p.text) return '\n'.join(paras)
なお,記事本文が格納されている要素はサイトによって異なります.ここまで分かっている中では,중앙일보(中央日報)の場合,「경향신문向け」としてある次の行をコメントアウトして,articles = soup.select('#article_body > p')
の方を使用すると,記事本文が取り出せます.他はまだ調査中です.
[2021/11/06 追加] いくつか調べた結果,書き換えの内容は以下の通りです.
한겨레(ハンギョレ)articles = soup.select('div.article-text')
노컷뉴스(ノーカットニュース・CBS)articles = soup.select('div', id = 'pnlContent')
MeCabで解析し,普通名詞(世宗タグ「NNG」),固有名詞(同「NNP」),語根(同「XR」)を取り出してその基本形を配列に格納します.最後に,配列の内容をスペースで連結します.
# mecabで解析,スペース区切りの配列にする def extract(text): words = [] node = tokenizer.parseToNode(text) while node: if node.feature.split(',')[10] in ['NNG', 'NNP', 'XR']: words.append(node.feature.split(',')[5]) node = node.next text_result = ' '.join(words) return text_result
名詞だけでもよいのですが,語根には「간단02(簡単)」「깨끗」「소중01(所重)」などといった語があり,特に政治の記事では人や政策のイメージなどとも関連すると思われるので,とりあえず含めてあります.世宗タグについてはHanDicの品詞体系(すいません,書きかけです)を参照のこと.
RSSフィードから各記事のリンクを取得します.
# RSSフィードの処理 # 경향신문 정치 feed = 'https://www.khan.co.kr/rss/rssdata/politic_news.xml' # 중앙일보 주요기사 # feed = 'https://rss.joins.com/joins_homenews_list.xml' req = requests.get(feed) req.encoding = req.apparent_encoding txt = BeautifulSoup(req.text, 'lxml-xml') rss_list = txt.select('channel > item')
中央日報の主要記事も例として挙げてあります.中央日報で試す場合はコメントアウトを外し,上の「경향신문 정치」の次の行をコメントアウトしてください.また上述の通り,news_scraping()
も変更しておく必要があります.
次に,リンクのリストからそれぞれの記事を取得して処理し,MeCabで解析する部分です.
# 記事本文の処理,docsに格納 docs = [] titles = [] for i in range(len(rss_list)): print("processing:", i+1, "/", len(rss_list)) text = k2jamo.substitute(news_scraping(rss_list[i].link.text)) text = extract(text) titles.append(rss_list[i].title.text) docs.append(text)
各記事のタイトルは,別途配列に入れました.
scikit-learnでTF-IDFを計算します.アップデートしたら「get_feature_names()
はバージョン1.0では非推奨で,1.2で削除する」と出てきたので,get_feature_names_out()
を使っています.
# モデルを作成 vectorizer = TfidfVectorizer(smooth_idf=False) values = vectorizer.fit_transform(docs).toarray() feature_names = vectorizer.get_feature_names_out() df = pd.DataFrame(values, columns = feature_names, index=titles)
上記データフレームから,ランダムに10件の記事を選んで,それぞれTF-IDFの値で上位10語を列挙します.
# 文書をランダムに10個選択,それぞれ上位10語の出力 for num in random.sample(range(len(df)), 10): temp_df = [] temp_df = df[num:num+1].T temp_df = temp_df.sort_values(by=titles[num], ascending=False) print(temp_df.head(10))
実行結果は以下の通り.フィードの項目(item)数が10以下だとエラーになります.random.sample()
で取り出す数を変更してください.
> python handic_mecab.py processing: 1 / 54 Some characters could not be decoded, and were replaced with REPLACEMENT CHARACTER. ...中略... processing: 54 / 54 Some characters could not be decoded, and were replaced with REPLACEMENT CHARACTER. 안철수 출마와 김동연, 제3지대 가능성은 지대07 0.363606 동연 0.293522 철수 0.269679 안03 0.248124 후보04 0.186889 양보03 0.149720 철수02 0.146761 사람 0.141769 출마 0.135213 김06 0.127492 한미연합군사령관 “가장 강력하고 준비된 연합전력” 연합03 0.383949 연합사 0.358117 군사령부 0.268588 주한02 0.268588 미14 0.239796 창설 0.179059 억제 0.179059 기념식 0.179059 미구03 0.179059 유지09 0.098292 윤석열, 공정·정의·소통 강조하며 ‘반문 기치’…“내 승리, 뼈아플 것” 윤01 0.297543 총장01 0.247608 정권04 0.226711 연설02 0.201548 나라01 0.180365 다짐 0.179952 공정02 0.172756 상식06 0.157493 후보04 0.152771 약탈 0.140487 당심에 막힌 홍준표의 두 번째 도전…“경선서 국민 관심 끌어온 게 내 역할” 홍01 0.288711 의원05 0.250445 경선08 0.197312 도전04 0.187441 역할 0.182526 세대02 0.180847 윤01 0.149584 마지막 0.145998 총장01 0.136150 중순 0.132053 민주당, 윤석열 후보 확정에 의혹·실언 거론하며 “정책·비전 선거” 제안 후보04 0.315293 의혹 0.280119 정책02 0.203595 수사18 0.203595 민주당 0.187165 윤01 0.185772 축하 0.175173 비전06 0.175173 중심01 0.155191 대변인 0.144918 문 대통령, 체코 등 4개국과 정상회담서 ‘원전 협력’ 강조…탈원전 정책 역행 원전04 0.623669 체코 0.205854 문01 0.178851 폴란드 0.176447 회담 0.176447 정상11 0.157953 총리01 0.155917 대통령 0.154182 기술01 0.147039 헝가리 0.147039 유승민 “깨끗이 승복하겠다”···백의종군 선언 경선08 0.338128 승리 0.330845 패배 0.317707 의원05 0.268237 지지자 0.211805 승복02 0.166795 문05 0.158157 결과02 0.157122 일01 0.154956 대선80 0.150343 이재명 “대선이 정책과 비전으로 미래 열어가는 장이 되길” 정의당 “고발 사주와 대장동…누가 덜 나쁜지 경쟁은 안 돼” 후보04 0.364681 축하 0.334874 윤01 0.248031 대변인 0.211075 미래02 0.183283 대선80 0.178533 수석02 0.158307 대출03 0.158307 의혹 0.152999 수사18 0.148270 청와대 ‘요소수 대응 TF’ 가동··· 매일 비상점검 요소04 0.403954 운영03 0.315363 대응02 0.297735 외교01 0.242372 수급02 0.188388 점검 0.188388 수석02 0.178641 수석 0.161582 청와대 0.155120 체계03 0.145901 송영길 “윤석열, 국민과 언론 앞에 성실하게 답변하라” 송01 0.438483 성실02 0.255614 의혹 0.234262 수사18 0.227021 기대03 0.219745 답변 0.219242 대표 0.191708 후보04 0.186125 윤01 0.172622 경쟁 0.170266
文字コード関連のエラーが出ますが,だいたいうまくいっているようです.경향신문以外の記事を使う場合,このエラーは出ませんでした.
大統領選挙(대선)が近づいて,各党の候補者選び(경선)が熱を帯びていることもあり,それぞれの記事で取り扱われている候補者(후보)の名前(윤, 홍, 유, 안, 김, …)などが上位にきていることが分かります.
RとRMeCabで扱う場合,別途Perlなどで字母に分解したりする必要がありましたが,データの取得から文字列の処理,形態素解析,計算まで一つの言語で完結するのは便利です.