Vraag Beste manier om een ​​Unicode-URL te converteren naar ASCII (UTF-8 procent-escaped) in Python?


Ik vraag me af wat de beste manier is - of dat er een eenvoudige manier is met de standaardbibliotheek - om een ​​URL te converteren met Unicode-tekens in de domeinnaam en het pad naar de equivalente ASCII-URL, gecodeerd met domein als IDNA en het pad% -gecodeerd, zoals RFC 3986.

Ik krijg van de gebruiker een URL in UTF-8. Dus als ze hebben ingetikt http://.ws/ ik krijg 'http://\xe2\x9e\xa1.ws/\xe2\x99\xa5' in Python. En wat ik wil is de ASCII-versie: 'http://xn--hgi.ws/%E2%99%A5'.

Wat ik momenteel doe, is de URL opsplitsen in delen via een regex, en dan handmatig IDNA-coderen voor het domein, en afzonderlijk het pad en de queryreeks coderen met verschillende urllib.quote() noemt.

# 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})?(/.*?)(\?.*)?$', 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'

Is dit correct? Nog betere suggesties? Is er een eenvoudige standaardbibliotheekfunctie om dit te doen?


27
2018-04-29 21:21


oorsprong


antwoorden:


Code:

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

Output:

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

Lees verder:

bewerkingen:

  • Probleem opgelost met reeds geciteerde tekens in de tekenreeks.
  • Changed urlparse/urlunparse naar urlsplit/urlunsplit.
  • Codeer geen gebruikers- en poortgegevens met de hostnaam. (Bedankt Jehiah)
  • Wanneer "@" ontbreekt, behandel de host / poort dan niet als gebruiker / pas! (Bedankt hupf)

44
2018-04-29 21:36



de code gegeven door MizardX is niet 100% correct. Dit voorbeeld zal niet werken:

example.com/folder/?page=2

check django.utils.encoding.iri_to_uri () om unicode URL naar ASCII urls te converteren.

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


5
2017-12-20 21:52



er is wat RFC-3896 URL-ontleding werk aan de gang (bijvoorbeeld als onderdeel van de Summer Of Code) maar niets in de standaardbibliotheek en toch AFAIK - en niet veel op de uri-codering kant van de dingen ook, opnieuw AFAIK. Dus je kunt net zo goed gaan met de elegante benadering van MizardX.


2
2018-04-29 21:44



Okay, met deze opmerkingen en een aantal bugfixaties in mijn eigen code (het behandelde fragmenten helemaal niet), heb ik het volgende bedacht canonurl() function - retourneert een canonieke, ASCII-vorm van de 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()

2
2018-04-30 02:43



Je zou kunnen gebruiken urlparse.urlsplit in plaats daarvan, maar verder lijkt het erop dat je een heel eenvoudige oplossing hebt.

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

(U kunt het domein en de poort afzonderlijk openen door de benoemde eigenschappen van de geretourneerde waarde te gebruiken, maar omdat de poortsyntaxis altijd in ASCII is, wordt deze niet beïnvloed door het IDNA-coderingsproces.)


1
2018-04-29 21:36