Как получить URL-адрес не-ascii с помощью urlopen Python?
Мне нужно получить данные из 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/Ñöñ-ÅŞÇİİ/
Ответы
Ответ 1
Строго говоря, URI не могут содержать символы, отличные от ASCII; у вас есть IRI.
Чтобы преобразовать IRI в простой URI-код URI:
-
символы, отличные от ASCII в имени узла, должны быть закодированы с использованием алгоритма IDNA Punycode;
-
символы не-ASCII в пути, и большинство других частей адреса должны быть закодированы с использованием UTF-8 и% -кодирования в соответствии с ответом Ignacio.
Итак:
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:[email protected]
или :port
от имени хоста. Только часть имени хоста должна быть закодирована IDNA. проще кодировать с помощью обычных urllib.quote
и .encode('idna')
во время создания URL-адреса, чем для выделения IRI.)
Ответ 2
В 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
Ответ 3
Кодировать unicode
до UTF-8, затем кодировать URL.
Ответ 4
В python3 используйте функцию urllib.parse.quote
в строке non-ascii:
>>> from urllib.request import urlopen
>>> from urllib.parse import quote
>>> chinese_wikipedia = 'http://zh.wikipedia.org/wiki/Wikipedia:' + quote('首页')
>>> urlopen(chinese_wikipedia)
Ответ 5
Используйте iri2uri
метод httplib2
. Он делает то же самое, что и бобин (он/она автор этого?)
Ответ 6
Для тех, кто не зависит строго от urllib, одна практическая альтернатива requests, которая обрабатывает IRI "из коробки".
Например, с http://bücher.ch
:
>>> import requests
>>> r = requests.get(u'http://b\u00DCcher.ch')
>>> r.status_code
200
Ответ 7
Это сложнее, чем принятый ответ @bobince предполагает:
- netloc должен быть закодирован с использованием IDNA;
- URL-адрес, не относящийся к ascii, должен быть закодирован в UTF-8, а затем в процентах;
- параметры запроса не-ascii должны быть закодированы для кодирования URL-адреса страницы, извлеченного из (или для использования сервером кодирования), а затем с процентом.
Так работают все браузеры; он указан в https://url.spec.whatwg.org/ - см. этот example. Реализация 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>")
Легкий способ проверить, является ли реализация escaping URL-адреса неправильной/неполной, это проверить, предоставил ли аргумент "кодирование страницы" или нет.
Ответ 8
На основании ответа @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