整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Python 中的不可见零宽度字符

Python 中的不可见零宽度字符

Python 编程中,不可见零宽度字符可能会在各种场景下悄悄出现,对文本处理造成干扰。这些字符在视觉上并不显现,但它们的存在可能会导致字符串比较失败、正则表达式匹配错误、编码问题等多种问题。本文将深入探讨这些字符的识别方法、处理策略以及实际应用场景,并附上相应的代码示例。

识别不可见零宽度字符

识别不可见零宽度字符通常涉及到对字符编码和 Unicode 标准的了解。Python 提供了多种工具和函数来帮助我们检测这些字符。

例如,我们可以使用 ord() 函数来检查字符的 Unicode 码点,进而判断它是否为不可见零宽度字符。下面是一个简单的示例:

def contains_zero_width_chars(s):
    for char in s:
        # 检查是否属于零宽度字符的 Unicode 范围
        if ord(char) in range(8202, 8208):  # 举例:零宽度字符范围
            return True
    return False


text="Hello\u200bWorld"  # 包含一个零宽度空格
print(contains_zero_width_chars(text))  # 输出:True

此外,正则表达式也是检测不可见零宽度字符的强大工具。例如,我们可以使用 re 模块来查找字符串中的零宽度空格:

import re

text="Hello\u200bWorld"  # 包含一个零宽度空格  
zero_width_space=re.search(r'\u200b', text)

if zero_width_space:
    print("Found zero-width space!")
else:
    print("No zero-width space found.")

处理不可见零宽度字符

一旦识别出不可见零宽度字符,我们可以采取多种策略来处理它们,例如移除、替换或忽略这些字符。

移除字符

import re


def remove_zero_width_chars(s):
    # 使用正则表达式替换所有零宽度字符为空字符串  
    return re.sub(r'[\u2000-\u200F]', '', s)


text="Hello\u200bWorld"
cleaned_text=remove_zero_width_chars(text)
print(cleaned_text)  # 输出:HelloWorld

替换字符

import re


def replace_zero_width_chars(s, replacement=' '):
    # 使用正则表达式替换所有零宽度字符为指定字符  
    return re.sub(r'[\u2000-\u200F]', replacement, s)


text="Hello\u200bWorld"
replaced_text=replace_zero_width_chars(text)
print(replaced_text)  # 输出:Hello World

应用场景

不可见零宽度字符在实际应用中可能出现在多种场景,以下是一些例子:

1. 文本处理与清洗
在文本处理任务中,如自然语言处理、数据挖掘或搜索引擎中,清洗文本数据是非常重要的步骤。不可见零宽度字符可能会导致分词错误、关键词匹配失败等问题,因此需要在预处理阶段进行识别和清理。

2. 网页内容爬取
从网页爬取文本时,由于 HTML 或 CSS 的原因,可能会包含不可见零宽度字符。这些字符可能会影响后续的数据分析或展示,因此需要去除。

3. 用户输入验证
在接收用户输入时,为确保数据的完整性和准确性,需要检查并处理不可见零宽度字符。这些字符可能是用户无意中引入的,或者是恶意用户为了绕过某些验证机制而故意插入的。

4. 跨平台文件传输
在跨平台文件传输或文本编辑过程中,由于不同平台或软件对字符编码的处理差异,可能会引入不可见零宽度字符。在文件接收方,需要处理这些字符以确保数据的一致性。

通过了解不可见零宽度字符的识别和处理方法,并结合实际应用场景,我们可以更好地处理文本数据,提高程序的健壮性和准确性。在编写代码时,应该始终注意检查和处理这些潜在的字符问题。

背景

最近发现某个数据采集的系统拿下来的数据,有些字段的JSON被莫名截断了,导致后续数据分析的时候解析JSON失败。

类似这样

{"title": "你好

或者这样,多了个双引号啥的

{"title":""你好"}

因为数据库是Oracle,起初以为是Oracle这老古董出问题了,结果一番折腾,把每条写入数据的SQL语句都拿出来,看起来里面的JSON格式都没问题。

这也太诡异了吧,看起来没毛病,但就为啥JSON被随机截断呢?

最后我试着把整段SQL放在Rider的 query console 里面执行,然后再去数据库里读取这段JSON,居然发现变成这样了:

{"title":"?你好"}

啊这,看到这个大大的问号,立刻就能知道这个“你好”里面不止是这两个字,肯定含有不可见的Unicode字符。

然后把这段JSON复制出来,用16进制模式打开,果然看到在“你好”前面有一个 \u0020 的字符…

2Unicode码表

  • 0000-007F:C0控制符及基本拉丁文 (C0 Control and Basic Latin)
  • 0080-00FF:C1控制符及拉丁文补充-1 (C1 Control and Latin 1 Supplement)
  • 0100-017F:拉丁文扩展-A (Latin Extended-A)
  • 0180-024F:拉丁文扩展-B (Latin Extended-B)
  • 0250-02AF:国际音标扩展 (IPA Extensions)
  • 02B0-02FF:空白修饰字母 (Spacing Modifiers)
  • ……

这里再附上部分 Unicode 表格

U+0123456789ABCDEF
0000NULSOHSTXETXEOTENQACKBELBSHTLFVTFFCRSOSI
0010DLEDC1DC2DC3DC4NAKSYNETBCANEMSUBESCFSGSRSUS
0020
!"#$%&'()*+,-./
00300123456789:;<=>?
0040@ABCDEFGHIJKLMNO
0050PQRSTUVWXYZ[\]^_
0060`abcdefghijklmno

可以看到上面那个 \u0020 在第三行第一列,是一个不可见字符,躲在标题的前面

也就是因为这个 Unicode 字符,Oracle无法正确解析,所以导致了插入数据的时候错乱了

所以破案了,就是系统前台使用人员,在输入的时候不知道咋滴搞了个Unicode字符进去…

解决方法就是我这边采集的时候再做一次过滤…

没想到C#要搞个过滤 Unicode 还挺折腾的,资料太少…

最后还是参考了Java的资料搞的。==...

3代码

代码如下

写了个扩展方法来过滤

public static class StringExt { 
// 控制字符
private static readonly Regex ControlCharRegex=new Regex(@"[\p{C}]", RegexOptions.Compiled);

/// <summary>
/// 移除控制字符
/// </summary>
public static string RemoveControlChars(this string text) {
return ControlCharRegex.Replace(text, string.Empty);
}
}

要使用的时候就这样

var outStr="带有Unicode的字符串".RemoveControlChars();

搞定。

4参考资料

  • UniCode编码表及部分不可见字符过滤方案 - https://www.cnblogs.com/fan-yuan/p/8176886.html
  • https://stackoverflow.com/questions/6198986/how-can-i-replace-non-printable-unicode-characters-in-java

avaScript奇技淫巧:隐形字符

本文,分享一种奇特的JS编程技巧,功能是:可以使字符串“隐形”、不可见!

效果展示

如下图所示,一个字符串经物别的操作之后,其长度有621字节,但内容却是“隐形”不可见的!

功能用途

这个技术可以应用到很多领域,非常具有实用性。

比如:代码加密、数据加密、文字隐藏、内容保密、隐形水印,等等。

原理介绍

实现字符串隐形,技术原理是“零宽字符”。

什么是“零宽字符”呢?

在Unicode编码中,有一类奇怪的字符格式,它们不可见、不可打印,主要用于调整字符的显示格式。

常见零宽字符类型:

空格符:格式为U+200B,用于较长字符的换行分隔;

非断空格符:格式为U+FEFF,用于阻止特定位置的换行分隔;

连字符:格式为U+200D,用于阿拉伯文与印度语系等文字中,使不会发生连字的字符间产生连字效果;

断字符:格式为U+200C,用于阿拉伯文、德文、印度语系等文字中,阻止会发生连字的字符间的连字效果;

左至右符:格式为U+200E,用于在混合文字方向的多种语言文本中,规定排版文字书写方向为左至右;

右至左符:格式为U+200F : 用于在混合文字方向的多种语言文本中,规定排版文字书写方向为右至左;

在编程实现隐形字符功能时,先将字符串转为二进制,再将二进制中的1转换为\u200b;0转换为\u200c;空格转换为\u200d,最后使用\ufeff 零宽度非断空格符作分隔符。这几种unicode字符都是不可见的,因此最终转化完成并组合后,就会形成一个全不可见的“隐形”字符串。

功能源码

function text_2_binary(text){
    return text.split('').map(function(char){ return char.charCodeAt(0).toString(2)}).join(' ');
}
function binary_2_hidden_text(binary){
    return binary.split('').map(function (binary_num){
        var num=parseInt(binary_num, 10);
        if (num===1) {
            return '\u200b';
        } else if(num===0) {
            return '\u200c';
        }
        return '\u200d';
    }).join('\ufeff')
}
var text="jshaman是专业且强大的JS代码混淆加密工具";
var binary_text=text_2_binary(text);
var hidden_text=binary_2_hidden_text(binary_text);
console.log("原始字符串:",text);
console.log("二进制:",binary_text);
console.log("隐藏字符:",hidden_text,"隐藏字符长度:",hidden_text.length);

隐型还原

接下来介绍“隐形”后的内容如何还原。

在了解上文内容之后,知道了字符隐形的原理,再结合源代码可知:还原隐形内容,即进行逆操作:将隐形的unicode编码转化成二进制,再将二进制转成原本字符。

直接给出源码:

function hidden_text_2_binary(string){
  return string.split('\ufeff').map(function(char){
    if (char==='\u200b') {
      return '1';
    } else if(char==='\u200c') {
      return '0';
    }
    return ' ';
  }).join('')
}
function binary_2_Text(binaryStr){
  var text=""
  binaryStr.split(' ').map(function(num){
    text +=String.fromCharCode(parseInt(num, 2));
  }).join('');
  return text.toString();
}
console.log("隐形字符转二进制:",hidden_text_2_binary(hidden_text));
console.log("二进制转原始字符:",binary_2_Text(hidden_text_2_binary(hidden_text)));

运行效果:

如果在代码中直接提供“隐形”字符内容,比如ajax通信时,将“隐形”字符由后端传给前端,并用以上解密方法还原,那么这种方式传递的内容会是非常隐秘的。

但还是存在一个安全问题:他人查看JS源码,能看到解密函数,这可能引起加密方法泄露、被人推导出加密、解密方法。

对此问题,可以采用JS代码混淆加密,进一步提升整体JS代码安全性。

JS代码加密

JShaman对上面两个解密函数进行代码混淆加密。

如下图,来到JShaman网站,贴入要加密的JS代码:

使用如下配置:

得到加密的JS代码:

将代码粘贴回源文件中:

加密的JS代码,运行起来跟之前完全一样。

但此时,已不再是裸露的透明JS代码,从这混乱复杂的代码中很难看出功能逻辑。

注:“隐形字符”技术,可用于前后端JS执行环境,即可在Node.JS中执行,也可在浏览器中使用。