Парсинг url'а, содержащего unicode параметры, используя urlparse.parse_qs

Задача: получить словарь параметров URL'a. Например, имеем адрес:

http://example.com/?key=value&a=b

и нужно получить такой словарь:

{'key': ['value'], 'a': ['b']}

Тут значения являются списками, т.к. у одного ключа может быть несколько значений:

In: http://example.com/?key=value&a=b&a=c
Out: {'key': ['value'], 'a': ['b', 'c']}

В python'е для этих целей есть функция urlparse.parse_qs, которая делает следующее:

>>> import urlparse
>>> query = "key=value&a=b"
>>> urlparse.parse_qs(query)
{'a': ['b'], 'key': ['value']}

Т.е. на вход функции parse_qs нужно давать сами параметры, без "http://exapmle.com/?". Для отделения параметров от остального адреса, можно воспользоваться функцией urlparse.urlparse:

>>> import urlparse
>>> url = "http://example.com/?key=value&a=b"
>>> query = urlparse.urlparse(url).query
>>> query
'key=value&a=b'
>>> params = urlparse.parse_qs(query)
>>> params
{'a': ['b'], 'key': ['value']}

Попробуем восстановить исходные параметры url'а. Воспользуемся функцией urllib.urlencode:

>>> import urllib
>>> urllib.urlencode(params, doseq=True)
'a=b&key=value'

Порядок параметров значения не имеет, так что все ок.

URL с unicode параметром.

По RFC3986, URL может содержать только ограниченный набор символов из набора US-ASCII, состоящий из цифр, букв и нескольких графических символов. Причем некоторые графические символы являются зарезервированными (":", "/", "?", "#", "[", "]", "@", "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="). Если в URL нужно передать непечатные или зарезервированные символы (например как значение параметра), то их нужно кодировать по правилам Percent-Encoding: %HH, где HH - это шестнадцатеричный код.

Предположим нужно передать u"значение". В python'е строка u"значение" содержит unicode коды, нам нужно получить байты. Для этого закодируем строку например кодировкой utf8:

>>> value = u'значение'
>>> value_utf8 = value.encode('utf8')
>>> value_utf8
'\xd0\xb7\xd0\xbd\xd0\xb0\xd1\x87\xd0\xb5\xd0\xbd\xd0\xb8\xd0\xb5'

Теперь уже надо закодировать эти байты, используя Percent-Encoding (%HH), для передачи в url:

>>> value_url = urllib.quote(value_utf8)
>>> value_url
'%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5'

Построим полный адрес:

>>> url = "http://example.com/?key=%s&a=b" % value_url
>>> url
'http://example.com/?key=%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5&a=b'

И опять попробуем получить словарь параметров:

>>> query = urlparse.urlparse(url).query
>>> query
'key=%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5&a=b'
>>> params = urlparse.parse_qs(query)
>>> params
{'a': ['b'], 'key': ['\xd0\xb7\xd0\xbd\xd0\xb0\xd1\x87\xd0\xb5\xd0\xbd\xd0\xb8\xd0\xb5']}

Видим, что parse_qs раскодировал значение из кодировки Percent-Encoding и вернул нам байты. Можно теперь получить unicode, ведь мы помним, что кодировали строку с помощью utf8:

>>> params['key'][0].decode('utf8')
u'\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435'
>>> print params['key'][0].decode('utf8')
значение

Ок. Восстановим исходные параметры из полученного словаря:

>>> urllib.urlencode(params, doseq=True)
'a=b&key=%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5'

Получили те же параметры, которые и задавали вначале.

Проделаем тоже самое для URL, которая возвращается в django при вызове request.get_full_path()

request.get_full_path() почему-то возвращает не строку (str), а unicode (пробовал на django 1.4, 1.5):

>>> request.get_full_path()
u'/?key=%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5&a=b'

Повторим те же шаги c этой url:

>>> url = request.get_full_path()
>>> query = urlparse.urlparse(url).query
>>> query
u'key=%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5&a=b'
>>> params = urlparse.parse_qs(query)
>>> params
{u'a': [u'b'], u'key': [u'\xd0\xb7\xd0\xbd\xd0\xb0\xd1\x87\xd0\xb5\xd0\xbd\xd0\xb8\xd0\xb5']}

Интересно, что значение для u'key' представляет из себя unicode строку, которая содержит байты! Конечно же, раскодировать ее уже не получится:

>>> params['key'][0].decode('utf8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python27\lib\encodings\utf_8.py", line 16, in decode
    return codecs.utf_8_decode(input, errors, True)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-15: ordinal not in range(128)

То же самое получим и с urlencode:

>>> urllib.urlencode(params, doseq=True)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python27\lib\urllib.py", line 1337, in urlencode
    l.append(k + '=' + quote_plus(str(elt)))
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-15: ordinal not in range(128)

Тут для меня были две неожиданности:

  • django вернул url как unicode (зачем? почему не обычная строка str, ведь в url'е не могут быть не-ASCII символы)
  • parse_qs вернул строку unicode, которая содержит байты.

Решение простое, на вход parse_qs нужно давать только строку str:

>>> url = request.get_full_path()
>>> url = url.encode('ascii')
>>> url
'/?key=%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5&a=b'

Либо так, что тоже самое:

>>> url = request.get_full_path()
>>> url = str(url)
>>> url
'/?key=%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5&a=b'

Ссылки:

Опубликовано: Май 22, 2013
Bookmark and Share
Comments powered by Disqus