如何使用Python urlopen获取非ascii url?

oma*_*mat 37 python unicode urllib2 non-ascii-characters urlopen

我需要从带有非ascii字符的URL获取数据,但urllib2.urlopen拒绝打开资源并引发:

UnicodeEncodeError: 'ascii' codec can't encode character u'\u0131' in position 26: ordinal not in range(128)
Run Code Online (Sandbox Code Playgroud)

我知道URL不符合标准,但我没有机会改变它.

什么是访问由包含使用Python非ASCII字符的URL指向的资源的方式吗?

编辑:换句话说,可以/如何urlopen打开一个URL,如:

http://example.org/Ñöñ-Å?Ç??/
Run Code Online (Sandbox Code Playgroud)

bob*_*nce 54

严格来说,URI不能包含非ASCII字符; 你有什么IRI.

要将IRI转换为纯ASCII URI:

  • 地址的主机名部分中的非ASCII字符必须使用基于Punycode的IDNA算法进行编码;

  • 根据Ignacio的回答,路径中的非ASCII字符和地址的大多数其他部分必须使用UTF-8和%-encoding进行编码.

所以:

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'
Run Code Online (Sandbox Code Playgroud)

(从技术上讲,这在一般情况下仍然不够好,因为urlparse不会在主机名上拆分任何user:pass@前缀或:port后缀.只有主机名部分应该是IDNA编码的.使用普通urllib.quote.encode('idna')当时你更容易编码构建URL而不是必须将IRI分开.)

  • 在python 3中,你用`import urllib.parse`代替`urlparse`,在urlEncodeNonAscii中解码b:`b.decode('utf-8')`并将idna部分从iriToUri中去掉:`return urllib.parse.urlunparse ([url_encode_non_ascii(part.encode('utf-8')) for part in part])` (2认同)

dar*_*ine 18

Python 3有库来处理这种情况.使用 urllib.parse.urlsplit的URL分割成其组成部分,并 urllib.parse.quote妥善报价/逃脱Unicode字符和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
Run Code Online (Sandbox Code Playgroud)


Per*_*rry 18

在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)
Run Code Online (Sandbox Code Playgroud)

  • 简单有效!:D (2认同)
  • 比其他答案好得多。 (2认同)
  • 这是一个很棒的解决方案。解决了我在 url 中使用汉字时的问题,适用于日语字符集。 (2认同)
  • 哇,这被低估了 (2认同)

Mik*_*bov 6

它比公认的@bobince 的答案所暗示的要复杂:

  • netloc 应使用 IDNA 进行编码;
  • 非 ascii URL 路径应编码为 UTF-8,然后进行百分比转义;
  • 非 ascii 查询参数应编码为从页面 URL 中提取的编码(或编码服务器使用),然后进行百分比转义。

这就是所有浏览器的工作方式;它在https://url.spec.whatwg.org/ 中指定- 请参阅此示例。可以在 w3lib 中找到 Python 实现(这是 Scrapy 正在使用的库);见w3lib.url.safe_url_string

from w3lib.url import safe_url_string
url = safe_url_string(u'http://example.org/Ñöñ-Å?Ç??/', encoding="<page encoding>")
Run Code Online (Sandbox Code Playgroud)

检查 URL 转义实现是否不正确/不完整的一种简单方法是检查它是否提供“页面编码”参数。


Ign*_*ams 5

编码unicode为UTF-8,然后进行URL编码.

  • http://farmdev.com/talks/unicode/ http://docs.python.org/library/urllib.html#urllib.quote (2认同)
  • 对于第一部分,您需要`url.encode('utf-8')`(假设`url`是一个`unicode`对象). (2认同)

h7r*_*h7r 5

对于那些不严格依赖 urllib 的人来说,一种实用的替代方案是requests,它“开箱即用”地处理 IRI。

\n\n

例如,与http://b\xc3\xbccher.ch

\n\n
>>> import requests\n>>> r = requests.get(u\'http://b\\u00DCcher.ch\')\n>>> r.status_code\n200\n
Run Code Online (Sandbox Code Playgroud)\n


Ukr*_*Ukr 5

基于@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
Run Code Online (Sandbox Code Playgroud)