lxml.etree.ElementTree 破坏变音符号
lxml.etree.ElementTree mangles diacritics
另一个标题可以是 为什么 lxml.etree.ElementTree.write 不相信我指定的编码?
使用 Python 3.6 将某些 json 响应转换为某些 XML 方言。 json 是正确的 utf-8,我对数据所做的一切就是使用 lxml.builder
将其包装在 XML-tags 中。
我希望能够在浏览器中检查 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,我学到了一个可能的解决方法,即 platform-independent 甚至 machine-independent:
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()
。
但是,当我指定它是 utf-8 时,为什么 lxml.etree.ElementTree.write()
将数据解释为 cp1252 编码?恕我直言,encode().decode()
根本没有必要。
任何有学问的评论将不胜感激。
网络服务器似乎没有为其传送的内容返回正确的 HTTP headers。
如果检查返回的 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 尽力解码响应 body 并使用 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
object 进行编码将为您提供可很好解码的 JSON 数据。
这个问题在Getty support group讨论
它在 10m 前已修复但尚未部署 :-( 我已重新打开该问题 ITSLOD-460 并希望它能尽快部署。
另一个标题可以是 为什么 lxml.etree.ElementTree.write 不相信我指定的编码?
使用 Python 3.6 将某些 json 响应转换为某些 XML 方言。 json 是正确的 utf-8,我对数据所做的一切就是使用 lxml.builder
将其包装在 XML-tags 中。
我希望能够在浏览器中检查 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,我学到了一个可能的解决方法,即 platform-independent 甚至 machine-independent:
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()
。
但是,当我指定它是 utf-8 时,为什么 lxml.etree.ElementTree.write()
将数据解释为 cp1252 编码?恕我直言,encode().decode()
根本没有必要。
任何有学问的评论将不胜感激。
网络服务器似乎没有为其传送的内容返回正确的 HTTP headers。
如果检查返回的 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 尽力解码响应 body 并使用 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
object 进行编码将为您提供可很好解码的 JSON 数据。
这个问题在Getty support group讨论 它在 10m 前已修复但尚未部署 :-( 我已重新打开该问题 ITSLOD-460 并希望它能尽快部署。