概要
海外のライブラリはエンコーディングの考えが適当なものがあるから注意って話。
トラブル内容
業務上、pythonでsftpを触る必要が出たからparamikoを使用し、ファイルをFTPサーバから直接ダウンロードして中身のテキストファイルを統計処理にかけようとした。
APIのドキュメントでは「file()関数はpythonのfileと同じ用に使えるようにしてあるよ」ってあったので適当にパスなどを指定したら下記のエラーが出た。
UnicodeDecodeError: 'utf-8' codec can't decode byte ~~ in position ~~: invalid start byte
コードは下記のような感じ。
client = paramiko.SSHClient()
client.connect(適当な接続情報)
sftp_connection = client.open_sftp()
with sftp_connection.open(ファイルパス) as f
for line in f:
print(line)
これを行うとfor文の付近でUnicodeDecodeErrorが出た。
原因
一言で言ってしまえば、「ファイルの中身を取得するときにエンコードを指定できないから」であった。ANSIでエンコーディングされたテキストファイルをUTF-8で読もうとしてしまっていてエラーが起きている。
ソースを見てみたが、現時点ではopen時にエンコーディングを指定することができない。標準の入力では出来てるんだけど。
回避策
ファイルの中身を英語に書き換えることも、utfで作り直させることもできなさそうなため、今回はバイナリで開いてANSIで別途エンコーディングして読ませることにした。
具体的には、上記のコードのwith文以下を次のようなイメージで書き直した。
(ある程度標準入力に似せているのはsftpじゃなくてローカルでデバッグできるようにしたかったため。当然通信が無いほうがデバッグにかかる手間は少なくて済む。)
import codecs
for line in readlines():
print(line)
def readlines():
file f = sftp_connection.open(ファイルパス, "rb")
return codecs.encode(f.read(), "ANSI").split("\r\n")
openの2つ目の変数に"rb"を指定することで、バイナリとしてファイルを読み込む。
それを読み込みたいエンコードで読み直してから、行を返すようにしている。
あまりにもファイルが巨大だった場合は問題が出そうだが、実用上問題なさそうだったので今回はこれでOKとした。
根本原因
今回の問題の根本原因はparamikoのBufferedFileにエンコードを指定させることだと思う。
しっかり読み込むことができたらいい機会だしparamikoのgithubの方にpull request飛ばしたいなぁ