Как получить url-адрес без ascii с помощью Python urlopen?

мне нужно получить данные из URL-адреса с символами, отличными от ascii, но urllib2.urlopen отказывается открывать ресурс и поднимает:

UnicodeEncodeError: 'ascii' codec can't encode character u'u0131' in position 26: ordinal not in range(128)

Я знаю, что URL не соответствует стандартам, но у меня нет возможности изменить его.

Как получить доступ к ресурсу, на который указывает URL, содержащий символы, отличные от ascii, с помощью Python?

edit: другими словами, может / как urlopen открыть URL-адрес, как:

http://example.org/Ñöñ-ÅŞÇİİ/

8 ответов


строго говоря, URIs не может содержать символы, отличные от ASCII; у вас есть IRI.

чтобы преобразовать IRI в простой ASCII URI:

  • символы, отличные от ASCII, в части имени хоста адреса должны быть закодированы с помощью Punycode-алгоритм IDNA на основе;

  • не-ASCII символы в пути, и большинство других частей адреса должны быть в кодировке UTF-8 и %- кодировка, согласно ответу Игнасио.

так:

import re, urlparse

def urlEncodeNonAscii(b):
    return re.sub('[\x80-\xFF]', lambda c: '%%%02x' % ord(c.group(0)), b)

def iriToUri(iri):
    parts= urlparse.urlparse(iri)
    return urlparse.urlunparse(
        part.encode('idna') if parti==1 else urlEncodeNonAscii(part.encode('utf-8'))
        for parti, part in enumerate(parts)
    )

>>> iriToUri(u'http://www.a\u0131b.com/a\u0131b')
'http://www.xn--ab-hpa.com/a%c4%b1b'

(технически это все еще недостаточно хорошо в общем случае, потому что urlparse не откололся ни user:pass@ префикс или :port - суффикс в имени. Только часть имени хоста должна быть закодирована IDNA. Проще кодировать с помощью normal urllib.quote и .encode('idna') в то время, когда вы создаете URL-адрес, чем нужно тянуть IRI друг от друга.)


Python 3 имеет библиотеки для обработки этой ситуации. Использовать urllib.parse.urlsplit чтобы разделить URL на его компоненты, и urllib.parse.quote чтобы правильно процитировать / избежать символов Юникода и urllib.parse.urlunsplit чтобы соединить его вместе.

>>> import urllib.parse
>>> url = 'http://example.com/unicodè'
>>> url = urllib.parse.urlsplit(url)
>>> url = list(url)
>>> url[2] = urllib.parse.quote(url[2])
>>> url = urllib.parse.urlunsplit(url)
>>> print(url)
http://example.com/unicod%C3%A8

в python3 используйте urllib.parse.quote функция в строке без ascii:

>>> from urllib.request import urlopen                                                                                                                                                            
>>> from urllib.parse import quote                                                                                                                                                                
>>> chinese_wikipedia = 'http://zh.wikipedia.org/wiki/Wikipedia:' + quote('首页')
>>> urlopen(chinese_wikipedia)

кодирование unicode в UTF-8, затем URL-кодирование.


использовать iri2uri метод httplib2. Это делает то же самое, что и Бобин (он / она автор этого?)


это сложнее, чем предполагает принятый ответ @bobince:

  • netloc должен быть закодирован с помощью IDNA;
  • не-ascii URL-путь должен быть закодирован в UTF-8, а затем процент-экранирован;
  • параметры запроса, отличные от ascii, должны быть закодированы в кодировку URL-адреса страницы, извлеченного из (или используемого сервером кодирования), а затем в процентах.

Так работают все браузеры; это указано в https://url.spec.whatwg.org/ - смотри это пример. Реализацию Python можно найти в w3lib (это библиотека, которую использует Scrapy); см. w3lib.url-адрес.safe_url_string:

from w3lib.url import safe_url_string
url = safe_url_string(u'http://example.org/Ñöñ-ÅŞÇİİ/', encoding="<page encoding>")

простой способ проверить, является ли реализация экранирования URL неправильной / неполной, - проверить, предоставляет ли она аргумент "кодировка страницы" или нет.


для тех, кто не зависит строго от urllib, одна практическая альтернатива -запросы, который обрабатывает IRIs "из коробки".

например,http://bücher.ch:

>>> import requests
>>> r = requests.get(u'http://b\u00DCcher.ch')
>>> r.status_code
200

на основе @darkfeline ответ:

from urllib.parse import urlsplit, urlunsplit, quote

def iri2uri(iri):
    """
    Convert an IRI to a URI (Python 3).
    """
    uri = ''
    if isinstance(iri, str):
        (scheme, netloc, path, query, fragment) = urlsplit(iri)
        scheme = quote(scheme)
        netloc = netloc.encode('idna').decode('utf-8')
        path = quote(path)
        query = quote(query)
        fragment = quote(fragment)
        uri = urlunsplit((scheme, netloc, path, query, fragment))

    return uri