踩坑纪实:UnicodeDecodeError: 'utf-8' codec can't decode bytes in position 21-22: invalid continuation byte

发布时间 2023-03-22 21:18:31作者: Jerome12138

问题出现过程

切换新的服务器之后,使用PyExecJS库报错

...
ctx = execjs.compile(js_str)
version_obj = ctx.eval('exportObj')

报错内容:

UnicodeDecodeError: 'utf-8' codec can't decode bytes in position 21-22: invalid continuation byte

排查踩坑过程

根据报错大概可以猜测是编码解析异常,于是网上搜索+ChatGPT搜索查询原因,进行排查调试:

  1. 首先当然是“控制变量法”,原服务器是一切正常的,为什么切换新服务器就报错了呢?
    但是检查系统环境和软件环境,安装的linux版本、python版本、依赖包的版本都与旧服务器完全一致,是不是有“玄学”??

  2. 项目里的使用方式是读取js文件再用execjs解析,怀疑是不是读取文件的方式有问题,于是将原先的r方式改为rb再解码,没有任何效果;

  3. 根据报错还不知道具体是那个字符解析出错,反复换了几个js文件之后仍然没有作用,于是排除变量,排除django项目、读取文件等因素的影响,直接在python命令行对字符串解析,最终发现还是报错,但是定位到原因是中文字符无法解析;

  4. 类似的中文字符解析问题之前有遇到过,在windows上会出现UnicodeDecodeError: 'GBK' codec错误,需要修改原生的subprocess.py文件解决,或者采用如下方式,于是进行尝试,但是无效;

    # 创建一个新的 Popen 类,并继承自 subprocess.Popen
    class MySubprocessPopen(subprocess.Popen):
    	def __init__(self, *args, **kwargs):
    		# 在调用父类(即 subprocess.Popen)的构造方法时,将 encoding 参数直接置为 UTF-8 编码格式
    		super().__init__(encoding='UTF-8', *args, **kwargs)
    
    
    # 必须要在导入 PyExecJS 模块前,就将 subprocess.Popen 类重置为新的类
    subprocess.Popen = MySubprocessPopen
    

    ps: 上述代码忘了是在哪篇文章看到的了,比修改原生文件的方式好,记下来备用。

  5. 指定execjs的运行环境:

    os.environ["EXECJS_RUNTIME"] = "JScript"
    os.environ["EXECJS_RUNTIME"] = "Node"

    加上相关代码后仍然无效(其实这个才是关键,后面会讲)

  6. python3的编码问题,在py文件开头加上# -- coding: utf-8 --,或者尝试对字符串编码再解码等,都无效;

  7. 还有一系列绞尽脑汁的重装python,升级python版本等无效操作...

最终解决方案

尝试了无数方案后,复盘代码,发现可能还是运行execjs导致的问题,偶然间在一个网页上找到execjs.get().name可以获取执行环境,于是再试试:

>>> import execjs
>>> execjs.get().name
'SpiderMonkey'
>>> import os
>>> os.environ["EXECJS_RUNTIME"] = "Node"
>>> execjs.get().name
'SpiderMonkey'
>>> os.environ["EXECJS_RUNTIME"] = "JScript"
>>> execjs.get().name
'SpiderMonkey'

发现没有?在执行os.environ["EXECJS_RUNTIME"] = "xxx"前后运行环境不会有任何改变,这是什么原因呢?

再在旧服务器上执行execjs.get().name,结果得到的环境是'Node.js (V8)'

噢,因为新服务器没有使用到node环境,所以没有安装nodejs,这就是那个“变量”?

于是在新服务器上安装同版本nodejs,执行成功,大功告成!