へぬもへメモ

https://twitter.com/henumohe

Webstemmer使おうとして引数エラーと文字コードに悩まされる

Webページの本文を抽出したくて、MOONGIFTで見つけたWebstemmerを使おうと思ったら、いろんな障害にぶつかった。Python歴3日なので、ソース読むのも一苦労。

エラー1

『ステップ1. 学習するためのHTMLページを取得する』をやろうとしたら早速エラーが。

C:\Python27\Lib\site-packages\webstemmer> textcrawler.py -o asahi http://asahi.com/
Writing: 'asahi.201107111850.zip'
Traceback (most recent call last):
  File "C:\Python27\Lib\site-packages\webstemmer\textcrawler.py", line 444, in <module>
    if __name__ == '__main__': main()
  File "C:\Python27\Lib\site-packages\webstemmer\textcrawler.py", line 436, in main
    debug=debug).run()
  File "C:\Python27\Lib\site-packages\webstemmer\textcrawler.py", line 356, in __init__
    cookie_file, acldb, urldb, default_charset, delay, timeout, debug)
  File "C:\Python27\Lib\site-packages\webstemmer\textcrawler.py", line 113, in __init__
    self.robotstxt.read()
  File "C:\Python27\lib\robotparser.py", line 57, in read
    f = opener.open(self.url)
  File "C:\Python27\lib\urllib.py", line 205, in open
    return getattr(self, name)(url)
  File "C:\Python27\lib\urllib.py", line 342, in open_http
    h.endheaders(data)
TypeError: endheaders() takes exactly 1 argument (2 given)

エラーの意味は「引数2つあるよ!1つにしてよ!」らしい。(参考:『初めてのPython』 IV部 演習問題 - ケーズメモ)
試しにendheaders()の元であるhttplib.pyの中身を覗くと、

class HTTPConnection:
  ...
  def endheaders(self, message_body=None):
    ...

となってる。引数がちゃんと2つあるのでこれはおかしい。
試行錯誤の結果、httplib.pyがそこかしこのフォルダに存在していることがわかったため、まずimportの優先順位を確認する。

import sys
sys.path

結果がこう。

['C:\\Python27\\Lib\\site-packages\\webstemmer', 'C:\\Python27\\Lib\\site-packages', 
 'C:\\Windows\\system32\\python27.zip', 'C:\\Python27\\DLLs', 'C:\\Python27\\lib', 
 'C:\\Python27\\lib\\plat-win', 'C:\\Python27\\lib\\lib-tk', 'C:\\Python27']

で、C:\Python27\Lib\site-packages\webstemmerの中を見ると、しっかりhttplib.pyが入ってる。こっちのendheaders()を確認すると、

class HTTPConnection:
  ...
  def endheaders(self):
    ...

引数が1つしかない。ようやく原因特定。
呼び出し先の引数を増やしても、呼び出し元の引数を減らしても一応動いたけど、引数減らすのはちょっと怖いので、webstemmerフォルダのC:\Python27\lib\httplib.pyに書き換えた。よく分からないしこれ以上触りたくない。

エラー2

『ステップ4. 学習したパターンを使って本文を抽出する』を実行し、やっと終わったと思いきや。

C:\Python27\Lib\site-packages\webstemmer>extract.py -C euc-jp asahi.pat asahi.201107111623.zip
PATTERN: 201107111602/www.asahi.com/digital/av/TKY201106280357.html
MAIN-6: 召ツ宵エ。&#57817;・将ツ。・。・娼。将苡ェイ召エ宵ョ将」召&#57422;ェツ。&#57798;ェ。。&#57798;ェゥ。&#57798;ェメ。&#57798;ェ。\将「\宵ェ\召エ召ツ娼ャ。&#57798;ェ。。&#57798;ェゥ。&#57798;ェメ。&#57798;ェ。召ツ娼ホ。&#57798;ェ。。&#57798;ゥテ。&#57798;ゥテ。&#57798;ォタ。&#57798;ォゥ \。&#57560;宵ェ\。゚\将ウ召&#57422;ェチ召&#57422;「・\召・召&#57422;ェョ召・召・召ツ宵繽「・召&#57422;ォア宵イ。ュ召ト将イ召・召&#57422;「ト???将モ召&#57422;ェョ召・召ツ宵ウ

文字化け!これは辛い。文字コード周りについて一から調べることになってしまった。
色々調べて分かったことは、

  • 元の文字列はUnicode文字列
  • EUC-JP,shift-jis,utf-8などひと通りエンコード試したけど全て文字化け
  • PyhtonはUnicode文字列を直接表示できない
    • 直接表示しようとすると"UnicodeEncodeError: 'cp932' codec can't encode character u'\xa1' in position 0:illegal multibyte sequence"
  • try-except文を使って1文字ずつ↑のエラー吐かせてみた
    • u'\xa1' u'\xca' u'\xa3' u'\xb6' u'\xb7' u'\xee' u'\xa3' u'\xb2' ...
    • Unicode対応 文字コード表EUC文字コードを2バイトずつ確認すると、"a1ca,a3b6,b7ee,a3b2"→"(,6,月,2"
    • 元記事の本文始めは「(6月29日発売のAERAムック『AERA×Apple アップルはお好きですか』から抜粋した記事です)」
      • 合ってるし!

ここまでやって今日は諦めた。文字コード自体はeuc-jpで合ってるっぽいので、文字列の扱い方が良くないんだろうか?なんとか明日中に解決したい。