LoginSignup
3
4

More than 3 years have passed since last update.

pythonでSFTP通信したら地味にハマった話

Posted at

概要

海外のライブラリはエンコーディングの考えが適当なものがあるから注意って話。

トラブル内容

業務上、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飛ばしたいなぁ

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4