Slugificación de cadenas en Python

97

Estoy buscando la mejor manera de "slugify" string lo que es "slug" , y mi solución actual se basa en esta receta

Lo he cambiado un poco a:

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)

¿Alguien ve algún problema con este código? Está funcionando bien, pero ¿tal vez me falta algo o conoces una mejor manera?

Zygimantas
fuente
¿Estás trabajando mucho con Unicode? si es así, el último re.sub podría ser mejor si envuelve unicode () alrededor de él. Esto es lo que hace django. Además, el [^ a-z0-9] + se puede acortar para usar \ w. vea django.template.defaultfilters, está cerca del suyo, pero un poco más refinado.
Mike Ramirez
¿Se permiten los caracteres Unicode en la URL? Además, he cambiado \ w a a-z0-9 porque \ w incluye el carácter _ y letras mayúsculas. Las letras se establecen en minúsculas de antemano, por lo que no habrá letras mayúsculas para coincidir.
Zygimantas
'_' es válido (pero tu elección, preguntaste), Unicode es un porcentaje de caracteres codificados.
Mike Ramirez
Gracias Mike. Bueno, hice una pregunta incorrecta. ¿Hay alguna razón para volver a codificarlo en una cadena Unicode, si ya reemplazamos todos los caracteres excepto "az", "0-9" y "-"?
Zygimantas
Para django, creo que es importante para ellos tener todas las cadenas como objetos Unicode por compatibilidad. Es tu elección si quieres esto.
Mike Ramirez

Respuestas:

146

Hay un paquete de Python llamado python-slugify, que hace un buen trabajo de slugificar:

pip install python-slugify

Funciona así:

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

Ver más ejemplos

Este paquete hace un poco más de lo que publicó (eche un vistazo a la fuente, es solo un archivo). El proyecto todavía está activo (se actualizó 2 días antes de que respondiera originalmente, más de siete años después (última consulta 2020-06-30), todavía se actualiza).

cuidado : hay un segundo paquete, llamado slugify. Si tiene ambos, es posible que tenga un problema, ya que tienen el mismo nombre para la importación. El que acabo de nombrar slugifyno hizo todo lo que verifiqué rápidamente: se "Ich heiße"convirtió "ich-heie"(debería ser "ich-heisse"), así que asegúrese de elegir el correcto cuando use pipo easy_install.

Kratenko
fuente
6
python-slugifytiene licencia de MIT, pero utiliza Unidecodela licencia de GPL, por lo que puede que no sea adecuada para algunos proyectos.
Rotareti
@Rotareti ¿Podría explicarme por qué no se ajusta a todos los proyectos? ¿No podemos usar nada bajo licencia MIT o GPL e incluirlos dentro del software comercial? Creo que la única restricción es poner la licencia además de los códigos que desarrollamos. ¿Me equivoco?
Ghassem Tofighi
1
@GhassemTofighi En resumen: puede usarlo en su software comercial, pero si lo usa, también debe abrir el código fuente. De todos modos IANAL y esto no es un consejo legal.
Rotareti
@GhassemTofighi tal vez eche un vistazo a softwareengineering.stackexchange.com/q/47032/71504 sobre ese tema
kratenko
1
@Rotareti python-slugifyahora utiliza de forma predeterminada la licencia artística en text-unidecodelugar de la licencia GPL Unidecode, lo que aborda su problema de licencia. github.com/un33k/python-slugify/commit/…
Emilien
31

Instale el formulario unidecode desde aquí para compatibilidad con 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)

>>> mi-khello-vorld personalizado

usuario1078810
fuente
1
hola, es un poco extraño pero da para mi resolución como esa "my-custom-ndud-d-d3-4-d2d3-4nd-d-"
derevo
1
@derevo que sucede cuando no envías cadenas Unicode. Reemplace slugify("My custom хелло ворлд")con slugify(u"My custom хелло ворлд"), y debería funcionar.
Kratenko
9
Sugeriría no usar nombres de variables como str. Esto oculta el strtipo incorporado .
crodjer
2
unidecode es GPL, que puede no ser adecuado para algunos.
Jorge Leitao
¿Qué hay de reslugificar o deslugificar?
Ryan Chou
11

Hay un paquete de Python llamado awesome-slugify :

pip install awesome-slugify

Funciona así:

from slugify import slugify

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

página de github impresionante-slugify

voronin
fuente
2
¡Lindo paquete! Pero tenga cuidado, tiene licencia GPL.
Rotareti
1
Aviso: esto no reducirá automáticamente () sus URL. Necesitarás correr slugify(text).lower()si quieres eso.
Kalob Taulien
7

Funciona bien en Django , así que no veo por qué no sería una buena función slugify de propósito general.

¿Tiene algún problema con él?

Nick Presta
fuente
Es posible que, en algunos casos, sea una dosis saludable de paranoia :-)
nemesisfixx
El código se ha trasladado a aquí .
raylu
13
Para los perezosos:from django.utils.text import slugify
Espartaco
6

El problema es con la línea de normalización ascii:

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

Se llama normalización unicode, que no descompone muchos caracteres en ascii. Por ejemplo, eliminaría los caracteres no ascii de las siguientes cadenas:

Mørdag -> mrdag
Æther -> ther

Una mejor manera de hacerlo es usar el módulo unidecode que intenta transliterar cadenas a ascii. Entonces, si reemplaza la línea anterior con:

import unidecode
slug = unidecode.unidecode(s)

Obtiene mejores resultados para las cadenas anteriores y también para muchos caracteres griegos y rusos:

Mørdag -> mordag
Æther -> aether
Björn Lindqvist
fuente
6
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 es la función slugify presente en django.utils.text Esto debería ser suficiente para su requisito.

Animesh Sharma
fuente
3

Unidecode es bueno; sin embargo, tenga cuidado: unidecode es GPL. Si esta licencia no encaja, use esta

Mikhail Korobov
fuente
2

Un par de opciones en GitHub:

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

Cada uno admite parámetros ligeramente diferentes para su API, por lo que deberá revisar para averiguar qué prefiere.

En particular, preste atención a las diferentes opciones que ofrecen para tratar con caracteres no ASCII. Pydanny escribió una publicación de blog muy útil que ilustra algunas de las diferencias de manejo de Unicode en estas bibliotecas de slugify'ing: http://www.pydanny.com/awesome-slugify-human-readable-url-slugs-from-any-string.html Esta publicación de blog está un poco desactualizada porque Mozilla'sunicode-slugify ya no es específica de Django.

También tenga en cuenta que actualmente awesome-slugifyes GPLv3, aunque hay un problema abierto en el que el autor dice que preferiría publicarlo como MIT / BSD, pero no estoy seguro de la legalidad: https://github.com/dimka665/awesome-slugify/issues/ 24

Jeff Widman
fuente
1

Podría considerar cambiar la última línea a

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

ya que el patrón [-]+no es diferente de -+, y realmente no le importa hacer coincidir solo un guión, solo dos o más.

Pero, por supuesto, esto es bastante menor.

unutbu
fuente