首页 > 解决方案 > 在 python 中解码时,85 位编码的字符串没有被正确处理

问题描述

我已经编码了一个十六进制(设备 ID):

9F1D8E8BA2194CD29CC744083914535A

转化为 85 位编码数字(等效转换):

T,irMU)?YQSB"#\'3>>Cq

这些都存储在 spark 数据框中。

问题是当我尝试将其转换回来(将列传递给 UDF)时,它没有返回设备 ID,在调试时我发现它给出的输出实际上是:

T,irMU)?YQSB"#\\\'3>>Cq

这意味着实际的字符串会自动添加转义字符,然后对其进行解码。

这是 udf,我为转换而写的:

def convert_id(id):
    id = id.replace("-", "")
    return str(bs64.a85encode(bytearray.fromhex(id)))[2:-1]

udf_convert_id = udf(convert_id, StringType())

这是为了解码它:

def convert_docid2idfa(docId):
    try:
        docId = docId.replace('\\\\','\\')
        id_str = bs64.a85decode(docId).hex()
        idfa = id_str[:8]+"-"+id_str[8:12]+"-"+id_str[12:16]+"-"+id_str[16:20]+"-"+id_str[20:]
        return idfa
    except:
        return docId

convert_docid2idfa_udf = udf(convert_docid2idfa, StringType())

我正在解码这个版本,它有逃逸。

标签: python-3.xcharacter-encodingpyspark

解决方案


ASCII85 编码的 ID的实际值应该是:

T,irMU)?YQSB"#'3>>Cq

值中不应有 a \。错误是您将a85encode()结果转换为字符串:

str(bs64.a85encode(bytearray.fromhex(id)))[2:-1]

a85encode()返回一个bytes对象,您需要将其解码为 ASCII,以获得具有相同代码点的字符串值:

bs64.a85encode(bytearray.fromhex(id)).decode('ASCII')

str(bytesobject)为您提供了一个调试友好的表示形式,可以安全地粘贴回 Python 代码中,因此任何字符都在前面'加上 a 进行转义。\您不想使用此表示进行序列化。

请注意,您不需要bytearray一个常规的不可变bytes对象就足以将十六进制 ID 解码为二进制字符串:

bs64.a85encode(bytes.fromhex(id)).decode('ASCII')

演示:

>>> import base64 as b64
>>> id = '9F1D8E8BA2194CD29CC744083914535A'
>>> encoded = bs64.a85encode(bytes.fromhex(id)).decode('ASCII')
>>> print(encoded)
T,irMU)?YQSB"#'3>>Cq
>>> b64.a85decode(encoded).hex()
'9f1d8e8ba2194cd29cc744083914535a'

如果您无法修复编码,您仍然可以使用unicode_escape编解码器修复损坏的值;首先将字符串编码为 ASCII。您应该能够通过测试长度来检测这种损坏的值,一个 20 字节的 IDFA 应该总是产生一个 20 个字符的 ASCII85 字符串,任何更长的都需要修复:

if len(docId) > 20:
    docId = docId.encode('ascii').decode('unicode_escape')
decoded = b64.a85decode(docId).hex()

以上修复了通过调用str()字节对象引入的转义:

>>> encoded
'T,irMU)?YQSB"#\'3>>Cq'
>>> botched = str(encoded.encode('ascii'))[2:-1]
>>> botched
'T,irMU)?YQSB"#\\\'3>>Cq'
>>> botched.encode('ascii').decode('unicode_escape')
'T,irMU)?YQSB"#\'3>>Cq'
>>> bs64.a85decode(botched.encode('ascii').decode('unicode_escape')).hex()
'9f1d8e8ba2194cd29cc744083914535a'

请注意,如果您正在使用 IDFA 值,则可以使用uuid.UUID()该类在表示之间进行转换:

from uuid import UUID

bs64.a85encode(UUID(hex=id).bytes).decode('ASCII')

编码,和

str(UUID(bytes=bs64.a85decode(docId)))

回到带有破折号的 8-4-4-4-12 十六进制字符串:

>>> from uuid import UUID
>>> id = '9F1D8E8B-A219-4CD2-9CC7-44083914535A'
>>> encoded = bs64.a85encode(UUID(hex=id).bytes).decode('ASCII')
>>> encoded
'T,irMU)?YQSB"#\'3>>Cq'
>>> str(UUID(bytes=bs64.a85decode(encoded)))
'9f1d8e8b-a219-4cd2-9cc7-44083914535a'

推荐阅读