解决Python urlretrieve下载非ASCII字符URL文件报ascii编码错误
问题:下载含非ASCII字符文件名的Excel文件触发编码报错
批量爬取选举结果Excel文件时,遇到文件名带é这类非ASCII字符的资源(比如Orléans选区的结果表),调用urllib.request.urlretrieve会抛出UnicodeEncodeError: 'ascii' codec can't encode characters报错。
之前尝试的两种修复方案都存在逻辑问题:
- 直接过滤删除非ASCII字符:会改变原始文件名,和服务器存储的实际资源路径不匹配,触发404错误
- 直接对整个URL路径调用
urllib.parse.quote全量编码:会把路径中已经存在的%编码字符转义为%25,破坏原有合法路径,同样触发404错误
问题根因
urlretrieve默认使用ASCII编码解析URL,遇到非ASCII字符时会直接抛出编码错误;之前的两种修复方案都没有正确区分URL中「已经完成百分号编码的合法内容」和「未编码的非ASCII字符」,要么删除字符破坏路径完整性,要么重复编码导致路径失效。
正确修复方案
核心处理逻辑是:仅对URL中未编码的非ASCII字符做百分号编码,保留已经合法的编码内容和路径分隔符,不要手动全量编码或删除字符。
修复后的可运行完整代码如下:
import requests from bs4 import BeautifulSoup from urllib.request import urlretrieve from urllib.parse import urlsplit, urlunsplit, quote headers = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36" } resp = requests.get( "https://www.elections.on.ca/en/resource-centre/elections-results.html#accordion2022ge", headers=headers ) soup = BeautifulSoup(resp.text, "html.parser") for link in soup.find_all('a', href=True): if 'xlsx' not in link['href']: continue raw_url = "https://www.elections.on.ca/" + link['href'] # 拆分URL为独立组成部分,避免编码时破坏原有结构 url_parts = urlsplit(raw_url) # 仅对路径部分编码,指定safe参数保留路径分隔符/和已有的%编码字符,避免重复编码 encoded_path = quote(url_parts.path, safe="/%") # 拼接为合法可访问的URL valid_url = urlunsplit(( url_parts.scheme, url_parts.netloc, encoded_path, url_parts.query, url_parts.fragment )) # 提取本地保存文件名,替换特殊字符避免本地文件系统编码兼容问题 raw_file_name = valid_url.split("/")[-1] local_file_name = raw_file_name.replace("é", "e") urlretrieve(valid_url, local_file_name)
关键注意点
- 不要直接对完整URL字符串做全量编码,使用
urlsplit拆分后单独处理路径段,避免破坏协议、域名等固定结构 - 调用
quote时必须传入safe="/%"参数,明确告知函数不需要转义路径分隔符和已经存在的百分号编码内容,从根源解决重复编码问题 - 本地保存的文件名不需要和远程文件名完全一致,替换特殊字符为普通ASCII字符即可,不会影响下载的文件内容,还能避免不同操作系统的文件编码兼容问题
内容的提问来源于stack exchange,提问作者Isaac A
相关产品推荐
相关产品推荐

