Python中的字符串规范化

138
我正在寻找最佳的方法来“slugify”字符串,了解“slug”是什么,我的当前解决方案基于这个示例。我稍微改了一下它:
s = 'String to slugify'

slug = unicodedata.normalize('NFKD', s)
slug = slug.encode('ascii', 'ignore').lower()
slug = re.sub(r'[^a-z0-9]+', '-', slug).strip('-')
slug = re.sub(r'[-]+', '-', slug)

有人看到这段代码有问题吗?它可以正常工作,但是我可能漏掉了什么或者您知道更好的方法吗?


1
你经常使用Unicode吗?如果是这样,最后一个re.sub可能更好,如果你在它周围包装unicode()。这就是Django所做的。此外,[^a-z0-9]+可以缩短为使用\w。看看django.template.defaultfilters,它与你的代码很接近,但更加精细。 - Mike Ramirez
URL中允许使用Unicode字符吗?此外,我已将\w更改为a-z0-9,因为\w包括下划线字符和大写字母。字母提前设置为小写,因此不会有大写字母匹配。 - Zygimantas
'_'是有效的(但这是你的选择,你问了),Unicode以百分比编码字符表示。 - Mike Ramirez
谢谢你,Mike。嗯,我问错问题了。如果我们已经替换了除“a-z”、“0-9”和“-”之外的所有字符,那么将其重新编码为Unicode字符串是否有任何理由? - Zygimantas
在 Python 3.6 中,我需要将解码添加回字符串:slug = slug.encode('ascii', 'ignore').lower().decode('ascii') 否则 slug 变量会转换为二进制并且 re.sub 会引发异常。 - Mr. Girgitt
显示剩余2条评论
12个回答

206

有一个名为python-slugify的Python软件包,可以很好地将文本转换为slug格式:

pip install python-slugify

这是工作原理:
from slugify import slugify

txt = "This is a test ---"
r = slugify(txt)
self.assertEquals(r, "this-is-a-test")

txt = "This -- is a ## test ---"
r = slugify(txt)
self.assertEquals(r, "this-is-a-test")

txt = 'C\'est déjà l\'été.'
r = slugify(txt)
self.assertEquals(r, "cest-deja-lete")

txt = 'Nín hǎo. Wǒ shì zhōng guó rén'
r = slugify(txt)
self.assertEquals(r, "nin-hao-wo-shi-zhong-guo-ren")

txt = 'Компьютер'
r = slugify(txt)
self.assertEquals(r, "kompiuter")

txt = 'jaja---lol-méméméoo--a'
r = slugify(txt)
self.assertEquals(r, "jaja-lol-mememeoo-a")

请参阅更多示例

这个软件包比你发布的内容做了更多的事情(看一下源代码,它只有一个文件)。该项目仍然活跃(我最初回答时已更新2天,超过9年后(最后检查时间为2022-03-30),它仍在更新)。

注意:还有一个名为slugify的第二个软件包。如果你两个都有,可能会出现问题,因为它们导入时具有相同的名称。只命名为slugify的那一个没有做到我快速检查的所有事情:"Ich heiße" 变成了 "ich-heie" (应该是 "ich-heisse"),所以在使用pipeasy_install时,请确保选择正确的软件包。


7
python-slugify 是根据 MIT 许可证发布的,但它使用了根据 GPL 许可证发布的 Unidecode 库,因此可能不适合某些项目。 - Rotareti
@Rotareti,您能否解释一下为什么它无法适用于所有项目?我们不能使用任何MIT或GPL许可证并将它们包含在商业软件中吗?我认为唯一的限制是在我们开发的代码旁边放置许可证。我错了吗? - Ghassem Tofighi
1
@GhassemTofighi 简而言之:您可以在商业软件中使用它,但如果您使用它,则必须开源您的代码。无论如何,我不是律师,这不是法律建议。 - Rotareti
@GhassemTofighi 可以看一下这个主题的 https://softwareengineering.stackexchange.com/q/47032/71504 - kratenko
3
@Rotareti python-slugify 现在默认采用艺术许可证的 text-unidecode 而非 GPL 许可证的 Unidecode,以解决您的许可证问题。 https://github.com/un33k/python-slugify/commit/b8be7d69119dcceb9a3e0ce64a509415737190ac#diff-e4156a8bee1b298082516842836621b9 - Emilien

41

这里安装unidecode以支持Unicode。

pip install unidecode

# -*- coding: utf-8 -*-
import re
import unidecode

def slugify(text):
    text = unidecode.unidecode(text).lower()
    return re.sub(r'[\W_]+', '-', text)

text = u"My custom хелло ворлд"
print slugify(text)

>>> my-custom-khello-vorld


1
嗨,这有点奇怪,但它给我的结果是这样的:“my-custom-ndud-d-d3-4-d2d3-4nd-d-”。 - derevo
1
@derevo 当你不发送Unicode字符串时就会发生这种情况。将 slugify("My custom хелло ворлд") 替换为 slugify(u"My custom хелло ворлд"),它应该可以工作了。 - kratenko
11
建议避免使用变量名 str,因为这会隐藏内置的 str 类型。 - crodjer
2
unidecode是GPL许可的,这可能不适合某些人。 - Jorge Leitao
重命名或取消重命名怎么样? - Ryan Chou
显示剩余2条评论

12

4
包装很不错!但要小心,它是在GPL许可下发布的。 - Rotareti
1
注意:这不会自动将您的URL转换为小写。如果您想要这样做,您需要运行 slugify(text).lower() - Kalob Taulien

9
def slugify(value):
    """
    Converts to lowercase, removes non-word characters (alphanumerics and
    underscores) and converts spaces to hyphens. Also strips leading and
    trailing whitespace.
    """
    value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
    value = re.sub('[^\w\s-]', '', value).strip().lower()
    return mark_safe(re.sub('[-\s]+', '-', value))
slugify = allow_lazy(slugify, six.text_type)

这是django.utils.text中的slugify函数,可以满足您的需求。

8
问题出在ASCII规范化行上:
slug = unicodedata.normalize('NFKD', s)

这被称为Unicode标准化(Unicode normalization),它不会将许多字符分解为ASCII。例如,它将从以下字符串中剥离非ASCII字符:

Mørdag -> mrdag
Æther -> ther

更好的方法是使用unidecode模块,它试图将字符串转换为ASCII编码。因此,如果您将上述行替换为:
import unidecode
slug = unidecode.unidecode(s)

对于上述字符串以及许多希腊和俄罗斯字符,您可以获得更好的结果:

Mørdag -> mordag
Æther -> aether

8

它在Django中运行良好,所以我不明白为什么它不能成为一个很好的通用slugify函数。

你有遇到任何问题吗?


代码已经移动到这里 - raylu
24
对于懒人来说:from django.utils.text import slugify - Spartacus

4

Unidecode很不错,但是要小心:unidecode是GPL许可证。如果这个许可证不适合您,请使用这个


3
在GitHub上有几个选项:
  1. https://github.com/dimka665/awesome-slugify
  2. https://github.com/un33k/python-slugify
  3. https://github.com/mozilla/unicode-slugify
每个选项都支持稍微不同的API参数,因此您需要查看以确定您喜欢哪种。特别是要注意它们为处理非ASCII字符提供的不同选项。 Pydanny撰写了一篇非常有用的博客文章,说明这些slugify库中的一些Unicode处理差异:http://www.pydanny.com/awesome-slugify-human-readable-url-slugs-from-any-string.html 这篇博客文章略有过时,因为Mozilla的unicode-slugify不再仅限于Django。
还要注意目前awesome-slugify是GPLv3,尽管作者在一个开放问题中表示他们更喜欢发布为MIT / BSD,只是不确定合法性:https://github.com/dimka665/awesome-slugify/issues/24

1
你可以考虑将最后一行改为:

slug=re.sub(r'--+',r'-',slug)

由于模式[-]+-+没有区别,而且您并不真正关心匹配一个连字符,只有两个或更多的情况。

但是,当然,这相当微小。


1

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