我有一个数据集,其中包含一个采用未知(且不友好)编码的ID字段。我可以使用普通的python读取单列,并验证值在多个数据集之间是不同的和一致的(即它可以用作连接的主键)。
使用spark.read.csv
加载文件时,似乎spark正在将列转换为utf-8
。但是,某些多字节序列将转换为Unicode字符U+FFFD REPLACEMENT CHARACTER.
(十六进制EF BF BD
)。
是否有一种方法可以强制Spark以字节而不是字符串的形式读取列?
以下是一些可用于重新创建我的问题的代码(让列a
为ID字段):
使用示例数据创建文件
data = [
(bytes(b'\xba\xed\x85\x8e\x91\xd4\xc7\xb0'), '1', 'a'),
(bytes(b'\xba\xed\x85\x8e\x91\xd4\xc7\xb1'), '2', 'b'),
(bytes(b'\xba\xed\x85\x8e\x91\xd4\xc7\xb2'), '3', 'c')
]
with open('sample.csv', 'wb') as f:
header = ["a", "b", "c"]
f.write(",".join(header)+"\n")
for d in data:
f.write(",".join(d) + "\n")
使用熊猫阅读
import pandas as pd
df = pd.read_csv("sample.csv", converters={"a": lambda x: x.encode('hex')})
print(df)
# a b c
#0 baed858e91d4c7b0 1 a
#1 baed858e91d4c7b1 2 b
#2 baed858e91d4c7b2 3 c
尝试使用Spark读取同一文件
spark_df = spark.read.csv("sample.csv", header=True)
spark_df.show()
#+-----+---+---+
#|a |b |c |
#+-----+---+---+
#|�텎��ǰ|1 |a |
#|�텎��DZ|2 |b |
#|�텎��Dz|3 |c |
#+-----+---+---+
赞!好的,如何转换为hex
?
import pyspark.sql.functions as f
spark_df.withColumn("a", f.hex("a")).show(truncate=False)
#+----------------------------+---+---+
#|a |b |c |
#+----------------------------+---+---+
#|EFBFBDED858EEFBFBDEFBFBDC7B0|1 |a |
#|EFBFBDED858EEFBFBDEFBFBDC7B1|2 |b |
#|EFBFBDED858EEFBFBDEFBFBDC7B2|3 |c |
#+----------------------------+---+---+
(在此示例中,值是不同的,但在我的较大文件中不是这样)
如您所见,值是 close ,但是某些字节已由EFBFBD
有什么方法可以在Spark中读取文件(也许使用rdd
吗?),以便我的输出看起来像熊猫版本:
#+----------------+---+---+
#|a |b |c |
#+----------------+---+---+
#|baed858e91d4c7b0|1 |a |
#|baed858e91d4c7b1|2 |b |
#|baed858e91d4c7b2|3 |c |
#+----------------+---+---+
我尝试过强制转换为byte
并指定架构,以使此列为ByteType()
,但这没用。
修改
我正在使用Spark v 2.1。
答案 0 :(得分:0)
问题根源在于分隔文件不适合二进制数据。
如果文本存在已知的一致编码,请使用charset
选项。请参阅https://github.com/databricks/spark-csv#features(我不知道2.x文档中描述分隔阅读选项的好地方,所以我仍然回到1.x文档)。我建议尝试使用8位ASCII码,例如ISO-8859-1
或US-ASCII
。
如果没有这样的编码,则需要将输入转换为其他格式,例如,对第一列进行base64编码,或者操纵读取的数据以将其恢复为所需的格式。
答案 1 :(得分:0)
如何将其存储为base 64编码并在读取时进行解码?
import base64
data = [
(base64.b64encode(bytes(b'\xba\xed\x85\x8e\x91\xd4\xc7\xb0')), '1', 'a'),
(base64.b64encode(bytes(b'\xba\xed\x85\x8e\x91\xd4\xc7\xb1')), '2', 'b'),
(base64.b64encode(bytes(b'\xba\xed\x85\x8e\x91\xd4\xc7\xb2')), '3', 'c')
]
with open('sample.csv', 'wb') as f:
header = ["a", "b", "c"]
f.write(",".join(header)+"\n")
for d in data:
f.write(",".join(d) + "\n")
import pyspark.sql.functions as f
import base64
spark_df.withColumn("a", base64.b64decode("a"))
答案 2 :(得分:0)
看起来正在进行一些 UTF-8 解码; \xba 不是任何有效的 UTF-8 编码(见下文),正在被“替换字符”\uFFFD 替换。这肯定会发生:CSV 是一种文本格式,因此解码器必须假设一些编码以解释二进制。
让我们取第一个值 EFBFBD
的开头并手动对其进行解码(https://en.wikipedia.org/wiki/UTF-8 可能有助于理解编码)。
EF
是 0b11101111
,因此一个 3 字节序列(初始位 1110
)从位 1111 开始。BF
是 0b10111111
,所以是位 111111 的延续(初始位 10
)。BD
是 0b10111101
,所以继续位 111101。你不能用像 CSV 这样的文本格式来解决这个问题,除非你能让你的 CSV 编码器和解码器就某种二进制编码格式达成一致。并且该格式将是 binary,而不是 Unicode。请注意,如果您有一般的二进制值,您甚至不能相信行尾!
您也可以使用 base-64 编码。或者,如果您知道行尾不是问题(没有 \x0a
和/或 \x0c
字节),您可能使用行阅读器。但这可能是不推荐的。