Python Python 3 挑战第 17 关
Python Challenge level 17 in Python 3
我最近开始玩 The Python Challenge。虽然相当复杂,但所需的编码并不是很难,这使得学习许多有用的模块非常有趣。
我的问题是关于第 17 关的。我理解在第 4 关中需要遵循线索的想法,同时收集 cookie,这就是我所做的。但是,我无法对得到的字符串进行 BZ2 解压缩。
我尝试了谷歌搜索,我发现了一个很好的博客,其中包含 Python 2 中的解决方案。具体来说,第 17 级的是 here。分析那个,我意识到我确实得到了压缩字符串(来自 cookies)并且它在 Python 2:
中正确解压
bz2.decompress(urllib.unquote_plus(compressed))
然而,Python 3 中的 bz2.decompress
需要一个字节数组而不是字符串,但是上面那行明显的 Python 3 对应物:
bz2.decompress(urllib.parse.unquote_plus(message).encode("utf8"))
失败 OSError: Invalid data stream
。我尝试了所有 standard encodings 和上面的一些变体,但无济于事。
到目前为止,这是我的(非工作)解决方案:
#!/usr/bin/env python3
"""
The Python Challenge #17: http://www.pythonchallenge.com/pc/return/romance.html
This is similar to #4 and it actually uses its solution. However, the key is in
the cookies. The page's cookie says: "you+should+have+followed+busynothing..."
So, we follow the chain from #4, using the word "busynothing" and
reading the cookies.
"""
import urllib.request, urllib.parse
import re
import bz2
nothing = "12345"
last_cookie = None
message = ""
while True:
headers = dict()
if last_cookie:
headers["Cookie"] = last_cookie
r = urllib.request.Request("http://www.pythonchallenge.com/pc/def/linkedlist.php?busynothing=" + nothing, headers=headers)
with urllib.request.urlopen(r) as u:
last_cookie = u.getheader("Set-Cookie")
m = re.match(r"info=(.*?);", last_cookie)
if m:
message += m.group(1)
text = u.read().decode("utf8")
print("{} >>> {}".format(nothing, text))
m = re.search(r"\d+$", text)
try:
nothing = str(int(m.group(0)))
except Exception as e:
print(e)
break
print("Cookies message:", message)
print("Decoded:", bz2.decompress(urllib.parse.unquote_plus(message).encode("utf8")))
所以,我的问题是:Python 3 上述问题的解决方案是什么样的,为什么我的方法没有按预期工作?
我很清楚这部分可以做得更好。我正在寻找一个快速而肮脏的解决方案,所以我在这里的兴趣只是它有效(为什么不是我上面那样做的)。
这里需要用到urllib.parse.unquote_to_bytes()
function。它不支持 +
到 space 的映射,但可以通过 str.replace()
:
轻松解决
urllib.parse.unquote_to_bytes(message.replace('+', '%20'))
这会很好地解压缩。您可以将生成的未压缩字符串解码为 ASCII:
print("Decoded:", bz2.decompress(urllib.parse.unquote_to_bytes(message.replace('+', '%20'))).decode('ascii'))
使用不同消息的演示我准备不放弃这个谜题:
>>> import bz2
>>> import urllib.parse
>>> another_message = 'BZh91AY%26SY%80%F4C%E8%00%00%02%13%80%40%00%04%00%22%E3%8C%00+%00%22%004%D0%40%D04%0C%B7%3B%E6h%B1AIM%3D%5E.%E4%8Ap%A1%21%01%E8%87%D0'
>>> bz2.decompress(urllib.parse.unquote_to_bytes(another_message.replace('+', '%20'))).decode('ascii')
'This is not the message'
或者,告诉 urllib.unquote_plus()
使用 Latin-1 编码而不是 UTF-8。 unquote_plus()
的默认错误处理程序设置为 'replace'
,因此您永远不会注意到原始数据无法解码为 UTF-8,因此字节被替换为 U+FFFD 替换字符,这是什么导致解压失败。 Latin-1 将所有字节 one-on-oe 直接映射到前 256 个 Unicode 字符,因此您可以编码回原始字节:
>>> '\ufffd' in urllib.parse.unquote_plus(another_message)
True
>>> bz2.decompress(urllib.parse.unquote_plus(another_message, 'latin1').encode('latin1')).decode('ascii')
'This is not the message'
我最近开始玩 The Python Challenge。虽然相当复杂,但所需的编码并不是很难,这使得学习许多有用的模块非常有趣。
我的问题是关于第 17 关的。我理解在第 4 关中需要遵循线索的想法,同时收集 cookie,这就是我所做的。但是,我无法对得到的字符串进行 BZ2 解压缩。
我尝试了谷歌搜索,我发现了一个很好的博客,其中包含 Python 2 中的解决方案。具体来说,第 17 级的是 here。分析那个,我意识到我确实得到了压缩字符串(来自 cookies)并且它在 Python 2:
中正确解压bz2.decompress(urllib.unquote_plus(compressed))
然而,Python 3 中的 bz2.decompress
需要一个字节数组而不是字符串,但是上面那行明显的 Python 3 对应物:
bz2.decompress(urllib.parse.unquote_plus(message).encode("utf8"))
失败 OSError: Invalid data stream
。我尝试了所有 standard encodings 和上面的一些变体,但无济于事。
到目前为止,这是我的(非工作)解决方案:
#!/usr/bin/env python3
"""
The Python Challenge #17: http://www.pythonchallenge.com/pc/return/romance.html
This is similar to #4 and it actually uses its solution. However, the key is in
the cookies. The page's cookie says: "you+should+have+followed+busynothing..."
So, we follow the chain from #4, using the word "busynothing" and
reading the cookies.
"""
import urllib.request, urllib.parse
import re
import bz2
nothing = "12345"
last_cookie = None
message = ""
while True:
headers = dict()
if last_cookie:
headers["Cookie"] = last_cookie
r = urllib.request.Request("http://www.pythonchallenge.com/pc/def/linkedlist.php?busynothing=" + nothing, headers=headers)
with urllib.request.urlopen(r) as u:
last_cookie = u.getheader("Set-Cookie")
m = re.match(r"info=(.*?);", last_cookie)
if m:
message += m.group(1)
text = u.read().decode("utf8")
print("{} >>> {}".format(nothing, text))
m = re.search(r"\d+$", text)
try:
nothing = str(int(m.group(0)))
except Exception as e:
print(e)
break
print("Cookies message:", message)
print("Decoded:", bz2.decompress(urllib.parse.unquote_plus(message).encode("utf8")))
所以,我的问题是:Python 3 上述问题的解决方案是什么样的,为什么我的方法没有按预期工作?
我很清楚这部分可以做得更好。我正在寻找一个快速而肮脏的解决方案,所以我在这里的兴趣只是它有效(为什么不是我上面那样做的)。
这里需要用到urllib.parse.unquote_to_bytes()
function。它不支持 +
到 space 的映射,但可以通过 str.replace()
:
urllib.parse.unquote_to_bytes(message.replace('+', '%20'))
这会很好地解压缩。您可以将生成的未压缩字符串解码为 ASCII:
print("Decoded:", bz2.decompress(urllib.parse.unquote_to_bytes(message.replace('+', '%20'))).decode('ascii'))
使用不同消息的演示我准备不放弃这个谜题:
>>> import bz2
>>> import urllib.parse
>>> another_message = 'BZh91AY%26SY%80%F4C%E8%00%00%02%13%80%40%00%04%00%22%E3%8C%00+%00%22%004%D0%40%D04%0C%B7%3B%E6h%B1AIM%3D%5E.%E4%8Ap%A1%21%01%E8%87%D0'
>>> bz2.decompress(urllib.parse.unquote_to_bytes(another_message.replace('+', '%20'))).decode('ascii')
'This is not the message'
或者,告诉 urllib.unquote_plus()
使用 Latin-1 编码而不是 UTF-8。 unquote_plus()
的默认错误处理程序设置为 'replace'
,因此您永远不会注意到原始数据无法解码为 UTF-8,因此字节被替换为 U+FFFD 替换字符,这是什么导致解压失败。 Latin-1 将所有字节 one-on-oe 直接映射到前 256 个 Unicode 字符,因此您可以编码回原始字节:
>>> '\ufffd' in urllib.parse.unquote_plus(another_message)
True
>>> bz2.decompress(urllib.parse.unquote_plus(another_message, 'latin1').encode('latin1')).decode('ascii')
'This is not the message'