ナード戦隊データマン

データサイエンスを用いて悪と戦うぞ

アメブロの芸能人ブログをTopic Modelingで分類する

トピックモデリングとは、教師なし学習の一つで、文書を分類する手法です。ここでは、アメブロ芸能人ブログを分類しますが、結果としてはとても面白い分類結果になったので紹介します。

seleniumとPhantomJSを用いてスクレイピング

まず、芸能人ブログのURLをブログランキングから取得したいのですが、このページはどうやらJavaScriptによって表示しているようです。そのため、通常のurllib.requestを使う方法ではスクレイピングできません。そこで、PhantomJSを使います。

In[1]:

import urllib.request, urllib
from bs4 import BeautifulSoup
import time
from selenium import webdriver
import re
import pandas as pd

rss_urls = []

for i in range(17):
    print("Page:"+str(i))
    page = i+1
    url = "https://official.ameba.jp/rankings/total?pageNo="+str(page)
    driver = webdriver.PhantomJS()
    driver.get(url)
    soup = BeautifulSoup(driver.page_source, "html5lib")
    cites = soup.find_all("a", class_="ranking-list-title-link")
    for cite in cites:
        rss_urls.append(re.sub(r'https://ameblo.jp/(.+)/', r'http://rssblog.ameba.jp/\1/rss20.xml', cite['href']))
        
rss_urls_df = pd.DataFrame(rss_urls, columns=["rss"])
rss_urls_df.to_csv("rss_data_ameblo.csv", index=False)

まず、芸能人ブログのURLをPhantomJSで取得し、次いでrssのURLに変換します。何か問題が生じるとまずいので、結果は保存しておきます。

注意: スクレイピングの挙動をアメーバのサーバサイドで検出される可能性があります。その場合、IPをブロックするなどの対処をされる可能性があり、スクレイピングが失敗するかもしれません。

ブログのコンテンツを取得

次に、ブログコンテンツを取得します。RSSのdescription部分の日本語を収集します。

In[2]:

texts = []
for rss_url in rss_urls:
    text = ""
    try:
        req = urllib.request.Request(rss_url)
        xml = urllib.request.urlopen(req)
        soup = BeautifulSoup(xml, "lxml-xml")
        descs = soup.find_all("description")
        for desc in descs:
            tmp = re.sub(r'[a-zA-Z0-9<>\-_\./=\"\':]',"", desc.text)
            tmp = re.sub(r'続きを[見み]る', "", tmp)
            tmp = re.sub(r'『著作権保護のため、記事の一部のみ表示されております。』', "", tmp)
            tmp = re.sub(r'[  ]', "", tmp)
            tmp = re.sub(r'\n', "", tmp)
            text = text + tmp
    except:
        pass
    texts.append(text)

texts_df = pd.DataFrame(texts, columns=["text"])

out = pd.DataFrame()
out["url"] = rss_urls_df
out["text"] = texts_df
out.to_csv("ameblo_tests.csv")

この取得したテキストを、トークナイズします。

In[3]:

import tinysegmenter
segmenter = tinysegmenter.TinySegmenter()
texts_fmt = []
for text in texts:
    texts_fmt.append(' '.join(segmenter.tokenize(text)))
out["text_fmt"] = texts_fmt

Latent Dirichlet Allocationでトピックを探す

それでは、前処理は完了したのでトピックモデリングを実行します。ここでは、どのトピックがどういう言葉を含んでいるのかを出力することを目標にします。

In[4]:

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.decomposition import LatentDirichletAllocation
vect = CountVectorizer(max_features=10000, max_df=.15)
X = vect.fit_transform(out["text_fmt"])
lda = LatentDirichletAllocation(n_topics=6, learning_method="batch", max_iter=25, random_state=0)
document_topics = lda.fit_transform(X)

In[5]:

import mglearn
import numpy as np
sorting = np.argsort(lda.components_, axis=1)[:, ::-1]
feature_names = np.array(vect.get_feature_names())
mglearn.tools.print_topics(topics=range(6), feature_names=feature_names, 
                           sorting=sorting, topics_per_chunk=5, n_words=15)

この最終出力は以下のようになります。

topic 0       topic 1       topic 2       topic 3       topic 4       
--------      --------      --------      --------      --------      
握手            あなた           息子            コーデ           レシピ           
確認            意識            ママ            ニット           野菜            
受付            エネルギー         パパ            バッグ           焼き            
チケット          神社            赤ちゃん          アイテム          かぼちゃ          
本人            講座            ヶ月            サイズ           はん            
公演            男性            わたし           スカート          瀬戸            
販売            潜在            学校            カラー           おかず           
客様            宇宙            登録            パンツ           簡単            
劇場            現実            読者            トップス          弁当            
申込            お金            ランチ           価格            コチラ           
当日            変え            食事            工藤            晴れ            
終了            神様            飛行            デザイン          切り            
当選            伝え            移動            コート           さじ            
akb           恋愛            旦那            アウター          チーズ           
書類            人間            ルナ子           ブラック          康史            


topic 5       
--------      
ライブ           
公演            
映画            
舞台            
やんや           
公開            
ステージ          
番組            
作品            
メンバー          
嬉しかっ          
ファン           
公式            
稽古            
動画          

ただし、mglearnは英語に対応しているため、日本語(マルチバイト文字)だと出力がガタガタしています。

考察

出力されたトピックを見ると、以下のように説明することができそうです。

トピック0: アイドルグループ
トピック1: スピリチュアル・占い
トピック2: 子育て・家庭
トピック3: ファッション
トピック4: 料理
トピック5: 舞台俳優

6トピックによる分類でも、ハッキリとブログを分類することができました。

トピック数は変更することができますが、一般にトピック数が少ないほど抽象性は上がり、トピック数が多いほど具体的なトピックとして分類されます。ここでいう「抽象性」とは、そのトピックがカバーする範囲の広さのことです。

ちなみに、どのトピックに分類されたかを見るためには以下のコードを実行します。

In[6]:

np.argmax(document_topics,axis=1)

参考

  1. http://scikit-learn.org/stable/modules/generated/sklearn.decomposition.LatentDirichletAllocation.html
  2. https://code.tutsplus.com/tutorials/headless-functional-testing-with-selenium-and-phantomjs--net-30545