はてなキーワードしりとりという物を作ってみました

引き続きPythonの勉強中です。
今回はurllibを使ってみたいと思い「はてなキーワードしりとり」なる物を作ってみました。
要ははてなキーワードに登録されている単語のみ使えるという単なるしりとりです。

ルール
  • 使える単語は「はてなキーワード」に登録されている単語のみ
  • 前の単語の「読み仮名」の最後の文字から始まる単語のみ使える(しりとり基本ルール)
  • 最後に「ん」がついたら終わり(同上)
  • 同じ単語は二度以上使えない(同上)
  • 「ー」で終わる単語はその前の文字を次の単語の頭文字とする
  • 「ゃ」「ゅ」「ょ」「を」「っ」で終わる単語はそれぞれ「や」「ゆ」「よ」「お」「つ」を次の単語の頭文字とする
  • しりとりは本来複数人で対戦するものですが今回は一人で行う形にします
仕組みを考える

はてなキーワードには問い合わせた単語を検索してXMLで返してくれるAPIが用意されているのでそれを使うことにします。しりとり基本ルールである「前の単語のおしりの文字から始まる単語のみ」と「"ん"がついたら終わり」を実現するには各単語の「読み仮名」を判別することが必要ですが、はてなキーワードAPIは"hatena:furigana"というタグで読み仮名もあわせて教えてくれるためそれを使用することにします。

「同じ単語は二度以上使えない」ルールを実現するために使用済みの単語をリストに登録しておき、単語が入力されたら都度リストと比較して未使用の場合のみAPIに問い合わせることにします。またこれ以外に「APIに問い合わせたがはてなキーワードに登録されていなかった単語」のリストも作成していき、何度も同じ単語でAPIに問い合わせることがないようにします。この2つのリストをあわせたクラスを作って管理することにします。

コード

以上をふまえ、とにかく動くように書いたコードが以下です。

# coding: utf-8

import urllib
import urllib2
from xml.dom.minidom import parse

def askHatena(word):
    """wordをはてなキーワードAPIに問い合わせる
       成功すれば受け取ったファイルライクオブジェクトを返す
       失敗ならNoneを返す
    """
    #URLエンコード
    encodedWord = urllib.quote(word.encode("utf-8"))
    
    #URLを作成
    requestURL = "http://search.hatena.ne.jp/keyword?word=" + encodedWord +"&mode=rss&ie=utf8&page=1"
    
    try:
        return urllib2.urlopen(requestURL, None)
    except urllib2.URLError:
        print "urlopen error"
        return None


def xmlParser(xmlData, word):
    """受け取ったxmlDataをパースして問い合わせた単語wordが登録されていたかどうかを確認する
       xmlで1番目のitem要素内のtitleがwordと一致していれば「ある」と判断し、その
       hatena:furiganaタグの内容を返す
       「ない」場合はFalseを返す
       wordとfuriganaはunicode
    """
    try:
        dom = parse(xmlData)
    except:
        print u"エラー"
        print u"xmlデータを正しく受信できませんでした。他の単語を試してください。"
        return False
    
    items = dom.getElementsByTagName("item")
    hatenaNS = "http://www.hatena.ne.jp/info/xmlns#" #XMLのNS
    
    if items and (items[0].getElementsByTagName("title"))[0].firstChild.data == word:
        return (items[0].getElementsByTagNameNS(hatenaNS, "furigana"))[0].firstChild.data
    else:
        return False

def inputReader(text):
    """入力を受け取ってunicodeオブジェクトにして返す"""
    
    word = raw_input(text)
    
    return unicode(word, "utf-8")

class UsedWord:
    """一度でもAPIに問い合わせた単語を管理する"""
    def __init__(self):
        """usedWordsは一度使用した単語のリスト
           unqualifiedWordsは問い合わせた結果はてなキーワード
           に登録されていなかった単語のリスト
        """
        self.usedWords = []
        self.unqualifiedWords = []
    
    def contain(self, word):
        """wordがいずれのリストにも存在しなければFalseを返す
           usedWordsにあればそのインデックスに+1した数値を返し
           unqualifiedWordsにあればTrueを返す
        """
        if word in self.usedWords:
            for i, v in enumerate(self.usedWords):
                if v == word:
                    return i + 1
                
        elif word in self.unqualifiedWords:
            return True
        
        return False
    
    def add(self, word, isUsed):
        if isUsed:
            self.usedWords.append(word)
        else:
            self.unqualifiedWords.append(word)
        

def main():
    usedWord = UsedWord()
    turn = 1
    initialLetter = u"は" #しりとりの最初の文字
    
    print (u"最初のもじは「" + initialLetter + u"」です")
    
    while True:
        wordU = inputReader(u"次の単語を入力してください。次は" + str(turn) + u"語目です。")
        
        #wordUが既に使った単語、または問い合わせたことがある単語ではないかをusedWordで調べる
        if usedWord.contain(wordU):
            usedNum = usedWord.contain(wordU)
            
            if usedNum is not True:
                print u"その単語は" + str(usedNum) + u"番目に使いました。" 
            else:
                print u"その単語は登録されていませんでした。"
            
            continue
        
        #はてなキーワードAPIに問い合わせる
        xmlInfo = askHatena(wordU)
        
        if not xmlInfo:
            print u"はてなキーワードAPIへの接続に失敗しました。"
            break
        
        #xmlを読む
        furigana = xmlParser(xmlInfo, wordU)
        
        if not furigana:
            print u"この単語ははてなキーワードに登録されていませんでした。"
            usedWord.add(wordU, False)
            continue
        
        if not furigana.startswith(initialLetter):
            print u"次の文字は「" + initialLetter + "」です!"
            continue
        
        if furigana.endswith(u"ん"):
            print u"「ん」がついたよ。"
            break
        
        print wordU
        usedWord.add(wordU, True)
        
        if furigana.endswith("ー"):
            initialLetter = furigana[-2:-1]
        else:
            initialLetter = furigana[-1:]
        
        toRep = {u"ゃ":u"や",u"ゅ":u"ゆ",u"ょ":u"よ",u"を":u"お",u"っ":u"つ"}
        
        if initialLetter in toRep.keys():
            initialLetter = toRep[initialLetter]
        
        turn += 1
    print u"終了です"

if __name__ == "__main__":
    main()

実行すると次のように動作します(Eclipseのコンソール)

最初のもじは「は」です
次の単語を入力してください。次は1語目です。はてなダイアリー
はてなダイアリー
次の単語を入力してください。次は2語目です。Linux
Linux
次の単語を入力してください。次は3語目です。はてな
次の文字は「す」です!
次の単語を入力してください。次は3語目です。数字
数字
次の単語を入力してください。次は4語目です。定規
この単語ははてなキーワードに登録されていませんでした。
次の単語を入力してください。次は4語目です。JIN
「ん」がついたよ。
終了です

今回はクラスを使おう!と思っていたので使用済みの単語管理にUsedWordというクラスを作ったのですがインスタンスを1つしか作らないのでクラスをつかう意味があんまりなかったなーと思っています。単なるリスト2つでも別に問題なかった(^_^;)
XMLを解析するxmlParserはなかなかやり方が分からなくてすごく時間がかかったんですけど分かってみると別に難しいことは何もなくて取り立てて書くことがないという・・・。
main()にいろいろ書きすぎという気がしているのでその辺りから直していこうかなと思います。