51

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

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

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

使用 Python 访问包含非 ascii 字符的 URL 指向的资源的方法是什么?

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

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

10 回答 10

56

严格来说 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'

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

于 2010-12-08T19:13:27.343 回答
41

In python3, use the urllib.parse.quote function on the non-ascii string:

>>> from urllib.request import urlopen                                                                                                                                                            
>>> from urllib.parse import quote                                                                                                                                                                
>>> chinese_wikipedia = 'http://zh.wikipedia.org/wiki/Wikipedia:' + quote('首页')
>>> urlopen(chinese_wikipedia)
于 2015-03-24T11:32:16.180 回答
23

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
于 2013-08-16T08:56:41.027 回答
7

它比公认的@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>")

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

于 2016-11-17T11:57:45.367 回答
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
于 2017-02-17T23:15:35.523 回答
4

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

于 2010-12-08T16:07:37.300 回答
4

的使用iri2uri方法httplib2。它和 bobin 做的一样(他/她是那个的作者吗?)

于 2012-02-28T13:22:00.563 回答
3

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

例如,使用http://bücher.ch

>>> import requests
>>> r = requests.get(u'http://b\u00DCcher.ch')
>>> r.status_code
200
于 2016-05-22T15:39:33.827 回答
1
于 2021-07-26T10:09:25.043 回答
0

作品!最后

我无法避免这个奇怪的角色,但最后我还是通过了它。

import urllib.request
import os


url = "http://www.fourtourismblog.it/le-nuove-tendenze-del-marketing-tenere-docchio/"
with urllib.request.urlopen(url) as file:
    html = file.read()
with open("marketingturismo.html", "w", encoding='utf-8') as file:
    file.write(str(html.decode('utf-8')))
os.system("marketingturismo.html")
于 2018-10-31T06:18:55.287 回答