如何使用 urlopen 获取非 ascii url?
- 2025-03-05 09:17:00
- admin 原创
- 88
问题描述:
我需要从包含非 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/Ñöñ-ÅŞÇİİ/
解决方案 1:
严格来说,URI 不能包含非 ASCII 字符;那里有一个IRI。
要将 IRI 转换为纯 ASCII URI:
地址主机名部分中的非 ASCII 字符必须使用基于Punycode的 IDNA 算法进行编码;
路径中的非 ASCII 字符,并且地址的其他大部分部分必须使用 UTF-8 和 %-encoding 进行编码,如 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:pass@
前缀或后缀。只有主机名部分应该是 IDNA 编码。在构建 URL 时使用普通编码比将 IRI 分开更容易。):port
`urllib.quote`.encode('idna')
解决方案 2:
在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)
解决方案 3:
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
解决方案 4:
根据@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
解决方案 5:
它比公认的@bobince 的答案所暗示的要复杂得多:
netloc 应该使用 IDNA 进行编码;
非 ASCII URL 路径应该编码为 UTF-8,然后进行百分比转义;
非 ASCII 查询参数应编码为从中提取的页面 URL 的编码(或服务器使用的编码),然后进行百分比转义。
所有浏览器都是这样工作的;它在https://url.spec.whatwg.org/中有说明- 请参阅此示例。可以在 w3lib(这是 Scrapy 使用的库)中找到 Python 实现;请参阅w3lib.url.safe_url_string:
from w3lib.url import safe_url_string
url = safe_url_string(u'http://example.org/Ñöñ-ÅŞÇİİ/', encoding="<page encoding>")
检查 URL 转义实现是否不正确/不完整的简单方法是检查它是否提供“页面编码”参数。
解决方案 6:
对于那些不严格依赖 urllib 的人来说,一个实用的替代方案是request,它可以“开箱即用”地处理 IRI。
例如http://bücher.ch
:
>>> import requests
>>> r = requests.get(u'http://b/u00DCcher.ch')
>>> r.status_code
200
解决方案 7:
将其编码unicode
为 UTF-8,然后进行 URL 编码。
解决方案 8:
使用iri2uri
的方法httplib2
。它与 bobin 的方法相同(他/她是该方法的作者吗?)
解决方案 9:
将 IRI 转换为 ASCII URI 的另一种方法是使用furl
包:
gruns/furl:
扫码咨询,免费领取项目管理大礼包!