Slugification de string em Python

92

Estou em busca da melhor maneira de "slugify" string o que é "slug" , e minha solução atual é baseada nesta receita

Eu mudei um pouco para:

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)

Alguém viu algum problema com este código? Está funcionando bem, mas talvez eu esteja faltando alguma coisa ou você conhece uma maneira melhor?

Zygimantas
fonte
você está trabalhando muito com Unicode? se for o caso, o último re.sub pode ser melhor se você envolver unicode () em torno dele. Isso é o que django faz. Além disso, o [^ a-z0-9] + pode ser encurtado para usar \ w. veja django.template.defaultfilters, é parecido com o seu, mas um pouco mais refinado.
Mike Ramirez,
São permitidos caracteres Unicode no URL? Além disso, alterei \ w para a-z0-9 porque \ w inclui _ caracteres e letras maiúsculas. As letras são definidas para minúsculas antecipadamente, portanto, não haverá letras maiúsculas para corresponder.
Zygimantas
'_' é válido (mas sua escolha, você perguntou), unicode é como caracteres codificados por cento.
Mike Ramirez,
Obrigado Mike. Bem, eu fiz uma pergunta errada. Existe alguma razão para codificá-lo de volta para string Unicode, se já substituímos todos os caracteres, exceto "az", "0-9" e "-"?
Zygimantas de
Para django, acredito que seja importante para eles ter todas as strings como objetos Unicode para compatibilidade. É sua escolha se você quiser.
Mike Ramirez,

Respostas:

144

Existe um pacote python chamado python-slugify, que faz um bom trabalho de slugifying:

pip install python-slugify

Funciona assim:

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")

Veja mais exemplos

Este pacote faz um pouco mais do que o que você postou (dê uma olhada no código-fonte, é apenas um arquivo). O projeto ainda está ativo (foi atualizado 2 dias antes de eu responder originalmente, mais de sete anos depois (verificado pela última vez em 2020-06-30), ele ainda é atualizado).

cuidado : existe um segundo pacote, chamado slugify. Se você tiver os dois, poderá ter um problema, pois eles têm o mesmo nome para importação. O que acabou de ser nomeado slugifynão fez tudo o que eu verifiquei rapidamente: "Ich heiße"tornou - se "ich-heie"(deveria ser "ich-heisse"), portanto, certifique-se de escolher o certo ao usar pipou easy_install.

Kratenko
fonte
6
python-slugifyé licenciado pelo MIT, mas usa o Unidecodeque é licenciado pela GPL, portanto, pode não ser adequado para alguns projetos.
Rotareti
@Rotareti Você poderia me explicar por que não cabia em todos os projetos? Não podemos usar nada sob licença MIT ou GPL e incluí-los dentro de software comercial? Acho que a única restrição é colocar a licença além dos códigos que desenvolvemos. Estou errado?
Ghassem Tofighi
1
@GhassemTofighi Resumindo: você pode usá-lo em seu software comercial, mas se usá-lo, deverá abrir o código-fonte também. Enfim, IANAL e este não é um conselho jurídico.
Rotareti
@GhassemTofighi talvez dê uma olhada em softwareengineering.stackexchange.com/q/47032/71504 sobre esse assunto
kratenko
1
@Rotareti python-slugifyagora usa a Licença Artística em text-unidecodevez da licença GPL Unidecode, abordando sua preocupação com o licenciamento. github.com/un33k/python-slugify/commit/…
Emilien
31

Instale o formulário unidecode aqui para suporte Unicode

pip instalar 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

user1078810
fonte
1
oi, é um pouco estranho, mas dá para a minha resolução assim "my-custom-ndud-d-d3-4-d2d3-4nd-d-"
derevo
1
@derevo que acontece quando você não envia strings Unicode. Substitua slugify("My custom хелло ворлд")por slugify(u"My custom хелло ворлд")e deve funcionar.
Kratenko
9
Eu sugeriria contra o uso de nomes de variáveis ​​como str. Isso oculta o strtipo embutido .
crodjer
2
unidecode é GPL, o que pode não ser adequado para alguns.
Jorge Leitão
E quanto à reslugificação ou deslugificação.
Ryan Chou
11

Existe um pacote python chamado awesome-slugify :

pip install awesome-slugify

Funciona assim:

from slugify import slugify

slugify('one kožušček')  # one-kozuscek

página github incrível-slugify

voronina
fonte
2
Belo pacote! Mas tenha cuidado, ele está licenciado sob GPL.
Rotareti
1
Atenção: isso não irá diminuir automaticamente () seus urls. Você precisará correr slugify(text).lower()se quiser.
Kalob Taulien
6

Funciona bem no Django , então não vejo por que não seria uma boa função slugify de propósito geral.

Você está tendo problemas com isso?

Nick Presta
fonte
É possível que, para alguns casos, seja uma dose saudável de paranóia :-)
nemesisfixx
O código foi movido para aqui .
raylu
12
Para os preguiçosos:from django.utils.text import slugify
Spartacus
6

O problema é com a linha de normalização ASCII:

slug = unicodedata.normalize('NFKD', s)

É chamado de normalização Unicode, que não decompõe muitos caracteres em ascii. Por exemplo, retiraria caracteres não ascii das seguintes strings:

Mørdag -> mrdag
Æther -> ther

A melhor maneira de fazer isso é usar o módulo unidecode que tenta transliterar strings para ascii. Portanto, se você substituir a linha acima por:

import unidecode
slug = unidecode.unidecode(s)

Você obtém melhores resultados para as strings acima e também para muitos caracteres gregos e russos:

Mørdag -> mordag
Æther -> aether
Björn Lindqvist
fonte
5
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)

Esta é a função slugify presente em django.utils.text. Deve ser suficiente para seus requisitos.

Animesh Sharma
fonte
3

O unidecode é bom; no entanto, tenha cuidado: o unidecode é GPL. Se esta licença não se adequar, use esta

Mikhail Korobov
fonte
2

Algumas opções no GitHub:

  1. https://github.com/dimka665/awesome-slugify
  2. https://github.com/un33k/python-slugify
  3. https://github.com/mozilla/unicode-slugify

Cada um oferece suporte a parâmetros ligeiramente diferentes para sua API, então você precisará dar uma olhada para descobrir o que prefere.

Em particular, preste atenção às diferentes opções que eles fornecem para lidar com caracteres não ASCII. Pydanny escreveu uma postagem de blog muito útil ilustrando algumas das diferenças de manipulação de Unicode nessas bibliotecas de slugify: http://www.pydanny.com/awesome-slugify-human-readable-url-slugs-from-any-string.html Esta postagem do blog está um pouco desatualizada porque o Mozilla unicode-slugifynão é mais específico do Django.

Observe também que atualmente awesome-slugifyé GPLv3, embora haja um problema aberto em que o autor diz que prefere lançar como MIT / BSD, mas não tem certeza da legalidade: https://github.com/dimka665/awesome-slugify/issues/ 24

Jeff Widman
fonte
1

Você pode considerar alterar a última linha para

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

já que o padrão [-]+não é diferente de -+, e você realmente não se preocupa em combinar apenas um hífen, apenas dois ou mais.

Mas, é claro, isso é muito pequeno.

unutbu
fonte