如何使2个具有相同词/义,但Unicode差异哈希到相同ID的字符串?

问题描述

对于我正在从事的Web抓取项目,我计划将实体存储在数据库中,这些实体的ID是其名称/标题的md5哈希值。

但是,由于字符串中存在Unicode,因此会出现相同名称/标题的不同哈希值

例如,“运动学,物理Ed \ xa0和娱乐”的md5哈希与“运动学,物理Ed和娱乐”的md5哈希不同。

我尝试使用Unicode归一化,但哈希值之间的差异仍然相同

import hashlib
import unicodedata


def generate_id(*args):
    """

    :param args: strings to be used to generate an id
    :return: md5 hash of the passed arguments
    """
    string = ''
    for arg in args:
        string += ' ' + arg
    hash_algorithm = hashlib.md5()
    hash_algorithm.update(string.encode('utf-8'))
    return hash_algorithm.hexdigest()


def clean_text(text):
    """
    normalizes the unicode in a text to be more readable and generate a more accurate id from
    :param text: string to be normalized
    :return: normalized version of text
    """
    return unicodedata.normalize('NFC',text)


print(generate_id(clean_text('Kinesiology,Phys Ed\xa0and Recreation'))) # hashes to acd21f3b094a77d1a2393a8daeac42d9
print(generate_id('Kinesiology,Phys Ed and Recreation')) # hashes to 5ac6bc3ca3d743d99e9b93a7a5379fe9

我该怎么做,以确保两个字符串都相同并且散列到相同的id,以使“ Kinesiology,Phys Ed \ xa0和Recreation”与“ Kinesiology,Phys Ed和Recreation”具有相同的字符串和相同的哈希值(不管是否存在unicode,任何2个字符串都一样)?

解决方法

由于“具有相同的散列”只是二进制相等性的代理,因此您需要将字符串标准化为相同。

在Unicode术语中,两个给定的字符串不是规范上等效的,但它们是兼容的。因此,您将能够使用NFKD函数中的兼容性分解/合成范式(NFKCclean_text())来生成相同的哈希值:

def clean_text(text):
    return unicodedata.normalize('NFKD',text)

NO-BREAK SPACE (U+00A0)字符的分解属性设置为<noBreak> SPACE (U+0020)。分解属性中存在一个关键字(在这种情况下为<noBreak>),表明该字符与常规空格字符兼容,但不规范对等。


旁注

由于评论中有此要求,因此需要对NFKC和NFKD范式之间的区别进行一些澄清:

Unicode字符可以由多个代码点组成。某些字符可以以不同(但在规范上等效)的方式表示:作为单个代码点或作为代码点的组合。例如:é可以表示为ée + ◌́。规范化时,合成范式(NFC,NFKC)将尝试将序列转换为其组成形式(e + ◌́é);分解范式(NFD,NFKD)将尝试将组合的字符转换为序列(ée + ◌́)。您使用哪一个完全取决于情况。只需确保不要将苹果与橘子进行比较。