之前已经实现了用python获取网页的内容,相关已实现代码为:
try:
html = urlopen(req)
except HTTPError, e:
print 'HTTPErrorerr'
print e.code
print e.read()
return None
except BadStatusLine:
print "could not fetch %s" % url
try:
'''
乱码的成因来说,编码问题只是一个方面,使用GB18030确实能够解决。
另一个造成乱码的原因是压缩格式。
很多规模较大的网站都是以gzip的压缩格式输出页面的,
所以在用BS解析之前需要先判断该网页是否经过压缩,
如果经过压缩则先进行解压操作。完整代码如下:
'''
data = html.read()
soup = BeautifulSoup(data, "lxml")
实际使用中会发现soup的内容是乱码的,就像是代码注释里面说的,html以压缩的格式被传输过来,交给浏览器自动进行解压以后渲染进行展示。
现在我们要做的就是人工进行解压缩。
def gzdecode(data):
compressedStream = StringIO.StringIO(data)
gziper = gzip.GzipFile(fileobj=compressedStream)
data2 = gziper.read() # 读取解压缩后数据
return data2
但有些页面实际上可能并没有进行过压缩,我们需要判断这个页面是否被压缩过,才决定是否调用这个函数。
实际上,html = urlopen(req)
返回的html
中就包含了我们需要的信息,html.info()
返回的就是服务器返回的信息头,
如果我们可以在里面找到Content-Encoding: gzip
就以为着这个页面是被压缩过的。
所以此时我们只需要利用html.info()
创建一个dict来查找即可:
keyMap = dict(html.info())
if 'content-encoding' in keyMap and keyMap['content-encoding'] == 'gzip':
#do something
>
[1]. 传输数据编码:Transfer-Encoding
数据编码,即表示数据在网络传输当中,使用怎么样的保证方式来保证数据是安全成功地传输处理。
可以是分段传输,也可以是不分段,直接使用原数据进行传输。
有效的值为:trunked 和 identity
>
[2]. 传输内容编码:Content-Encoding
内容编码,即整个数据信息是在数据器端经过怎样的编码处理,然后客户端会以怎么的编码来反向处理,以得到原始的内容。
这里的内容编码主要是指压缩编码,即服务器端压缩,客户端解压缩。
可以参考的值为:gzip,compress,deflate和identity。
>
[3]. 传输内容格式:Content-Type
内容格式,即接收的数据最终是以何种的形式显示在浏览器中,可以是一个图片,还是一段文本,或者是一段html。
内容格式额外支持可选参数,charset,即实际内容的字符集。
通过字符集,客户端可以对数据进行解编码,以最终显示可以看得懂的文字(而不是一段byte[]或者是乱码)。
接下来,我还希望能够下载到一些指定的图片,我们直接利用urlretrieve
应该就是可以的,但根据返回头的信息显示,即使是图片也是经过gzip压缩的
urllib.urlretrieve(url, filename, reporthook=None,data=None)
参数说明:
url:外部或者本地url
filename:指定了保存到本地的路径(如果未指定该参数,urllib会生成一个临时文件来保存数据);
reporthook:是一个回调函数,当连接上服务器、以及相应的数据块传输完毕的时候会触发该回调。我们可以利用这个回调函数来显示当前的下载进度。
data:指post到服务器的数据。该方法返回一个包含两个元素的元组(filename, headers),filename表示保存到本地的路径,header表示服务器的响应头。
相比于urlopen
,需要创建一个表示远程url的类文件对象,然后像本地文件一样操作这个类文件对象来获取远程数据。
urlretrieve
,会直接将远程数据下载到本地。
def autoDownLoad(url,add):#文件下载不完整,自动重新加载
#print add
try:
'''
我们使用urllib.urlretrieve(url,filename)
时经常遇到下载到一半时,出现urllib.ContentTooShortError错误。
这是因为文件下载不完全导致的错误。
如果发现问题的是诸如图片和音乐文件这一类文件较小的问题,可以很容易使用以下方式解决。
'''
a, b = urllib.urlretrieve(url, add)#a表示地址, b表示返回头
'''
判断文件数据只返回了部分,导致文件偏小,
我们进行重新的存储,10240即我们认为如果存储数据小于10K,判定下载接收不完全
'''
if os.path.getsize(add) < 10240:
autoDownLoad(url,add)
except urllib.ContentTooShortError:
print 'Network conditions is not good.Reloading.'
autoDownLoad(url,add)
except socket.timeout:
print 'fetch ', url,' exceedTime '
try:
urllib.urlretrieve(url,add)
except:
print 'reload failed'
下载以后的图片全部无法进行显示,但根据上面的经验,我们只需要根据返回头判断是否需要解压缩即可,但因为urlretrieve
会直接将远程数据下载到本地。所以我们需要对本地数据进行二次处理
a, b = urllib.urlretrieve(url, add)#a表示地址, b表示返回头
keyMap = dict(b)
#print keyMap
#need decode
if 'content-encoding' in keyMap and keyMap['content-encoding'] == 'gzip':
print 'need2be decode'
objectFile = open(add, 'r+')#以读写模式打开
data = objectFile.read()
data = gzdecode(data)
objectFile.seek(0, 0)
objectFile.write(data)
objectFile.close()
文件 open 模式:
w:以写方式打开
a:以追加模式打开 (从 EOF 开始, 必要时创建新文件)
r+:以读写模式打开
w+:以读写模式打开 (参见 w )
a+:以读写模式打开 (参见 a )
rb:以二进制读模式打开
wb:以二进制写模式打开 (参见 w )
ab:以二进制追加模式打开 (参见 a )
rb+:以二进制读写模式打开 (参见 r+ )
wb+:以二进制读写模式打开 (参见 w+ )
ab+:以二进制读写模式打开 (参见 a+ )
考虑到效率问题,我们直接对文件做出修改-截断写:
seek(offset,where): where=0从起始位置移动,1从当前位置移动,2从结束位置移动。当有换行时,会被换行截断。seek()无返回值,故值为None。
truncate(n): 从文件的首行首字符开始截断,截断文件为n个字符;无n表示从当前位置起截断;截断之后n后面的所有字符被删除。其中win下的换行代表2个字符大小。
这里考虑的是原来是压缩文件,解压后势必会覆盖掉原来的全部数据,所以直接选择了seek的方式。