はてなキーワードしりとりという物を作ってみました
引き続き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()にいろいろ書きすぎという気がしているのでその辺りから直していこうかなと思います。