2011/06/26

mecab-pythonを使って、RSSの中に含まれる名詞の集計表を作るソースを試してみました。



mecab-pythonを使って、何か面白いサンプルは無いかなと探していたら、下記の記事を見つけました。

RSSフィードから単一品詞の単語別出現数を取得してみた.
http://d.hatena.ne.jp/r_e10/20110420/1303326055


面白そうなので、試してみました。今回は、自宅のサーバのPython2.6(fedoraの古いバージョン)で試します。

出来た集計表が下記のものです。

RSSの中に含まれる名詞を取り出し、その名詞の出現数を集計したCSVの表を作成するものです。


しかし、なかなか上手くいきませんでした。起こったエラーをまとめます。

エラーその1 そもそもRSSのフィードが読めない

「Failed to parse feed」というエラーが出ます。Yahooの提供するものであれば、問題なく読めるのですが、それ以外は、「Failed to parse feed」となります。各feedの構成を調べれば、解決するのかもしれませんが、現時点では、YahooのRSSだけで動作させてみます。

エラーその2 UnicodeEncodeError

色々なUnicode系のエラーが出ました。色々な対応を試しすぎて、結局、どれが有効なのか、はっきりとは分かっていません。まず、下記のサイトの記述を参考に、mecab-ipadic(辞書)のインストールをし直しました。

Python による日本語自然言語処理
http://nltk.googlecode.com/svn/trunk/doc/book-jp/ch12.html
MeCabの文字コードはデフォルトのEUC-JPからUTF-8に変更
Unix環境においては、辞書をインストールする際に、

% ./configure --with-charset=utf8
% make
% su
# make install

ソースコードの修正

analysis_rss_feed.py
本体のファイル
#coding:utf-8
import re
import csv
import MeCab
import feedparser
from extractKeyword  import extractKeyword


#=====================
# ----- set URLs -----
#=====================

"""
# IT関連
URLs = ['http://headlines.yahoo.co.jp/rss/itmedia_ait.xml',
        'http://headlines.yahoo.co.jp/rss/bcn.xml',
        'http://headlines.yahoo.co.jp/rss/impress_c_sci.xml',
        'http://headlines.yahoo.co.jp/rss/rbb.xml',
        'http://headlines.yahoo.co.jp/rss/scan.xml']
"""

# 雑誌関連
URLs = ['http://zasshi.news.yahoo.co.jp/rss/sspa.xml',
        'http://zasshi.news.yahoo.co.jp/rss/sasahi.xml',
        'http://zasshi.news.yahoo.co.jp/rss/shincho.xml',
        'http://zasshi.news.yahoo.co.jp/rss/sbunshun.xml',
        'http://zasshi.news.yahoo.co.jp/rss/chuokou.xml',
        'http://zasshi.news.yahoo.co.jp/rss/playboyz.xml',
        'http://zasshi.news.yahoo.co.jp/rss/gendaibiz.xml',
        'http://zasshi.news.yahoo.co.jp/rss/bshunju.xml']

#===============================
# ----- category selection -----
#===============================
class_num = 0
word_classes = [u'名詞',u'動詞',u'形容詞',u'副詞',u'助詞',u'助動詞']
word_class = word_classes[class_num]

#==============================
# ----- defined functions -----
#==============================
def getwordcounts(url):
    """RSSフィードのタイトルと、単語の頻度のディクショナリを返す"""
    # フィードをパースする
    d = feedparser.parse(url)
    wc={}
    
    # すべてのエントリをループする
    # RSSの種類によって,記事に相当する部分が異なることに対応する
    for e in d.entries:
        if 'summary' in e: summary = e.summary

        #  add for yahoo.co.jp_rss
        if 'summary_detail' in e: summary = e.summary_detail

        elif 'description' in e: summary = e.description
        else: summary = e.link

        # 単語のリストを取り出す
        words = getwords(e.title+' '+summary)
        # extractKeywordを用いるため,単語を連結してテキスト化
        txt = ''
        for i in range(len(words)):
            txt += words[i]
        words_sub = extractKeyword(txt,word_class)
        
        for word in words_sub:
            # dict型のkeyにwordがなければ,value=0として新設
            wc.setdefault(word,0)
            wc[word] += 1
    return d.feed.title,wc

def getwords(html):
    """すべてのHTMLタグを取り除き,単語リストを返す"""

    # htmlから'<[^>]+>'を''に置き換える
    txt = re.compile(r'<[^>]+>').sub('',html)
    tagger = MeCab.Tagger('-Owakati')
    txt = txt.encode('utf-8')
    return tagger.parse(str(txt)).split(' ')

def main():
    apcount = {}
    wordcounts = {}
    feedlist = [line for line in URLs]
    for feedurl in feedlist:
        wc = {}
        try:
            title,wc_ini = getwordcounts(feedurl)

            for w,bc in wc_ini.items():

                # csvで日本語を表示させるため,Shift_JIS化
                # この部分はエンコードをかけると、どの文字コード
                # でも、エラーになりました。
                # wc.setdefault(w.encode('sjis'),bc)

                wc.setdefault(w,bc)

            wordcounts[title] = wc

            # for word,count in wc.items():
            for word,count in wc.items():
                apcount.setdefault(word,0)
                if count>1:
                    # それぞれの単語が出現するフィードの数を数える
                    apcount[word]+=1
        except:
            # urlが認識できなければエラー表示
            print 'Failed to parse feed %s' % feedurl
    
    # でたらめな単語が存在するフィードがあることや
    # 一般的な単語の除外を考慮し,単語の出現率に敷居値を設定する
    wordlist = []
    for w,bc in apcount.items():
        frac = float(bc)/len(feedlist)
        if frac>0.1 and frac<0.5: wordlist.append(w)
    
    # csvへの書き込み用リストを生成
    header = ['Feed_name']+wordlist
    rows = []
    for blog,wc in wordcounts.items():
        # row = [blog.encode('sjis')]
        row = [blog.encode('utf-8')]
        for word in wordlist:
            if word in wc: row.append(wc[word])
            else: row.append('0')
        rows.append(row)
    
    # csvへ書き込み
    csv_writer = csv.writer(open('rss_data.csv','w'),lineterminator='\n')
    csv_writer.writerow(header)
    csv_writer.writerows(rows)    
    
if __name__ == "__main__":
    main()
修正したのは、下記の3箇所
#  add for yahoo.co.jp_rss
if 'summary_detail' in e: summary = e.summary_detail

# wc.setdefault(w.encode('sjis'),bc)
wc.setdefault(w,bc)

# row = [blog.encode('sjis')]
row = [blog.encode('utf-8')]
extractKeyword.py テキストを形態素解析して、名詞のみのリストを返すためファイル
#coding:utf-8
import MeCab

#=====================
# ----- set text -----
#=====================
test_txt = u'PythonからMeCabの形態素解析機能を使ってみました。'

#===============================
# ----- category selection -----
#===============================
class_num = 0
word_classes = [u'名詞',u'動詞',u'形容詞',u'副詞',u'助詞',u'助動詞']
word_class = word_classes[class_num]

#==============================
# ----- defined functions -----
#==============================

def extractKeyword(text,word_class):
    """textを形態素解析して、名詞のみのリストを返す"""
    
    tagger = MeCab.Tagger('-Ochasen')
    # node = tagger.parseToNode(text.encode('utf-8'))
    node = tagger.parseToNode(text)
    keywords = []
    while node:
        # if node.feature.split(",")[0] == word_class:
        if node.feature.split(",")[0] == word_class.encode('utf-8'):
            keywords.append(node.surface)
        node = node.next
    return keywords

def main():
    keywords = extractKeyword(test_txt)
    for w in keywords:
        print w,

if __name__ == "__main__":
    main()
修正したのは、下記の2箇所
# node = tagger.parseToNode(text.encode('utf-8'))
node = tagger.parseToNode(text)

# if node.feature.split(",")[0] == word_class:
if node.feature.split(",")[0] == word_class.encode('utf-8'):

0 件のコメント:

人気の投稿 (過去 30 日間)