从多语言Unicode文本中删除表情符号

8

我正在尝试从Unicode文本中仅删除表情符号。我尝试了另一个Stack Overflow帖子中描述的各种方法,但这些方法都没有完全删除所有表情符号/笑脸符号。例如:

解决方案1:

def remove_emoji(self, string):
    emoji_pattern = re.compile("["
                           u"\U0001F600-\U0001F64F"  # emoticons
                           u"\U0001F300-\U0001F5FF"  # symbols & pictographs
                           u"\U0001F680-\U0001F6FF"  # transport & map symbols
                           u"\U0001F1E0-\U0001F1FF"  # flags (iOS)
                           u"\U00002702-\U000027B0"
                           u"\U000024C2-\U0001F251"
                           "]+", flags=re.UNICODE)
    return emoji_pattern.sub(r'', string)

在下面的例子中,Leaves:

Input: తెలంగాణ రియల్ ఎస్టేట్ 
Output: తెలంగాణ రియల్ ఎస్టేట్ 

另一种尝试,解决方案2:
def deEmojify(self, inputString):
    returnString = ""
    for character in inputString:
        try:
            character.encode("ascii")
            returnString += character
        except UnicodeEncodeError:
            returnString += ''
    return returnString

删除任何非英文字符的结果:

 Input: Testరియల్ ఎస్టేట్ A.P&T.S. 
 Output: Test  A.P&T.S. 

它不仅删除了所有表情符号,还由于character.encode("ascii")删除了非英文字符;我的非英文输入无法编码为ASCII。是否有任何方法可以正确地从国际Unicode文本中移除表情符号?

新的表情符号定期添加到Unicode标准中,您需要不断更新正则表达式。 - Martijn Pieters
1
你第一个例子中留下的表情符号是U+1F91D,在Unicode 9.0中添加。而Unicode 10.0Unicode 11.0又再次扩展了列表。我相信版本12.0将需要更多更新。 - Martijn Pieters
3个回答

29
正则表达式已经过时。它似乎只覆盖了到Unicode 8.0定义的Emoji(自从Unicode 9.0添加了U+1F91D HANDSHAKE)。另一种方法是强制编码为ASCII的非常低效的方法,当仅删除Emoji时很少需要这样做(可以使用text.encode('ascii', 'ignore').decode('ascii')更轻松高效地实现)。
如果您需要更新的正则表达式,请从一个积极致力于保持Emoji最新状态的软件包中获取;它专门支持生成这样的正则表达式:
import emoji

def remove_emoji(text):
    return emoji.get_emoji_regexp().sub(u'', text)

该软件包目前已完全更新至Unicode 11.0,并具备快速升级到未来版本的基础设施。只需在有新版本发布时进行升级,您的项目就可以随之升级。

使用您的示例输入的演示:

>>> print(remove_emoji(u'తెలంగాణ రియల్ ఎస్టేట్ '))
తెలంగాణ రియల్ ఎస్టేట్ 
>>> print(remove_emoji(u'Testరియల్ ఎస్టేట్ A.P&T.S. '))
Testరియల్ ఎస్టేట్ A.P&T.S. 

请注意,正则表达式适用于Unicode文本,在Python 2中,请确保您已从str解码为unicode,在Python 3中,请先从bytes解码为str
现在的Emoji非常复杂。上述方法将删除完整且有效的Emoji。如果您有“不完整”的Emoji组件,例如skin-tone codepoints(仅与特定Emoji组合使用),那么删除这些内容将更加困难。皮肤色调代码点很容易(只需之后删除那5个代码点),但是还有whole host of combinations由无辜字符(例如♀ U+2640 FEMALE SIGN或♂ U+2642 MALE SIGN)与variant selectorsU+200D ZERO-WIDTH JOINER一起组成,在其他情况下也具有特定含义,你不能仅仅通过正则表达式来处理这些问题,否则会破坏使用天城体、卡纳达语或CJK表意文字等文本。

话虽如此,以下Unicode 11.0代码点可能是安全的(基于过滤Emoji_Component Emoji-data designation):

20E3          ;  (⃣)     combining enclosing keycap
FE0F          ; ()        VARIATION SELECTOR-16
1F1E6..1F1FF  ; (..)  regional indicator symbol letter a..regional indicator symbol letter z
1F3FB..1F3FF  ; (..)  light skin tone..dark skin tone
1F9B0..1F9B3  ; (..) red-haired..white-haired
E0020..E007F  ; (..)      tag space..cancel tag

可以通过创建新的正则表达式来匹配并删除它们:

import re
try:
    uchr = unichr  # Python 2
    import sys
    if sys.maxunicode == 0xffff:
        # narrow build, define alternative unichr encoding to surrogate pairs
        # as unichr(sys.maxunicode + 1) fails.
        def uchr(codepoint):
            return (
                unichr(codepoint) if codepoint <= sys.maxunicode else
                unichr(codepoint - 0x010000 >> 10 | 0xD800) +
                unichr(codepoint & 0x3FF | 0xDC00)
            )
except NameError:
    uchr = chr  # Python 3

# Unicode 11.0 Emoji Component map (deemed safe to remove)
_removable_emoji_components = (
    (0x20E3, 0xFE0F),             # combining enclosing keycap, VARIATION SELECTOR-16
    range(0x1F1E6, 0x1F1FF + 1),  # regional indicator symbol letter a..regional indicator symbol letter z
    range(0x1F3FB, 0x1F3FF + 1),  # light skin tone..dark skin tone
    range(0x1F9B0, 0x1F9B3 + 1),  # red-haired..white-haired
    range(0xE0020, 0xE007F + 1),  # tag space..cancel tag
)
emoji_components = re.compile(u'({})'.format(u'|'.join([
    re.escape(uchr(c)) for r in _removable_emoji_components for c in r])),
    flags=re.UNICODE)

然后更新上面的remove_emoji()函数以使用它:

def remove_emoji(text, remove_components=False):
    cleaned = emoji.get_emoji_regexp().sub(u'', text)
    if remove_components:
        cleaned = emoji_components.sub(u'', cleaned)
    return cleaned

1
@ascii_walker:那是一个未配对的U+1F3FC EMOJI MODIFIER FITZPATRICK TYPE-3代码点。它本身是否是表情符号还有待商榷。 - Martijn Pieters
1
@ascii_walker:显然,这个表情包并�认为它是一个表情符�;如�你将它�支�的表情符��对,它就会被移除。例如,“🤟��就会被移除,因为它是U+1F91F U+1F3FC的组�,这就是该模�的使用方�。 - Martijn Pieters
@ascii_walker:完整的肤色修饰符列表请参见https://www.unicode.org/emoji/charts/full-emoji-modifiers.html;您需要单独为[5个音调组件代码点](https://www.unicode.org/emoji/charts/full-emoji-modifiers.html#component)创建一个正则表达式。 - Martijn Pieters
在将文本解码为 remove_emoji1("PlotFlatHouseSaleAdvt".decode("utf-8") 后,它会返回 'PlotFlatHouseSaleAdvt' - iamabhaykmr
2
@ascii_walker:没错,我假设你在使用Python 3(Python 2.7即将到达生命周期的尽头,你真的应该考虑升级!)。这个正则表达式旨在处理Unicode文本,在正则表达式中处理表情符号作为UTF-8序列会引起另一个巨大的问题。今天我不打算深入探讨这个问题。 - Martijn Pieters
显示剩余6条评论

1

0
如果您使用regex库而不是re库,则可以访问Unicode属性,然后您可以将函数更改为:
def remove_emoji(self, string):
    emoji_pattern = re.compile("[\P{L}&&\P{D}&&\P{Z}&&\P{M}]", flags=re.UNICODE)
    return emoji_pattern.sub(r'', string)

这将保留所有字母、数字、分隔符和标记(重音符号)


2
这个还是不够的。你忘了加上\P{P}SmSm 也应该可以; 表情符通常属于类别SoSk。除非有些不是,emoji.UNICODE_EMOJI中的表情序列属于类别Cf、Cn、Ll、Me、Mn、Nd、Pd、Po、Sk、Sm和So,因此你的模式实际上会保留一些表情符号 - Martijn Pieters
2
请注意,许多 Emoji 是由代码点组合而成的。例如,'\U0001f477\U0001f3ff\u200d\u2640\ufe0f'一个 Emoji:‍♀️。您的正则表达式会保留最后一个代码点,因此是 ♀️。这可能会令人困惑。 - Martijn Pieters
如果我没记错的话,Unicode 的最新版本有一个“emoji”属性,但我不知道它涵盖了哪些代码点。 - JGNI
Unicode 11有这样的与Emoji相关的属性,但您可能会遗留下Emoji_Component代码点。而且它们并不是UCD的正式部分。 - Martijn Pieters
1
还可以查看代码点具有哪些Emoji属性的完整列表。但请注意,该属性相当无用。数字具有该属性,#*也是如此。 - Martijn Pieters

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接