Python Requests请求含Emoji的URL时解码失败问题求助
我用Python的requests模块发起请求时遇到编码问题,代码如下:
import requests url = "https://www.foo.bar/topic/512174-some-text-�-text-some-�☀️/" r=requests.get(url)
试过Python 3.11.0、3.7.3,requests 2.30.0、2.25.1版本,目标论坛的原始URL格式类似:https://www.foo.bar/topic/1234567-✅-some-text-✅/
已尝试的方案
- 方案1:直接传入URL时触发编码错误:
'utf-8' codec can't decode bytes in position 35-37: invalid continuation byte,请求无法执行 - 方案2:从网页源码获取URL后用utf-8解码,得到的URL是
https://www.foo.bar/topic/1234567-�-some-text-��/,请求依然失败 - 方案3:改用latin-1编码解码URL,还是无法处理特殊字符
- 方案4:用urllib的quote方法转义URL字符,转义结果和浏览器处理方式不一致,请求失败
使用的请求头:
headers = { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip,deflate", "Connection": "keep-alive", "Upgrade-Insecure-Requests": "1", }
补充:Requests能正常处理已百分编码的Emoji,但从原始HTML拿到的URL如www.foo.bar/topic/123123-some-text-�--text-some-�☀️/,用urllib.quote转义后得到https://www.foo.bar/topic/1233123-some-text-%EF%BF%BD-text-some-%EF%BF%BD%E2%98%80%EF%B8%8F,请求还是失败,怀疑是新Emoji的编码问题导致。
可行解决方案
1. 确保URL的原始字节正确解码
从网页源码获取URL时,不要盲目用utf-8或latin-1解码,先确认网页的实际编码(比如从响应的Content-Type头获取,或者用chardet/cchardet自动检测),再用对应编码解码:
import requests from chardet import detect # 先获取网页源码的原始字节 response = requests.get("论坛首页URL") encoding = detect(response.content)['encoding'] # 用正确编码解码HTML html = response.content.decode(encoding) # 从解码后的HTML中提取URL
2. 手动构造符合网站规则的URL
观察论坛URL格式:https://www.foo.bar/topic/[ID]-[Emoji]-[文本]-[Emoji]/,可以提取帖子ID和文本部分,再手动替换成正确的Emoji编码(参考浏览器地址栏的编码结果)。比如浏览器中✅的编码是%E2%9C%85,直接用这个构造URL:
url = "https://www.foo.bar/topic/512174-some-text-%E2%9C%85-text-some-%E2%9C%85%E2%98%80%EF%B8%8F/" r = requests.get(url, headers=headers)
3. 使用requests的预编码URL方式
把URL拆分成scheme、netloc、path等部分,用urllib.parse.urlunparse拼接,确保path部分正确编码:
from urllib.parse import urlunparse, quote url_parts = ( "https", "www.foo.bar", "/topic/512174-some-text-✅-text-some-✅☀️/", "", "", "" ) # 单独对path部分编码,保留/不被转义 encoded_path = quote(url_parts[2], safe='/') url_parts = url_parts[:2] + (encoded_path,) + url_parts[3:] final_url = urlunparse(url_parts) r = requests.get(final_url, headers=headers)
4. 直接使用原始字节发起请求
绕过字符串解码的问题,直接用字节形式的URL请求(requests支持传入字节对象作为URL):
# 假设从网页源码中获取到的原始字节URL是url_bytes url_bytes = b"https://www.foo.bar/topic/512174-some-text-\xe2\x9c\x85-text-some-\xe2\x9c\x85\xe2\x98\x80\xef\xb8\x8f/" r = requests.get(url_bytes, headers=headers)
内容的提问来源于stack exchange,提问作者emilio-cea

