V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
watermelon92
V2EX  ›  Python

[求大神解答] Django 项目部署到线上后出现编码问题

  •  
  •   watermelon92 · 2018-06-26 12:03:49 +08:00 · 3070 次点击
    这是一个创建于 2377 天前的主题,其中的信息可能已经有所发展或是发生改变。

    一、项目说明:

    用户上传一个压缩文件(内含 html 文件夹),网站解压文件并保存至指定目录,供外部直接访问静态 html 文件。

    二、问题:

    解压文件时报错:UnicodeEncodeError: 'ascii' codec can't encode characters,具体信息见链接:报错信息

    三、尝试思路

    1. 代码在本地,macos 下可解压含中文的文件。
    2. 在线上解压英文 html 文件也是正常的。
    3. 报错的代码单独在 ubuntu 下运行正常,可以创建文件。
    4. google 了所有关键词,基本都是 2.7 的解决方案,在项目里使用无效。
    5. 找到一个类似的问题,可能由 ubuntu 系统语言原因导致。原问题链接

    四、出错代码块:

    def handle_uploaded_file(f, num, new_dir_name):
        release_file_dir = os.path.join(UPLOAD_FILES_DIR, str(num))
        new_dir = os.path.join(release_file_dir,new_dir_name)
        filePath = f
        with zipfile.ZipFile(filePath, 'r') as zf:
            for fn in zf.namelist():
                right_fn = fn.encode('cp437').decode('utf-8')  # 将文件名正确编码
                right_fn = os.path.join(release_file_dir, right_fn)
    
                if right_fn[-1] == '/':
                    os.makedirs(right_fn, mode=0o777) #创建文件夹
                    continue
    
                with codecs.open(right_fn, 'w+', encoding='utf-8') as output_file:  # 创建并打开新文件
                    with zf.open(fn, 'r') as origin_file:  # 打开原文件
                        shutil.copyfileobj(origin_file, output_file)  # 将原文件内容复制到新文件
    
            renameFile(release_file_dir,new_dir)
    
            return os.path.join(release_file_dir,new_dir_name)```
    
    第 1 条附言  ·  2018-07-02 19:11:48 +08:00
    感谢各位大佬的帮助,问题已解决。现贴出解决方案供后来者参考:
    确认问题:
    是运行环境的默认编码问题,尽管在 shell 中打印出环境是 utf-8 解码,但是项目中依旧是 ascii.

    解决方案:
    服务器中配置环境参数:environment=LANG=en_US.UTF-8,LC_ALL=en_US.UTF-8
    如果是用的 uWSGI:
    可参考该链接说明配置: https://docs.djangoproject.com/en/dev/howto/deployment/wsgi/uwsgi/
    如果是用的 gunicorn:
    可使用 superviosr 来管理进程,并在 supervisor 中配置上述环境参数。

    再次感谢大佬们帮助。
    36 条回复    2018-06-27 12:22:17 +08:00
    golmic
        1
    golmic  
       2018-06-26 12:42:25 +08:00 via Android
    服务器的 python 版本是? Python 解压用的哪个库?我没理解错需求吧,就是程序解压 zip 的时候出的错
    watermelon92
        2
    watermelon92  
    OP
       2018-06-26 12:48:59 +08:00
    @golmic python3.4 解压用的 zipfile
    1130335361
        3
    1130335361  
       2018-06-26 12:55:46 +08:00
    用的是 apache 还是 nginx,我之前 django 配合 apache 的时候出现过这个问题
    watermelon92
        4
    watermelon92  
    OP
       2018-06-26 12:57:44 +08:00
    @1130335361 用的 Nginx 加 gunicorn,你之前也是到服务器上才报错吗?
    golmic
        5
    golmic  
       2018-06-26 13:02:00 +08:00 via Android   ❤️ 1
    服务器上手动跑一下解压的函数,贴一下报错信息
    1130335361
        6
    1130335361  
       2018-06-26 13:10:14 +08:00
    @watermelon92 是的,改了 apache 的的环境变量才行的
    1130335361
        8
    1130335361  
       2018-06-26 13:16:19 +08:00   ❤️ 1
    @watermelon92 拉到最底部有说“ Fixing UnicodeEncodeError for file uploads ”这个错误的解决办法,https://docs.djangoproject.com/en/dev/howto/deployment/wsgi/uwsgi/
    watermelon92
        9
    watermelon92  
    OP
       2018-06-26 13:33:35 +08:00
    @golmic 好的,我稍后试下。谢谢!
    watermelon92
        10
    watermelon92  
    OP
       2018-06-26 13:34:05 +08:00
    @1130335361 我用的是 gunicorn,好像没写过 uwsgi.ini 文件。。
    v1v
        11
    v1v  
       2018-06-26 13:47:46 +08:00   ❤️ 1
    以前 centos 也出现这个问题, 改 /etc/sysconfig/i18n,
    设置 LANG=en_US.UTF-8 , 重启服务器就好了
    watermelon92
        12
    watermelon92  
    OP
       2018-06-26 13:57:44 +08:00
    em...我的 ubuntu 系统没有 sysconfig 文件夹
    watermelon92
        13
    watermelon92  
    OP
       2018-06-26 14:03:29 +08:00
    @v1v 这是现在 locale 的结果:

    LANG=en_US.utf8
    LANGUAGE=
    LC_CTYPE="en_US.utf8"
    LC_NUMERIC="en_US.utf8"
    LC_TIME="en_US.utf8"
    LC_COLLATE="en_US.utf8"
    LC_MONETARY="en_US.utf8"
    LC_MESSAGES="en_US.utf8"
    LC_PAPER="en_US.utf8"
    LC_NAME="en_US.utf8"
    LC_ADDRESS="en_US.utf8"
    LC_TELEPHONE="en_US.utf8"
    LC_MEASUREMENT="en_US.utf8"
    LC_IDENTIFICATION="en_US.utf8"
    LC_ALL=
    freakxx
        14
    freakxx  
       2018-06-26 14:20:11 +08:00
    @watermelon92
    直接去到 21 行 log 下看名字变成了什么;

    确定编码问题后再往上处理

    楼上说的解决办法
    如果是 uwsgi + nginx 这一套,直接再 uwsgi 指定 LANG 就可以了。
    lxy42
        15
    lxy42  
       2018-06-26 14:24:21 +08:00
    right_fn = fn.encode('cp437').decode('utf-8') # 将文件名正确编码
    这里为什么需要对 fn 进行 encode 和 decode ? fn 如果可以 encode 的话,说明 fn 是 unicode 类型,就没必要先 encode 在 decode 了(最终结果还是 unicode ) 而且 encode 和 decode 的编码参数都不一致,可能会导致异常。

    说回你的问题,我猜测是 `right_fn = os.path.join(release_file_dir, right_fn)` 导致`right_fn`编码出问题了,建议你检查一下`release_file_dir` 和 `right_fn ` 的编码。
    watermelon92
        16
    watermelon92  
    OP
       2018-06-26 14:29:42 +08:00
    debug 信息里,文件名是对的。因为 zipfile 解压是默认用‘ cp437 ’ decode 的,所以我先做了解压文件的名称处理。得到正确的文件名后,用 open()函数基于解压后的文件名创建文件的。目前就是提示在这个 open()函数上出错了。但我在服务器端直接运行同样的函数是可以正常创建文件的。
    watermelon92
        17
    watermelon92  
    OP
       2018-06-26 14:32:03 +08:00
    @lxy42
    @freakxx
    两位大佬我在楼上回复了
    watermelon92
        18
    watermelon92  
    OP
       2018-06-26 14:39:07 +08:00
    @lxy42
    @freakxx
    我把 debug 出来的文件名称截图了,见下方链接:
    http://7xuagw.com1.z0.glb.clouddn.com/debug.png
    glasslion
        19
    glasslion  
       2018-06-26 15:05:47 +08:00   ❤️ 1
    @watermelon92
    目前你的写法是要求 zip 包里的所有文件都是 utf-8 编码的。
    既然你目的是复制文件,直接把文件以二进制打开,岂不是更简单?
    freakxx
        20
    freakxx  
       2018-06-26 15:12:37 +08:00
    $LANG
    freakxx
        21
    freakxx  
       2018-06-26 15:14:22 +08:00   ❤️ 1
    @watermelon92
    echo $LANG
    然后在 Gunicorn 指定用户为你当前用户
    试下
    lxy42
        22
    lxy42  
       2018-06-26 15:19:12 +08:00   ❤️ 1
    @watermelon92 #18
    right_fn 编码没问题,那可能是文件系统编码默认是 ascii 的问题,你看一下 sys.getfilesystemencoding()是什么。
    如果是 ascii 的话,
    1. 设置正确的 locale
    或者
    2. 将 right_fn 编码为 bytes:
    right_fn = right_fn.encode(ENCODING)
    with codecs.open(right_fn, 'w+', encoding='utf-8') as output_file: # 创建并打开新文件
    Tzen
        23
    Tzen  
       2018-06-26 15:20:23 +08:00
    之前遇到过这个问题,我用 supervisor 管理的进程,配置里更改环境变量就好;
    我项目里是这么加的:environment=LANG=en_US.UTF-8,LC_ALL=en_US.UTF-8
    copie
        24
    copie  
       2018-06-26 15:39:19 +08:00 via Android
    这个属于系统编码的问题。我用 docker 发布的时候如下解决的。


    echo "zh_CN.UTF-8 UTF-8" > /etc/locale.gen
    locale-gen
    ENV LANG=zh_CN.UTF-8
    copie
        25
    copie  
       2018-06-26 15:39:57 +08:00 via Android   ❤️ 1
    这个属于系统编码的问题。我用 docker 发布的时候如下解决的。

    RUN echo "zh_CN.UTF-8 UTF-8" > /etc/locale.gen
    RUN locale-gen
    ENV LANG=zh_CN.UTF-8
    robinlovemaggie
        26
    robinlovemaggie  
       2018-06-26 15:46:04 +08:00   ❤️ 1
    把字符乱码问题归结系统语言设置上是不可取的,这本身不符合 KISS 原则的。
    问题可能处在你的开发环境里“ cp437 ”字符编码不能和目标服务器兼容,处理这部分就好了,放弃修改服务器系统设置。
    freakxx
        27
    freakxx  
       2018-06-26 15:55:24 +08:00
    @watermelon92
    @copie


    尽量像
    @robinlovemaggie
    说的,
    不要去动系统层面的配置

    如果是用 uwsgi 那么就在里面指定 LANG
    如果是用 supervisor 也在里面像 @Tzen 说的,设置下
    robinlovemaggie
        28
    robinlovemaggie  
       2018-06-26 16:02:36 +08:00
    @freakxx 附议。
    @watermelon92 其实道理很简单,如果是靠更改系统设置解决了问题,那么换一个系统,问题依然存在,等于问题没解决。
    watermelon92
        29
    watermelon92  
    OP
       2018-06-26 16:26:50 +08:00
    @robinlovemaggie
    @freakxx
    谢谢指导。
    cp437 是 zipfile 的标准吧?为啥会不兼容 ubuntu ?而且我打印出来的 log 是显示正常的,楼上有截图。
    robinlovemaggie
        30
    robinlovemaggie  
       2018-06-26 16:35:03 +08:00
    @watermelon92 来自 wiki 关于 codepage437 的解释:
    Code page 437 is the character set of the original IBM PC (personal computer), or MS-DOS. It is also known as CP437, OEM 437, PC-8, or MS-DOS Latin US. The set includes ASCII codes 32 – 126, extended codes for accented letters (diacritics), some Greek letters, icons, and line-drawing symbols.
    copie
        31
    copie  
       2018-06-26 19:50:12 +08:00 via Android
    @robinlovemaggie 但是通过配置 Python 启动的环境变量确实可以成功运行。但是如果有一天代码上线后出现问题远程打开代码注释都是乱码。log 汉字这是乱码岂不是更加的闹心。
    tlday
        32
    tlday  
       2018-06-27 00:11:42 +08:00 via iPhone   ❤️ 1
    呃,我没看懂你们在说啥,但是,同样是 zip 文件,在 Windows 下和 Unix 下,文件名的编码是有差异的,这个是 zip 文件标准没有覆盖文件名编码方式的问题,应该从这个方向着手。修改系统编码方式是不可取的。
    tlday
        33
    tlday  
       2018-06-27 00:18:02 +08:00 via iPhone
    你的注释打开原文件的这一行好像也没有指定打开文件的编码方式,然后把原文件复制到新文件如果编码不同,是可以直接复制的吗…不需要 decode,encode 吗?我在 python 解决编码问题方面是个菜鸡,只是提供下思路。
    tlday
        34
    tlday  
       2018-06-27 00:29:29 +08:00 via iPhone
    感觉自己没仔细看回复闹了乌龙
    robinlovemaggie
        35
    robinlovemaggie  
       2018-06-27 09:40:51 +08:00
    @copie 远程汉字乱码可以改一下 ssh 工具的传输编码就行了
    watermelon92
        36
    watermelon92  
    OP
       2018-06-27 12:22:17 +08:00
    @golmic
    我在服务器上单独运行了,成功解压没有报错。
    demo 代码见下:
    import os,shutil
    import zipfile

    filePath = '/Users/dylan/Desktop/3.2.zip'
    release_file_dir = '/Users/dylan/Desktop/testunzip3'


    with zipfile.ZipFile(filePath, 'r') as zf:
    for fn in zf.namelist():
    right_fn = fn.encode('cp437').decode('utf-8') # 将文件名正确编码
    right_fn = os.path.join(release_file_dir,right_fn)

    if right_fn[-1]=='/':
    os.makedirs(right_fn, mode=0o777)
    continue

    with open(right_fn, 'wb') as output_file: # 创建并打开新文件
    with zf.open(fn, 'r') as origin_file: # 打开原文件
    shutil.copyfileobj(origin_file, output_file) # 将原文件内容复制到新文件
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2808 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 07:53 · PVG 15:53 · LAX 23:53 · JFK 02:53
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.