关于Windows中PyExecjs库中文乱码的解决
正文
废话不多说,直接看下面a.js中的代码
function a() {console.log("你好")return "你好";
}
a()
安装PyExecjs并使用,代码如下
import execjswith open('a.js', encoding='utf-8') as f:data = f.read()ctx = execjs.compile(data)
ret = ctx.call('a')
print(ret)
运行
结果报错,如下
File "F:\code\Python\study-spider\.venv\Lib\site-packages\execjs\_external_runtime.py", line 88, in _exec_
return self._extract_result(output)
~~~~~~~~~~~~~~~~~~~~^^^^^^^^
File "F:\code\Python\study-spider\.venv\Lib\site-packages\execjs\_external_runtime.py", line 157, in _extract_result
output = output.replace("\r\n", "\n").replace("\r", "\n")
^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'replace'
笔者去搜了搜,看到一个好的解决办法
其他大佬的解决
Windows中解决execjs的乱码问题_execjs utf-8-CSDN博客https://blog.csdn.net/m0_60642470/article/details/127954981上面这位大佬给出的解决办法是——软改
import subprocess
from functools import partial #用来固定某个参数的固定值
subprocess.Popen=partial(subprocess.Popen,encoding='utf-8')
即,全部代码如下
import subprocess
from functools import partial #用来固定某个参数的固定值
subprocess.Popen=partial(subprocess.Popen,encoding='utf-8')import execjswith open('a.js', encoding='utf-8') as f:data = f.read()ctx = execjs.compile(data)
ret = ctx.call('a')
print(ret)
运行没问题
笔者的解决
首先,先对报错进行思考
AttributeError: 'NoneType' object has no attribute 'replace'
NoneType这个对象没有属性replace,说明output是None,
因此,打个断点,看看堆栈的调用
在execjs对应的文件中_extract_result函数搜索,发现有两个
if self._tempfile:output = self._exec_with_tempfile(source)else:output = self._exec_with_pipe(source)return self._extract_result(output)
一个本身,一个是调用的地方。
可以发现这个output,来源于_exec_with_tempfile或者_exec_with_pipe
很容易发现output,来源于_exec_with_pipe,打个断点进入_exec_with_pipe函数
def _exec_with_pipe(self, source):cmd = self._runtime._binary()p = Nonetry:p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=self._cwd, universal_newlines=True)input = self._compile(source)if six.PY2:input = input.encode(sys.getfilesystemencoding())stdoutdata, stderrdata = p.communicate(input=input)ret = p.wait()finally:del pself._fail_on_non_zero_status(ret, stdoutdata, stderrdata)return stdoutdata
如下
观察,可以发现这个cmd是node.exe的路径,而这里面有个关键的东西——Popen
而这个Popen的encoding是cp936,这就明白了
因此,笔者有两个做法,
第一种方式——在源码里面加一个encoding
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=self._cwd, universal_newlines=True,encoding='utf-8')
运行没问题
第二种做法——直接把cp936变成utf_8
在笔者早期的博客中曾经干过这个事情
对python中open函数的偷梁换柱_mbcs' codec can't decode byte 0x91 in position 63-CSDN博客https://blog.csdn.net/qq_63401240/article/details/128665532?ops_request_misc=%257B%2522request%255Fid%2522%253A%252201ac56de0709aa67bbb1b532edbfc42b%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=01ac56de0709aa67bbb1b532edbfc42b&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-128665532-null-null.nonecase&utm_term=%E5%81%B7%E6%A2%81%E6%8D%A2%E6%9F%B1&spm=1018.2226.3001.4450Windows 系统默认的中文编码(即 GBK),即cp936,那么
全局搜索cp936,可以在如下路径
C:\Users\26644\.rye\py\cpython@3.13.2\Lib\encodings\aliases.py
笔者使用的rye,路径看个人python路径进行寻找,笔者这个只是一个参考,总之aliases.py文件。
发现如下东西
直接偷梁换柱,把cp936的右边变成utf_8,如下
直接换掉,运行是正确的,偷梁换柱。
笔者经常干这个事情,哈哈哈哈哈哈
进一步的思考
为什么console.log没有输出到终端??????????????
这不是一个bug,仅仅只是笔者希望输出到终端。
修改一下js文件
function a() {console.log("123")return "你好";
}
运行,没有输出123
看看源码在.venv\Lib\site-packages\execjs\_external_runtime.py文件中,有下面一个函数
def _extract_result(self,output: str):output = output.replace("\r\n", "\n").replace("\r", "\n")output_last_line = output.split("\n")[-2]ret = json.loads(output_last_line)if len(ret) == 1:ret = [ret[0], None]status, value = retif status == "ok":return valueelse:raise ProgramError(value)
说白了,这就是处理输出后的结果,而 output.split("\n")[-2],只获取倒数第二个元素
打个断点看看,output是什么
output
Out[4]: '123\n\n["ok","你好"]\n'
output.split("\n")
Out[5]: ['123', '', '["ok","你好"]', '']
可以发现倒数第二个是一个列表字符串,如果修改一下js文件
function a() {console.log("123")console.log("456")return "你好";
}
debug输出如下。
Out[1]: '123\n456\n\n["ok","你好"]\n'
output.split('\n')
Out[2]: ['123', '456', '', '["ok","你好"]', '']
看来分开之后,倒数第二个是return的结果,
除去后面三个,就是中间的输出。
尝试一下,修改js文件
function b(){console.log('66666666')return 'hello'
}
function a() {console.log("123")console.log("456")console.log(b())return "你好";
}
Out[1]: '123\n456\n66666666\nhello\n\n["ok","你好"]\n'
output.split('\n')
Out[2]: ['123', '456', '66666666', 'hello', '', '["ok","你好"]', '']
没问题,那么,修改源码
def _extract_result(self,output: str):output = output.replace("\r\n", "\n").replace("\r", "\n")output_split= output.split("\n")# 其他输出intermediate=output_split[:-3]# 返回值output_last_line = output_split[-2]ret = json.loads(output_last_line)if len(ret) == 1:ret = [ret[0], None]status, value = retif status == "ok":return {'return_value': value,'intermediate_value': intermediate}else:raise ProgramError(value)
原来是返回最终函数调用的返回值,现在返回了一个字典,既可以获取返回值,也可以获取中间变量。哈哈哈哈哈哈,可以。
测试一下
function b(){// 对象console.log({a:1,b:2})// 数组console.log([1,2,3,4,5])// setconsole.log(new Set([1,2,3,4,5]))// mapconsole.log(new Map([['a',1],['b',2]]))return 'hello'
}
function a() {console.log("123")console.log("456")console.log(b())return "你好";
}
结果如下
{'return_value': '你好', 'intermediate_value': ['123', '456', '{ a: 1, b: 2 }', '[ 1, 2, 3, 4, 5 ]', 'Set(5) { 1, 2, 3, 4, 5 }', "Map(2) { 'a' => 1, 'b' => 2 }", 'hello']}
没问题,哈哈哈哈哈