Python:在字边界上分割unicode字符串

Python: Split unicode string on word boundaries

我需要取一个字符串,并将其缩短为140个字符。

当前我正在做的事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if len(tweet) > 140:
    tweet = re.sub(r"\\s+","", tweet) #normalize space
    footer ="a€|" + utils.shorten_urls(post['url'])
    avail = 140 - len(footer)
    words = tweet.split()
    result =""
    for word in words:
        word +=""
        if len(word) > avail:
            break
        result += word
        avail -= len(word)
    tweet = (result + footer).strip()
    assert len(tweet) <= 140

因此,这对英语以及像字符串一样的英语非常有用,但是中文字符串失败,因为tweet.split()仅返回一个数组:

1
2
3
4
5
>>> s = u"??€è?ˉ????–°è?ˉ?¤??±é?"?????????????μ±?¥§?·′é|??1????????€???oè????€è???€??°??????????10???42???é€2?…¥????μ·??o??????é?è¨??′?30???é???????μé?"?μ|??±???é??????′???é–??±???–?????????é|–???è¨aè?ˉ?1??—…?€?"
>>> s
u'\\u7b80\\u8baf\\uff1a\\u65b0\\u83ef\\u793e\\u5831\\u9053\\uff0c\\u7f8e\\u570b\\u7e3d\\u7d71\\u5967\\u5df4\\u99ac\\u4e58\\u5750\\u7684\\u300c\\u7a7a\\u8ecd\\u4e00\\u865f\\u300d\\u5c08\\u6a5f\\u665a\\u4e0a10\\u664242\\u5206\\u9032\\u5165\\u4e0a\\u6d77\\u7a7a\\u57df\\uff0c\\u9810\\u8a08\\u7d0430\\u5206\\u9418\\u5f8c\\u62b5\\u9054\\u6d66\\u6771\\u570b\\u969b\\u6a5f\\u5834\\uff0c\\u958b\\u5c55\\u4ed6\\u4e0a\\u4efb\\u5f8c\\u9996\\u6b21\\u8a2a\\u83ef\\u4e4b\\u65c5\\u3002'
>>> s.split()
[u'\\u7b80\\u8baf\\uff1a\\u65b0\\u83ef\\u793e\\u5831\\u9053\\uff0c\\u7f8e\\u570b\\u7e3d\\u7d71\\u5967\\u5df4\\u99ac\\u4e58\\u5750\\u7684\\u300c\\u7a7a\\u8ecd\\u4e00\\u865f\\u300d\\u5c08\\u6a5f\\u665a\\u4e0a10\\u664242\\u5206\\u9032\\u5165\\u4e0a\\u6d77\\u7a7a\\u57df\\uff0c\\u9810\\u8a08\\u7d0430\\u5206\\u9418\\u5f8c\\u62b5\\u9054\\u6d66\\u6771\\u570b\\u969b\\u6a5f\\u5834\\uff0c\\u958b\\u5c55\\u4ed6\\u4e0a\\u4efb\\u5f8c\\u9996\\u6b21\\u8a2a\\u83ef\\u4e4b\\u65c5\\u3002']

我应该怎么做才能处理I18N?这对所有语言都有意义吗?

如果重要,我正在使用python 2.5.4。


中文在单词之间通常没有空格,并且根据上下文,符号可以具有不同的含义。您将必须理解文本才能在单词边界处进行拆分。换句话说,您通常想做的事情并不容易。


对于中文分词以及处理自然语言的其他高级任务,如果不是完整的解决方案,则可以将NLTK视为一个很好的起点-这是一个基于Python的丰富工具包,特别适合于学习NL处理技术(和往往不够好,无法为您解决其中一些问题)。


re.U标志将根据Unicode字符属性数据库处理\\s

根据python的unicode数据库,给定的字符串显然不包含任何空格字符:

1
2
3
>>> x = u'\\u7b80\\u8baf\\uff1a\\u65b0\\u83ef\\u793e\\u5831\\u9053\\uff0c\\u7f8e\\u570b\\u7e3d\\u7d71\\u5967\\u5df4\\u99ac\\u4e58\\u5750\\u7684\\u300c\\u7a7a\\u8ecd\\u4e00\\u865f\\u300d\\u5c08\\u6a5f\\u665a\\u4e0a10\\u664242\\u5206\\u9032\\u5165\\u4e0a\\u6d77\\u7a7a\\u57df\\uff0c\\u9810\\u8a08\\u7d0430\\u5206\\u9418\\u5f8c\\u62b5\\u9054\\u6d66\\u6771\\u570b\\u969b\\u6a5f\\u5834\\uff0c\\u958b\\u5c55\\u4ed6\\u4e0a\\u4efb\\u5f8c\\u9996\\u6b21\\u8a2a\\u83ef\\u4e4b\\u65c5\\u3002'
>>> re.compile(r'\\s+', re.U).split(x)
[u'\\u7b80\\u8baf\\uff1a\\u65b0\\u83ef\\u793e\\u5831\\u9053\\uff0c\\u7f8e\\u570b\\u7e3d\\u7d71\\u5967\\u5df4\\u99ac\\u4e58\\u5750\\u7684\\u300c\\u7a7a\\u8ecd\\u4e00\\u865f\\u300d\\u5c08\\u6a5f\\u665a\\u4e0a10\\u664242\\u5206\\u9032\\u5165\\u4e0a\\u6d77\\u7a7a\\u57df\\uff0c\\u9810\\u8a08\\u7d0430\\u5206\\u9418\\u5f8c\\u62b5\\u9054\\u6d66\\u6771\\u570b\\u969b\\u6a5f\\u5834\\uff0c\\u958b\\u5c55\\u4ed6\\u4e0a\\u4efb\\u5f8c\\u9996\\u6b21\\u8a2a\\u83ef\\u4e4b\\u65c5\\u3002']


我尝试使用PyAPNS的推送通知解决方案,只是想分享对我有用的解决方案。我遇到的问题是,在UTF-8中以256个字节截断会导致删除通知。我必须确保将通知编码为" unicode_escape "才能使其正常工作。我假设这是因为结果以JSON而非原始UTF-8的形式发送。无论如何,这里是对我有用的功能:

1
2
3
def unicode_truncate(s, length, encoding='unicode_escape'):
    encoded = s.encode(encoding)[:length]
    return encoded.decode(encoding, 'ignore')

基本上,在CJK(带空格的韩语除外)中,您需要字典查找才能正确分割单词。根据您对"单词"的确切定义,日语可能会比这更困难,因为并非所有单词的变体形式(即"è????" ??? "与"è?? ?? £ ??? ")将出现在字典中。是否值得付出努力取决于您的应用程序。


在与一些以粤语,普通话和日语为母语的人交谈之后,看来正确的事情很难,但是在互联网帖子的背景下,我目前的算法仍然对他们有意义。

意思是,它们习惯于"在空间上分割并在末尾添加a ||"。

所以我要懒惰并坚持下去,直到我收到了一些不了解它的人的抱怨。

对我原来的实现的唯一更改是不要在最后一个单词上加上空格,因为在任何语言中都不需要它(并使用unicode字符a €| 代替... three dots以保存2个字符)


您正在寻找的是中文分词工具。分词不是一件容易的事,目前还不能很好地解决。有几种工具:

  • CkipTagger

    由台湾中央研究院开发。

  • jieba

    由Sun开发百度工程师Junyi。

  • pkuseg

    北京大学语言计算和机器学习小组开发的

  • 如果您要的是字符分割,尽管不是很有用,但可以做到。

    1
    2
    3
    4
    5
    6
    >>> s = u"??€è?ˉ????–°è?ˉ?¤??±é?"?????????????μ±?¥§?·′é|??1????????€???oè????€è???€??°??????????10???42???é€2?…¥????μ·??o??????é?è¨??′?30???é???????μé?"?μ|??±???é??????′???é–??±???–?????????é|–???è¨aè?ˉ?1??—…?€?"
    >>> chars = list(s)
    >>> chars
    [u'\\u7b80', u'\\u8baf', u'\\uff1a', u'\\u65b0', u'\\u83ef', u'\\u793e', u'\\u5831', u'\\u9053', u'\\uff0c', u'\\u7f8e', u'\\u570b', u'\\u7e3d', u'\\u7d71', u'\\u5967', u'\\u5df4', u'\\u99ac', u'\\u4e58', u'\\u5750', u'\\u7684', u'\\u300c', u'\\u7a7a', u'\\u8ecd', u'\\u4e00', u'\\u865f', u'\\u300d', u'\\u5c08', u'\\u6a5f', u'\\u665a', u'\\u4e0a', u'1', u'0', u'\\u6642', u'4', u'2', u'\\u5206', u'\\u9032', u'\\u5165', u'\\u4e0a', u'\\u6d77', u'\\u7a7a', u'\\u57df', u'\\uff0c', u'\\u9810', u'\\u8a08', u'\\u7d04', u'3', u'0', u'\\u5206', u'\\u9418', u'\\u5f8c', u'\\u62b5', u'\\u9054', u'\\u6d66', u'\\u6771', u'\\u570b', u'\\u969b', u'\\u6a5f', u'\\u5834', u'\\uff0c', u'\\u958b', u'\\u5c55', u'\\u4ed6', u'\\u4e0a', u'\\u4efb', u'\\u5f8c', u'\\u9996', u'\\u6b21', u'\\u8a2a', u'\\u83ef', u'\\u4e4b', u'\\u65c5', u'\\u3002']
    >>> print('/'.join(chars))
    ??€/è?ˉ/???/?–°/è?ˉ/?¤?/?±/é?"/???/???/???/???/?μ±/?¥§/?·′/é|?/?1?/???/???/?€?/??o/è??/??€/è??/?€?/?°?/???/???/???/1/0/???/4/2/???/é€2/?…¥/???/?μ·/??o/???/???/é?/è¨?/?′?/3/0/???/é??/???/??μ/é?"/?μ|/??±/???/é??/???/?′/???/é–?/?±?/??–/???/???/???/é|–/???/è¨a/è?ˉ/?1?/?—…/?€?

    这会把断字的决定推到re模块上,但它可能对您来说足够好。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import re

    def shorten(tweet, footer="", limit=140):
       """Break tweet into two pieces at roughly the last word break
        before limit.
       """

        lower_break_limit = limit / 2
        # limit under which to assume breaking didn't work as expected

        limit -= len(footer)

        tweet = re.sub(r"\\s+","", tweet.strip())
        m = re.match(r"^(.{,%d})\\b(?:\\W|$)" % limit, tweet, re.UNICODE)
        if not m or m.end(1) < lower_break_limit:
            # no suitable word break found
            # cutting at an arbitrary location,
            # or if len(tweet) < lower_break_limit, this will be true and
            # returning this still gives the desired result
            return tweet[:limit] + footer
        return m.group(1) + footer


    保存两个字符并使用省略号(a€|,0x2026)代替三个点!