python - lxml.etree.ElementTree 破坏变音符号

标签 python json python-3.x lxml diacritics

另一个标题可能是为什么 lxml.etree.ElementTree.write 不相信我指定的编码?

使用 Python 3.6 将一些 json 响应转换为一些 XML 方言。 json 是正确的 utf-8,我对数据所做的一切都是使用 lxml.builder 将其包装在 XML 标签中。

我希望能够在浏览器中检查 xml 结果,所以我使用 lxml.etree.ElementTree 中的 write 方法制作一个 xml 文件使用 Firefox(或 Chrome、IE 或 Edge,没有区别)打开。

下面是一些测试代码,使用带有变音符号的字符串而不是 json 响应。这很好用。注意 xml_declaration=True 通知浏览器编码。

# -*- coding: utf-8 -*-

from lxml import etree as ET
from lxml.builder import E          # E *is* ElementMaker()

s = 'Björn Nøsflùgl in Israël'      # ö = c3 b6, ø = c3 b8, ù = c3 b9, ë = c3 ab

xml = E.myXML(E.name(s))            # <class 'lxml.etree._Element'>
tree = ET.ElementTree(xml)          # <class 'lxml.etree._ElementTree'>

tree.write(open('1.xml', 'wb'), xml_declaration=True, encoding='utf-8')
# xml declaration says 'UTF-8', Firefox renders correctly 

但是,当我对 json 响应执行相同操作时,变音符号会被破坏。

编辑: 下面演示了问题(在 Windows/Python 3.6 虚拟环境中)。

# -*- coding: utf-8 -*-  

import requests  
import json 
from lxml import etree as ET  
from lxml.builder import E  

URL = '''http://vocab.getty.edu/sparql.json?query=SELECT ?term WHERE {?subject luc:term "löss*"; xl:prefLabel [dct:language gvp_lang:nl; xl:literalForm ?term]}'''

gvp_json = requests.get(URL).json()

with open('gvp_response.json', 'w') as f:           
    f.write(str(gvp_json))

for record in gvp_json['results']['bindings']: 
    term = record['term']['value']  # .encode('cp1252').decode('utf-8')
    print(term)    

xml = E.myXML(E.term(term)) 
tree = ET.ElementTree(xml)          
tree.write(open('1.xml', 'wb'), xml_declaration=True, encoding='utf-8')    

如果我将 .encode('cp1252').decode('utf-8') 附加到注释中指示的 term 子句,问题就解决了.但为什么这是必要的呢?

编辑 2: 同时,来自 this old issue ,我学到了一种可能的解决方法,即与平台无关甚至与机器无关:

import locale 
...

myencoding = locale.getpreferredencoding()  
for record in gvp_json['results']['bindings']: 
    s = record['term']['value']
    if myencoding == 'utf-8':
        term = s
    else:
        term = s.encode(myencoding).decode('utf-8') 

    print(term) 
    ...    

它确实不漂亮,但它确实有效。而且它不会不必要地 encode().decode()

解释 - 请 CMIIW:print() 需要假设一些编码,无法从数据本身推导出它,因此求助于 locale.getpreferredencoding() 打印到控制台时。

但为什么 lxml.etree.ElementTree.write() 将数据解释为 cp1252 编码当我指定它是 utf-8 时?恕我直言,根本不需要 encode().decode()

任何博学的评论将不胜感激。

最佳答案

网络服务器似乎没有为其传送的内容返回正确的 HTTP header 。

如果检查返回的 header ,您可以看到 ISO-8859-1(参见 Content-Type header ):

$ python
Python 3.6.3 (default, Oct  3 2017, 21:45:48) 
[GCC 7.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> 
>>> url = '''http://vocab.getty.edu/sparql.json?query=SELECT ?term WHERE {?subject luc:term "löss*"; xl:prefLabel [dct:language gvp_lang:nl; xl:literalForm ?term]}'''
>>>
>>> r = requests.get(url)

>>> r.encoding
'ISO-8859-1'
>>> r.apparent_encoding
'ISO-8859-9'
>>> 
>>> from pprint import pprint as pp
>>> pp(dict(r.headers))
{'Access-Control-Allow-Origin': '*',
 'Content-Disposition': 'attachment; filename="sparql.json"',
 'Content-Language': 'en-US',
 'Content-Type': 'application/sparql-results+json;charset=ISO-8859-1',
 'Date': 'Wed, 04 Apr 2018 09:55:40 GMT',
 'Link': '<http://opendatacommons.org/licenses/by/1.0/>; rel="license"',
 'Set-Cookie': 'BIGipServerForest=587573440.45165.0000; path=/; Httponly, '
               'TS01e0ec9b=01612fcdbaa1d82ab58469a933fdc88755f6f4d7323361b3f59734f898a9c7014e66f7c5cbf39c733fd24dc4e8817f73daf98f5aba52069337bdae2569cd6dbf2a6f05579c; '
               'Path=/',
 'Transfer-Encoding': 'chunked'}

而且文字确实不可读:

>>> r.text
'{\n  "head" : {\n    "vars" : [ "term" ]\n  },\n  "results" : {\n    "bindings" : [ {\n      "term" : {\n        "xml:lang" : "nl",\n        "type" : "literal",\n        "value" : "lössgronden"\n      }\n    } ]\n  }\n}'

python-requests 尽力解码响应主体并使用 ISO-8859-1。 参见 the docs对于发生的事情。

The encoding of the response content is determined based solely on HTTP headers, following RFC 2616 to the letter. If you can take advantage of non-HTTP knowledge to make a better guess at the encoding, you should set r.encoding appropriately before accessing this property.

问题是你知道响应是 UTF-8 编码的,所以你可以强制它:

>>> # force encoding used when accessing r.text
... # see http://docs.python-requests.org/en/master/api/#requests.Response.text
... 
>>> r.encoding = 'utf-8'
>>> 
>>> 
>>> r.text
'{\n  "head" : {\n    "vars" : [ "term" ]\n  },\n  "results" : {\n    "bindings" : [ {\n      "term" : {\n        "xml:lang" : "nl",\n        "type" : "literal",\n        "value" : "lössgronden"\n      }\n    } ]\n  }\n}'
>>> 
>>> 
>>> r.json()
{'head': {'vars': ['term']}, 'results': {'bindings': [{'term': {'xml:lang': 'nl', 'type': 'literal', 'value': 'lössgronden'}}]}}
>>> 
>>> pp(r.json())
{'head': {'vars': ['term']},
 'results': {'bindings': [{'term': {'type': 'literal',
                                    'value': 'lössgronden',
                                    'xml:lang': 'nl'}}]}}
>>> 

因此,对从 requests.get() 获得的 Response 对象强制编码将为您提供可很好解码的 JSON 数据。

关于python - lxml.etree.ElementTree 破坏变音符号,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49575005/

相关文章:

java - java中如何对JSON数组进行排序

python - 如何使用 PYQT5 在 QTreeView 中选择和编辑新创建的文件夹

python - 艰难地学习 Python 练习 47 个 Nose 测试 NameError

python - DeprecationWarning : BaseException. 消息已从 Python 2.6 exception.__class__, exception.message,

python - 通过将 default_factory 作为命名参数传递来构造 defaultdict

Python 使用列表理解读取行(csv 和 json 文件)

json - JAXRS 和 tomee 的基本查询

python - 有没有办法防止在 python 2 中调用 python 3 脚本?

python - 使用 Django ORM 计算多对多关系的频率和相关性的优雅方法?

python - Xlsx Writer 不创建 excel 文件但也不创建错误