Python2.x から Python3.x への乗り換えで日本語処理でハマったのでメモ。
スポンサードリンク
レンタルサーバなどで試す場合には、自分のローカル環境にインストールします。
mkdir -p $HOME/local/src/ cd $HOME/local/src/ wget http://python.org/ftp/python/3.2.2/Python-3.2.2.tgz tar zxvf Python-3.2.2.tgz cd Python-3.2.2 ./configure --help ./configure --prefix=$HOME/local/ make make install
Python3をインストールすると「2to3」という実行ファイルもインストールされるのが分かると思います。
これはPython2のソースコードをPython 3でも動くように可能な限り自動変換してくれるツールです。また「2to3」は関数の呼び出しも修正してくれます。
例えば、次のような変換を自動的に行なってくれます。
【参考】http://diveintopython3-ja.rdy.jp/porting-code-to-python-3-with-2to3.html
Python 2 | Python 3 |
---|---|
import urllib | import urllib.request, urllib.parse, urllib.error |
import urllib2 | import urllib.request, urllib.error |
import urlparse | import urllib.parse |
import robotparser | import urllib.robotparser |
from urllib import FancyURLopener from urllib import urlencode | from urllib.request import FancyURLopener from urllib.parse import urlencode |
from urllib2 import Request from urllib2 import HTTPError | from urllib.request import Request from urllib.error import HTTPError |
Python 3では、print()は関数となり、print()に引数として渡す必要があるなど修正箇所が多いので「2to3」は強力です。
Python2で書かれたソースコードです。urllib2を用いてURL先のソースを取得しています。
#!/local/bin/python # -*- coding: utf-8 -*- import sys import threading, urllib2 f = open('hoge.txt', 'a') opener = urllib2.build_opener() httpres = opener.open('http://headlines.yahoo.co.jp/cm/personal') html = httpres.read() print html f.write(html) f.close()
実行結果は次のようになります。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html lang="ja"> ...(省略)
これを「2to3」を利用すると差分パッチが作成されます。
%2to3 sample.py RefactoringTool: Skipping implicit fixer: buffer RefactoringTool: Skipping implicit fixer: idioms RefactoringTool: Skipping implicit fixer: set_literal RefactoringTool: Skipping implicit fixer: ws_comma --- sample.py (original) +++ sample.py (refactored) @@ -2,14 +2,14 @@ # -*- coding: utf-8 -*- import sys -import threading, urllib2 +import threading, urllib.request, urllib.error, urllib.parse f = open('hoge.txt', 'a') -opener = urllib2.build_opener() +opener = urllib.request.build_opener() httpres = opener.open('http://headlines.yahoo.co.jp/cm/personal') html = httpres.read() -print html +print(html) f.write(html) f.close() RefactoringTool: Files that need to be modified: RefactoringTool: sample.py
「2to3」で修正された内容で実際に動作させてみましょう。
#!/local/bin/python # -*- coding: utf-8 -*- import sys import threading, urllib.request, urllib.error f = open('hoge.txt', 'a') opener = urllib.request.build_opener() httpres = opener.open('http://headlines.yahoo.co.jp/cm/personal') html = httpres.read() print(html) f.write(html) f.close()
実行すると、次のようなエラーが出ます。
%$HOME/local/bin/python3 sample.py > hage.txt Traceback (most recent call last): File "sample.py", line 14, inf.write(html) TypeError: must be str, not bytes
また print() 関数で出力した結果にも「b'」が付き、「\xa5\xb2\xa5\xb9\xa5\xc8\xa4\xb5\xa4\xf3\」と日本語部分が文字化けしています。
b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> \n<html lang="ja">\n
これは Python3より、取得したHTMLデータはバイナリとなったためです。
このため、print すると、b'..........' という bytes 表現となってしまいます。
対応方法として、デコード・エンコード処理が必要です。
文字列 → バイナリ bytes = str.encode('utf-8など') バイナリ → 文字列 str = bytes.decode('utf-8など')
「'utf-8'」以外には「'shift-jis'」「'euc-jp'」「'iso2022-jp'」などがあります。
ここで、試しに「'utf_8'」としてみます。
【修正前】html = httpres.read() 【修正後】html = httpres.read().decode('utf-8')
実行結果は次のようなエラーが出ます。
%$HOME/local/bin/python3 sample.py > hage.txt Traceback (most recent call last): File "sample.py", line 11, inhtml = httpres.read().decode('utf-8') UnicodeDecodeError: 'utf8' codec can't decode byte 0xa5 in position 328: invalid start byte
指定したURLのサイトは「'euc-jp'」で記載されているので、正しいコーデックを指定する必要があります。
このソースコードをPython3上で実行してみます。
#!/local/bin/python # -*- coding: utf-8 -*- import sys import threading, urllib.request, urllib.error f = open('hoge.txt', 'a') opener = urllib.request.build_opener() httpres = opener.open('http://headlines.yahoo.co.jp/cm/personal') html = httpres.read().decode('euc-jp') print(html) f.write(html) f.close()
実行結果は次のようなエラーが出力されます。
%$HOME/local/bin/python3 sample.py > hage.txt Traceback (most recent call last): File "sample.py", line 13, inprint(html) UnicodeEncodeError: 'ascii' codec can't encode characters in position 328-339: ordinal not in range(128)
これは、デフォルトで設定されてる文字コードを変えると解決します。
%$HOME/local/bin/python3 Python 3.2.2 (default, Sep 26 2011, 17:16:07) [GCC 4.2.1 20070719 [FreeBSD]] on freebsd7 Type "help", "copyright", "credits" or "license" for more information. >>> import sys >>> print(sys.stdout.encoding) US-ASCII
確認すると「US-ASCII」のようなので次のように修正
#!/local/bin/python # -*- coding: utf-8 -*- import sys import threading, urllib.request, urllib.error import io # 追加 sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') # 追加 f = open('hoge.txt', 'a') opener = urllib.request.build_opener() httpres = opener.open('http://headlines.yahoo.co.jp/cm/personal') html = httpres.read().decode('euc-jp') print(html) f.write(html) f.close()
これで UTF-8 形式で hage.txt ファイルが出力されました。
上記のソースコードでは、ファイルへの書き出しで次のようなエラーが出ます。
%$HOME/local/bin/python3 sample.py > hage.txt Traceback (most recent call last): File "sample.py", line 16, inf.write(html) UnicodeEncodeError: 'ascii' codec can't encode characters in position 328-339: ordinal not in range(128)
Python3では、デフォルトがunicode形式のため、ファイル作成時に「'ecu-jp'」を指定してあげる必要があります。
スポンサードリンク
#!/local/bin/python # -*- coding: utf-8 -*- import sys import threading, urllib.request, urllib.error import io sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') f = open('hoge.txt', 'a', encoding='euc-jp') opener = urllib.request.build_opener() httpres = opener.open('http://headlines.yahoo.co.jp/cm/personal') html = httpres.read().decode('euc-jp') print(html) f.write(html) f.close()
これで、エラーが出力される事なく処理が完了しました。
decode("utf-8") などの処理が必要です。bytes コードを文字列として処理しようとしているのが原因です。
HTMLソースの中に、javascriptで</scrとipt>をくっつけて</script>のようにする箇所があるのが原因。
document.write('<scr'); document.write('ipt src="' + impAserver + '/bservers/AAMALL/ACC_RANDOM=' + impArnd + '/PAGEID=' + impApid + impAtarget + '">'); document.write('</scr'); document.write('ipt>');
これは、BeautifulSoupやHTMLParserを利用していると発生します。
以前は、SGMLParserを利用して回避することができましたが、Pythob3ではSGMLParserは無くなりました。
この対策として、 Python-3/Lib/html/parser.py のソースコードを overrideする事が考えられます。
import html.parser HTMLParser.endtagfind = re.compile("</\s*([a-zA-Z][-.a-zA-Z0-9:_+'\" ]*)\s*>")
スポンサードリンク