精通Python爬虫-02-初遇

urllib简介

首先需要说明的是,本系列教程,全部采用Python3.5作为开发环境,因为我不想做一些影响Python3发展的事情,如非必要,请使用Python3。 urllib是Python提供的一个用来访问网络的库,在Python3中有了较大的改动,首先最明显的就是整合了urllib2和urllib,使用起来更加的明了简单。

第一个请求

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# 引入urllib下的request模块
import urllib.request

# 使用urlopen发送请求,并获得响应
response = urllib.request.urlopen('http://www.imooc.com')
print(response.read())
  • urllib.request模块:包含了跟请求相关的方法。
  • urlopen:可以根据链接或Request对象来发送请求,并将结果返回为一个HTTPResponse对象。结果对象包含一个read方法,可以输出获取到的HTML代码。

使用Request对象

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import urllib.request

# 创建一个Request对象
req = urllib.request.Request('http://www.imooc.com')
# 使用Request对象发送请求
response = urllib.request.urlopen(req)
print(response.read())

和上面有所不同的是,这里我们使用了Request方法来发送请求,这里或许看不出什么差异,我们将在下个案例中看到他们的区别。

和上面有所不同的是,这里我们使用了Request方法来发送请求,这里或许看不出什么差异,我们将在下个案例中看到他们的区别。

发送POST请求

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import urllib.request
import urllib.parse

post_value = {
    'keyword': '大数据',
    'pageCount': '1'
}

# 把dict对象转换为请求的url参数字符,并按照utf-8转换为字节
post_data = urllib.parse.urlencode(post_value).encode('utf-8')

# 发送携带参数的POST请求
req = urllib.request.Request('http://www.iimedia.cn/do_search.jsp', data=post_data)
response = urllib.request.urlopen(req)
print(response.read())

urllib.request.Request在Python3中,必须接受一个字节类型的data,这是和Python2不同,需要区分。和requests这种库不同,Python的urlopen方法,会根据有没有携带data参数来决定是发送GET请求还是POST请求。

再试试带参数的GET请求

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import urllib.request
import urllib.parse

url = 'http://www.imooc.com/search/course'

get_value = {'words': 'Python', }
get_data = urllib.parse.urlencode(get_value)
# 拼装后的URL就是http://www.imooc.com/search/course?words=Python
req = urllib.request.Request(url  + '?' + get_data)
response = urllib.request.urlopen(req)
print(response.read())

欺骗的大师

我们来思考一个问题,那就是类似于腾讯,百度,乃至慕课网。愿意别人通过爬虫来获取网站上的一些数据吗?我想这种问题不用回答也知道结果,自然是不愿意。那么服务器是如何知道到底是什么在访问它呢?答案是通过请求的数据包中,一个叫做user-agent的参数来区分的,我们通过浏览器的开发者工具可以轻松的找到,如果不知道怎么使用的,可以参考我在慕课网的课程浏览器开发者工具使用技巧。 我们只需要使用真正的浏览器访问一下网站,然后在开发者工具中找到这个参数,把里面的内容复制下来。

例如我的是:

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36

可以看到,这里面包含了我的浏览器版本以及内核版本,系统的版本和位数等等。 这就像给我们的爬虫披上了一层伪装的外衣一样,可以悄无声息的渗透到我们想去的地方。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import urllib.request

headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36'}
url = 'http://www.imooc.com'
# 给headers参数赋值
req = urllib.request.Request(url, headers=headers)
response = urllib.request.urlopen(req)
print(response.read())

通过urllib的源码,我们可以看到,urllib.request.Request方法接收的参数: self, url, data=None, headers={}, originreqhost=None, unverifiable=False, method=None data和headers参数,如果我们不指定,默认都是为空的。

异常的处理

异常的处理,对于任何一个语言任何一个程序都是非常重要的,它意味着我们的程序在遇到错误以后需要如何处理。那么同样的,为了我们的爬虫不轻易的就遇到异常停止,我们需要对一些异常进行操作。

URLError

这是urllib内的一个异常类,正如其名,通常在遇到URL链接有问题时,会抛出这种异常,比如下面的代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import urllib.request
import urllib.error

# 注意这里的URL地址,我们故意写成htt
urllib.request.urlopen('htt://www.imooc.com')

运行上面的代码,得到一个错误: urllib.error.URLError: 那么我们如何捕获这个异常,在遇到这种问题时,不要让我们的程序崩溃呢?

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import urllib.request
# 所有urllib异常的类,在Python3中都移植到了urllib.error里面
import urllib.error

try:
    urllib.request.urlopen('htt://www.imooc.com')
except urllib.error.URLError as e:
    print(e.reason)

运行上面的程序,我们会得到一个输出信息:

unknown url type: htt

可以看到,这个内容,就是前面我们输出的

urllib.error.URLError: <urlopen error unknown url type: htt>

尖括号里面的内容,信息被存储在URLError对象里的reason属性,它就说明了导致异常的原因。

上面是我们协议写错,这种情况在真实的爬虫环境中,几乎不会遇到,但是我们常遇到的就是URL访问地址不可达,什么情况会导致这种现象发生?比如有一个网站里面有一个链接指向一个地址,但是那个地址的域名到期了,域名所有者没有续费,那这个域名现在就属于一个不可访问的地址了,我们来模拟这种情况,来看一下会报出什么异常信息。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import urllib.request
import urllib.error

try:
    # 这个域名显然不会有人注册,不排除有人看了教程以后注册这个域名...
    urllib.request.urlopen('http://www.imooc1imooc2imooc3.com')
except urllib.error.URLError as e:
    print(e.reason)

上面的代码运行会输出以下信息:

[Errno 11001] getaddrinfo failed

此处我们明显的发现,多了一个中括号,里面有一个Errno是11001。像这样的错误码还有很多,大家带Errno可以在网上搜索,也可以根据后面的错误信息自己去判断。

HTTPError

刚才我们说的都是URL上的一些错误,那么我们在HTTP请求的时候一定也会遇到很多的问题,比如常见的我们访问的页面不存在。需要注意的是,页面不存在不代表我们的请求没有发到对方的服务器,而是发到对方的服务器以后,对方的服务器没有找到我们要访问的网页,给我们返回一个错误码。这和上面的根本都找不到对方的服务器,有本质上的区别。

那么我们来看一下不捕获异常访问一个不存在的页面会怎样:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import urllib.request

# 显然慕课网目前没有cheshen.html这个页面
urllib.request.urlopen('http://www.imooc.com/cheshen.html')

运行以后,得到我们期望的异常:

urllib.error.HTTPError: HTTP Error 404: Not Found

其中HTTP Error 就是错误编号,这里是404,冒号后面是错误原因,意思是没有找到。 这些编号有很多,大家可以参考百度百科给出的资料:HTTP Error。

相比URLError,HTTPError更容易产生,也更容易让我们的程序变得脆弱不堪,所以没啥说的,捕获异常:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import urllib.request
import urllib.error

try:
    urllib.request.urlopen('http://www.imooc.com/cheshen.html')
except urllib.error.HTTPError as e:
    print(e.reason)
    print(e.code)

运行以后输出了:

Not Found
404

不用我说,大家也知道reason代表错误信息,code代表错误代码。 而我们如果修改上面的代码:

print(type(e.code))

就会看到

<class 'int'>

这说明code是一个int类型的数值,我们可以在代码中进行比较来判断我们是遇到了哪种错误,以决定接下来我们的爬虫如何工作。

作者: 秋名山车神 链接:http://www.imooc.com/article/16026 来源:慕课网