¿La mejor manera de convertir una URL Unicode a ASCII (UTF-8 por ciento escapado) en Python?

StackOverflow https://stackoverflow.com/questions/804336

  •  03-07-2019
  •  | 
  •  

Pregunta

Me pregunto cuál es la mejor manera, o si existe una forma simple con la biblioteca estándar, para convertir una URL con caracteres Unicode en el nombre de dominio y la ruta a la URL ASCII equivalente, codificada con dominio como IDNA y la ruta codificada en%, según RFC 3986.

Recibo del usuario una URL en UTF-8. Entonces, si escribieron http: // & # 10145; .ws / & # 9829; obtengo 'http: // \ xe2 \ x9e \ xa1.ws/ \ xe2 \ x99 \ xa5 ' en Python. Y lo que quiero es la versión ASCII: 'http://xn--hgi.ws/%E2%99%A5' .

Lo que hago en este momento es dividir la URL en partes a través de una expresión regular, y luego codificar IDNA manualmente el dominio, y codificar por separado la ruta y la cadena de consulta con diferentes urllib.quote () llamadas.

# url is UTF-8 here, eg: url = u'http://➡.ws/㉌'.encode('utf-8')
match = re.match(r'([a-z]{3,5})://(.+\.[a-z0-9]{1,6})'
                 r'(:\d{1,5})?(/.*?)(\?.*)?

¿Es esto correcto? ¿Alguna sugerencia mejor? ¿Existe una función simple de biblioteca estándar para hacer esto?

, url, flags=re.I) if not match: raise BadURLException(url) protocol, domain, port, path, query = match.groups() try: domain = unicode(domain, 'utf-8') except UnicodeDecodeError: return '' # bad UTF-8 chars in domain domain = domain.encode('idna') if port is None: port = '' path = urllib.quote(path) if query is None: query = '' else: query = urllib.quote(query, safe='=&?/') url = protocol + '://' + domain + port + path + query # url is ASCII here, eg: url = 'http://xn--hgi.ws/%E3%89%8C'

¿Es esto correcto? ¿Alguna sugerencia mejor? ¿Existe una función simple de biblioteca estándar para hacer esto?

¿Fue útil?

Solución

Código:

import urlparse, urllib

def fixurl(url):
    # turn string into unicode
    if not isinstance(url,unicode):
        url = url.decode('utf8')

    # parse it
    parsed = urlparse.urlsplit(url)

    # divide the netloc further
    userpass,at,hostport = parsed.netloc.rpartition('@')
    user,colon1,pass_ = userpass.partition(':')
    host,colon2,port = hostport.partition(':')

    # encode each component
    scheme = parsed.scheme.encode('utf8')
    user = urllib.quote(user.encode('utf8'))
    colon1 = colon1.encode('utf8')
    pass_ = urllib.quote(pass_.encode('utf8'))
    at = at.encode('utf8')
    host = host.encode('idna')
    colon2 = colon2.encode('utf8')
    port = port.encode('utf8')
    path = '/'.join(  # could be encoded slashes!
        urllib.quote(urllib.unquote(pce).encode('utf8'),'')
        for pce in parsed.path.split('/')
    )
    query = urllib.quote(urllib.unquote(parsed.query).encode('utf8'),'=&?/')
    fragment = urllib.quote(urllib.unquote(parsed.fragment).encode('utf8'))

    # put it back together
    netloc = ''.join((user,colon1,pass_,at,host,colon2,port))
    return urlparse.urlunsplit((scheme,netloc,path,query,fragment))

print fixurl('http://\xe2\x9e\xa1.ws/\xe2\x99\xa5')
print fixurl('http://\xe2\x9e\xa1.ws/\xe2\x99\xa5/%2F')
print fixurl(u'http://Åsa:abc123@➡.ws:81/admin')
print fixurl(u'http://➡.ws/admin')

Salida:

  

http://xn--hgi.ws/%E2%99%A5
   http://xn--hgi.ws/%E2%99%A5/%2F
   http: //% C3% 85sa: abc123@xn--hgi.ws: 81 / admin
  http://xn--hgi.ws/admin

Leer más:

Ediciones:

  • Se corrigió el caso de los caracteres ya citados en la cadena.
  • Cambió urlparse / urlunparse a urlsplit / urlunsplit .
  • No codifique la información de usuario y puerto con el nombre de host. (Gracias Jehiah)
  • Cuando " @ " falta, no trate el host / puerto como usuario / pase! (Gracias hupf)

Otros consejos

el código proporcionado por MizardX no es 100% correcto. Este ejemplo no funcionará:

example.com/folder/?page=2

consulte django.utils.encoding.iri_to_uri () para convertir la URL unicode en URL ASCII.

http://docs.djangoproject.com/en/dev/ref/unicode /

hay algún RFC-3896 url parsing trabajo en curso (por ejemplo, como parte del Summer Of Code) pero nada en la biblioteca estándar todavía AFAIK - y nada más en la codificación uri lado de las cosas tampoco, de nuevo AFAIK. Por lo tanto, también puede ir con el enfoque elegante de MizardX.

De acuerdo, con estos comentarios y algunas correcciones de errores en mi propio código (no manejó fragmentos en absoluto), se me ocurrió la siguiente función canonurl () : devuelve Una forma ASCII canónica de la URL:

import re
import urllib
import urlparse

def canonurl(url):
    r"""Return the canonical, ASCII-encoded form of a UTF-8 encoded URL, or ''
    if the URL looks invalid.

    >>> canonurl('    ')
    ''
    >>> canonurl('www.google.com')
    'http://www.google.com/'
    >>> canonurl('bad-utf8.com/path\xff/file')
    ''
    >>> canonurl('svn://blah.com/path/file')
    'svn://blah.com/path/file'
    >>> canonurl('1234://badscheme.com')
    ''
    >>> canonurl('bad$scheme://google.com')
    ''
    >>> canonurl('site.badtopleveldomain')
    ''
    >>> canonurl('site.com:badport')
    ''
    >>> canonurl('http://123.24.8.240/blah')
    'http://123.24.8.240/blah'
    >>> canonurl('http://123.24.8.240:1234/blah?q#f')
    'http://123.24.8.240:1234/blah?q#f'
    >>> canonurl('\xe2\x9e\xa1.ws')  # tinyarro.ws
    'http://xn--hgi.ws/'
    >>> canonurl('  http://www.google.com:80/path/file;params?query#fragment  ')
    'http://www.google.com:80/path/file;params?query#fragment'
    >>> canonurl('http://\xe2\x9e\xa1.ws/\xe2\x99\xa5')
    'http://xn--hgi.ws/%E2%99%A5'
    >>> canonurl('http://\xe2\x9e\xa1.ws/\xe2\x99\xa5/pa%2Fth')
    'http://xn--hgi.ws/%E2%99%A5/pa/th'
    >>> canonurl('http://\xe2\x9e\xa1.ws/\xe2\x99\xa5/pa%2Fth;par%2Fams?que%2Fry=a&b=c')
    'http://xn--hgi.ws/%E2%99%A5/pa/th;par/ams?que/ry=a&b=c'
    >>> canonurl('http://\xe2\x9e\xa1.ws/\xe2\x99\xa5?\xe2\x99\xa5#\xe2\x99\xa5')
    'http://xn--hgi.ws/%E2%99%A5?%E2%99%A5#%E2%99%A5'
    >>> canonurl('http://\xe2\x9e\xa1.ws/%e2%99%a5?%E2%99%A5#%E2%99%A5')
    'http://xn--hgi.ws/%E2%99%A5?%E2%99%A5#%E2%99%A5'
    >>> canonurl('http://badutf8pcokay.com/%FF?%FE#%FF')
    'http://badutf8pcokay.com/%FF?%FE#%FF'
    >>> len(canonurl('google.com/' + 'a' * 16384))
    4096
    """
    # strip spaces at the ends and ensure it's prefixed with 'scheme://'
    url = url.strip()
    if not url:
        return ''
    if not urlparse.urlsplit(url).scheme:
        url = 'http://' + url

    # turn it into Unicode
    try:
        url = unicode(url, 'utf-8')
    except UnicodeDecodeError:
        return ''  # bad UTF-8 chars in URL

    # parse the URL into its components
    parsed = urlparse.urlsplit(url)
    scheme, netloc, path, query, fragment = parsed

    # ensure scheme is a letter followed by letters, digits, and '+-.' chars
    if not re.match(r'[a-z][-+.a-z0-9]*, scheme, flags=re.I):
        return ''
    scheme = str(scheme)

    # ensure domain and port are valid, eg: sub.domain.<1-to-6-TLD-chars>[:port]
    match = re.match(r'(.+\.[a-z0-9]{1,6})(:\d{1,5})?, netloc, flags=re.I)
    if not match:
        return ''
    domain, port = match.groups()
    netloc = domain + (port if port else '')
    netloc = netloc.encode('idna')

    # ensure path is valid and convert Unicode chars to %-encoded
    if not path:
        path = '/'  # eg: 'http://google.com' -> 'http://google.com/'
    path = urllib.quote(urllib.unquote(path.encode('utf-8')), safe='/;')

    # ensure query is valid
    query = urllib.quote(urllib.unquote(query.encode('utf-8')), safe='=&?/')

    # ensure fragment is valid
    fragment = urllib.quote(urllib.unquote(fragment.encode('utf-8')))

    # piece it all back together, truncating it to a maximum of 4KB
    url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
    return url[:4096]

if __name__ == '__main__':
    import doctest
    doctest.testmod()

Puede usar urlparse.urlsplit en cambio, pero por lo demás parece que tienes una solución muy sencilla, allí.

protocol, domain, path, query, fragment = urlparse.urlsplit(url)

(Puede acceder al dominio y al puerto por separado accediendo a las propiedades nombradas del valor devuelto, pero como la sintaxis del puerto está siempre en ASCII, no se ve afectado por el proceso de codificación IDNA).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top