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
的字符…
这里再附上部分 Unicode 表格
U+ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0000 | NUL | SOH | STX | ETX | EOT | ENQ | ACK | BEL | BS | HT | LF | VT | FF | CR | SO | SI |
0010 | DLE | DC1 | DC2 | DC3 | DC4 | NAK | SYN | ETB | CAN | EM | SUB | ESC | FS | GS | RS | US |
0020 | ! | " | # | $ | % | & | ' | ( | ) | * | + | , | - | . | / | |
0030 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | : | ; | < | = | > | ? |
0040 | @ | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O |
0050 | P | Q | R | S | T | U | V | W | X | Y | Z | [ | \ | ] | ^ | _ |
0060 | ` | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o |
可以看到上面那个 \u0020
在第三行第一列,是一个不可见字符,躲在标题的前面
也就是因为这个 Unicode 字符,Oracle无法正确解析,所以导致了插入数据的时候错乱了
所以破案了,就是系统前台使用人员,在输入的时候不知道咋滴搞了个Unicode字符进去…
解决方法就是我这边采集的时候再做一次过滤…
没想到C#要搞个过滤 Unicode 还挺折腾的,资料太少…
最后还是参考了Java的资料搞的。==...
代码如下
写了个扩展方法来过滤
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();
搞定。
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代码安全性。
用JShaman对上面两个解密函数进行代码混淆加密。
如下图,来到JShaman网站,贴入要加密的JS代码:
使用如下配置:
得到加密的JS代码:
将代码粘贴回源文件中:
加密的JS代码,运行起来跟之前完全一样。
但此时,已不再是裸露的透明JS代码,从这混乱复杂的代码中很难看出功能逻辑。
注:“隐形字符”技术,可用于前后端JS执行环境,即可在Node.JS中执行,也可在浏览器中使用。
*请认真填写需求信息,我们会在24小时内与您取得联系。