万恶的emoji!!javascript读取特殊字符和使用python读取特殊字符(emoji)的问题及解决(文本编码格式天坑)

万恶的emoji!!javascript读取特殊字符和使用python读取特殊字符(emoji)的问题及解决(文本编码格式天坑)

十一月 08, 2020

最近在写一些网页,具体功能就是让别人在网页中给一句话中的词组打标签,其中需要记录词组的位置,也就是这个词在当前字符串中的下标

本来是很正常的一件事,但是由于使用该数据时用的是python,天坑就出现了。

怎么都对不齐的词

正常的拿到数据,然后开始通过下标来获取标记的词以及其标签进行训练时发现,诶,这数据里面的词怎么都不对劲,是不是标注给标错了。

再仔细看了一下发现里面的词好像是错位了,有的字符串中的词错了一位,有的错了两位,好像里面有特殊字符诶,是不是这玩意搞的事情,于是打开chrome来试一试发生了什么,下面分别是javascript和python中emoji的长度对比。

)

这是在搞什么诶,于是想了想,既然在js里面长度是2,python里面是1,能不能用re来搞这个呢,用re匹配到emoji来对下标进行减1处理,使索引正常
找了半天找到了匹配emoji的方法

1
2
pattern = re.compile(u"[\U0001F300-\U0001F64F\U0001F680-\U0001F6FF\u2600-\u2B55]")
emojiList = re.findall(emojiPattern, word[0:start])

试了一下,emmmm好像还行,但是还有一些数据没有匹配上,再看看什么情况
啊这…..emoji表情果然不止有长度是2的

把python变成js的样子

这没救了啊,算了,re这种方法本身就是治标不治本的,既然js里面解析的长度和python不一致,干脆直接用js解析一遍长度,再用python的长度比较一下来计算出差值进行匹配。
引入python和js联动的库,开始搞事情

1
2
3
4
5
6
import execjs
ctx = execjs.compile("""
function len(x) {
return x.length;
}
""")

emmmm,效果是有了,但是用Python执行js这个速度……,啊喂~我可是要跑几千条数据几十几百万字符的啊,这得整跑到明年去啊,改变战略。

终极方案,掏出编码方式,从最根源搞事情

既然模拟js不行,那我模拟js对字符串编码的解析格式行不行,搜一搜,试一试,将字符串编码使用utf-16le的长度除以2是和js中的长度相等的或者使用utf-16减去2也是可以的,因为utf-16带有两字节的头,既然这样那按照下标取到相应的字符,然后再转回相应的字符就可以获得正确的下标了

1
2

start = len(text.encode('utf-8')[:start*2].decode('utf-8'))

这样,我们就取到了正确的长度,python和js获取字符串长度采用的方式并不一样,所以导致了相同字符串统计出来的下标不一样,js统计长度的编码使用的是utf-16,而python使用的utf-32,自然二者对这特殊字符有着不一样的长度,utf-16对字符的容纳程度是2,也就是两个字节,utf-8容纳程度是1也就是一个字节,utf-32是4也就是4个字节,对于不到4个字节长度也就是8bit的字符统一计算为1,而utf-16是对不到2个字节长度也就是16bit的字符统一计算为1,所以当😈这样的字符出现时,对于utf-16来说它有3个字节,超出了2个字节的限制,所以统一计算为两个字符,而对于utf-32来说,它并没有到达4个字节的限制,所以计算为1个字符。

效果如下图所示,注意,u-16有2个字节的前缀,utf-32有4个字节的前缀,减去才是字符在该编码下的正确长度