urllib.request.urlopen(url)不能两次.read()?

问题描述:

笔者在初学Python爬虫时,用到 urllib.request.urlopen 获取百度搜索页面 (http://www.baidu.com) 上的信息。

首先,访问百度并获取网页信息,将信息保存在 response 中。代码如下:

from urllib.request import urlopen
url = r'http://www.baidu.com'
response = urlopen(url)

然后,调用 .read() 看一下 response 的内容,发现 charset=utf-8,于是用 utf-8 解码。代码如下:

print(response.read())

''' 

结果:
b'<!DOCTYPE html><!--STATUS OK-->\n\n\n    
<html><head><meta http-equiv="Content-Type" content="text/html;
charset=utf-8">
......'

'''

最后,用 utf-8 解码 response 的内容,并把解码后的内容保存到 百度.html 中。代码如下:

with open(r'百度.html','w') as f:
    f.write(response.read().decode('utf-8'))

然而,打开 百度.html 后却发现其一片空白???

现在打印一下 response.read().decode(‘utf-8’),居然也是空的???

print(response.read().decode('utf-8'))
''' 结果:b'' '''

探索原因:

首先,检查一下 response.read() 的内容是从哪一步开始变为空的。代码如下:

from urllib.request import urlopen
url = r'http://www.baidu.com'
response = urlopen(url)
print('1','-'*100,'\n',response.read(),sep='')
print('2','-'*100,'\n',response.read(),sep='')
with open(r'百度.html','w') as f:
    print('3','-'*100,'\n',response.read(),sep='')
    f.write(response.read().decode('utf-8'))
print('4','-'*100,'\n',response.read(),sep='')

'''
结果:
1----------------------------------------------------------------------------------------------------
b'<!DOCTYPE html><!--STATUS OK-->\n\n\n    <html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8">......
2----------------------------------------------------------------------------------------------------
b''
3----------------------------------------------------------------------------------------------------
b''
4----------------------------------------------------------------------------------------------------
b''
'''

发现,在 response.read() 写入 百度.html 前, response.read() 就已经变为空的了,即 response 的第一次 .read() 一切顺利,但是第二次 .read() 就变为空的了。

于是,我们探索一下 response.read()。
通过查看帮助文档,发现response.read 只有一个参数 amt:当 amt=None/负数 时,会读取所有字节;当 amt=正数k 时,会读取 k 个字节。

from urllib.request import urlopen
url = r'http://www.baidu.com'
response = urlopen(url)
print('1','-'*100,'\n',response.read(amt=20),sep='')
print('2','-'*100,'\n',response.read(amt=40),sep='')
response = urlopen(url)
print('3','-'*100,'\n',response.read(amt=60),sep='')
'''
结果:
1----------------------------------------------------------------------------------------------------
b'<!DOCTYPE html><!--S'
2----------------------------------------------------------------------------------------------------
b'TATUS OK-->\n\n\n    <html><head><meta http'
3----------------------------------------------------------------------------------------------------
b'<!DOCTYPE html><!--STATUS OK-->\n\n\n    <html><head><meta http'
'''

可以看到,在第一步解读 response 的前 20 个字节后,第二步解读的 40 个字节,是指 response 的第 21-60 个字节,而非前 40 个。第三步重新获取 response ,并解读其前 60 个字节,刚好是 第一步 与 第二步 的拼接。

因此,response.read() 解读了 response 的所有字节,再次使用 response.read() 时才会得到空的结果。


解决方案:

确定解码类型时,可以先打开网页源码查看 charset=utf-8,然后直接将 response.read().decode(‘utf-8’) 写入 百度.html。代码如下:

from urllib.request import urlopen
url = r'http://www.baidu.com'
response = urlopen(url)
with open(r'百度.html','w') as f:
    f.write(response.read().decode('utf-8'))

如有不当之处,烦请指出!

上一篇:Spring Security(1-6) Spring Security 底层原理


下一篇:es5,es6jQuery的差集,补集,并集,交集