(一)简介
Urllib库是Python内置的HTTP请求库包括四个函数方法
urllib.request 请求模块
urllib.error 异常处理模块
urllib.parse url解析模块
urllib.robotparser robots.txt解析模块
接下里,我们以代码的形式看看如何使用urllib库及相关语法。
(二)urlopen()
我们使用urlopen()来发起一个请求,获取HTML信息,其语法为:
urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)
1.第一个参数url 即为我们要访问的网址:
response = urllib.request.urlopen('http://www.baidu.com') print(response.read().decode('utf-8')) 》》》输出: <html> <head> <meta http-equiv="content-type" content="text/html;charset=utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=Edge"> <meta content="always" name="referrer"> <meta name="theme-color" content="#2932e1"> <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" /> <link rel="search" type="application/opensearchdescription+xml" href="/content-search.xml" title="百度搜索" /> ..............
第一行执行后返回的是一个对象,我们将在后面细说,我们采用他的read()读取网页源代码,请注意,读取时是bytes形式,我们需要解码转换成utf-8格式。所以就如代码所示。
2.第二个是一些额外的数据,当我们访问的是Post时就需要提供此参数。
这里我们在对Get类型和Post类型做一个解释:
Get:请求的url会附带查询参数
Post:请求的url不带参数
再通俗的说,Get是直接请求、打开,Post是请求、对方通过后才能打开。
Get和Post最重要的区别是,Get方式是直接以链接形式访问,链接中包含了所有的参数,当然如果包含了密码什么的是不安全的,不过你可以直观的看到自己提交的内容。
Post则不会在网址上显示所有参数,不过如果你想直接查看提交了什么就不太方便了。可以根据自己的需要酌情选择。
- get请求实例分析:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import urllib.request import urllib.parse url='http://www.baidu.com/s?wd=' key='A-Handsome-cxy的博客' key_code=urllib.request.quote(key) #因为URL里含中文,需要进行编码 url_all=url+key_code header={ 'User-Agent':'Mozilla/5.0 (X11; Fedora; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36' } #头部信息 request=urllib.request.Request(url_all,headers=header) reponse=urllib.request.urlopen(request).read() fh=open("./baidu.html","wb") #写入文件中 fh.write(reponse) fh.close()
- post请求实例分析:
1 #!/usr/bin/env python3 2 # -*- coding: utf-8 -*- 3 4 import urllib.request 5 import urllib.parse 6 7 url='http://www.iqianyue.com/mypost' 8 header={ 9 'User-Agent':'Mozilla/5.0 (X11; Fedora; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36' 10 } 11 12 data={'name':'fengxin','pass':'123'} 13 postdata=urllib.parse.urlencode(data).encode('utf8') #进行编码 14 request=urllib.request.Request(url,postdata) 15 reponse=urllib.request.urlopen(request).read() 16 17 fhandle=open("./1.html","wb") 18 fhandle.write(reponse) 19 fhandle.close()
上诉请求分析如有不理解请查看下列博客详解:
https://blog.csdn.net/fengxinlinux/article/details/77281253?locationNum=7&fps=1
3.第三个timeout是一个超时设置,即在设定时间内没有响应的话就会抛出一些异常。
response = urllib.request.urlopen('http://httpbin.org/post', timeout=0.1) print(response.read()) 》》》输出: socket.timeout: timed out
所以此时如果不能在0.1秒内使这个网页响应,就会出现此异常。
4.响应是个啥?
之前我们提供通过urlopen返回的是一个对象,具体是什么呢,我们用代码看看:
1 response = urllib.request.urlopen('https://www.python.org') 2 print(type(response)) 3 4 5 》》》输出: 6 <class 'http.client.HTTPResponse'>
可以看到,返回的是一个HTTP对象,在这个对象里,有三个比较重要的内容就是状态码和响应头以及1个read(),还是用代码看看:
- 状态码和响应头:
1 response = urllib.request.urlopen('http://www.python.org') 2 print(response.status) 3 print(response.getheaders()) 4 print(response.getheader('Server')) 5 6 7 》》》输出: 8 200 9 10 [('Server', 'nginx'), ('Content-Type', 'text/html; charset=utf-8'), ('X-Frame-Options', 'SAMEORIGIN'), ('x-xss-protection', '1; mode=block'), ('X-Clacks-Overhead', 'GNU Terry Pratchett'), ('Via', '1.1 varnish'), ('Content-Length', '49060'), ('Accept-Ranges', 'bytes'), ('Date', 'Sun, 23 Sep 2018 06:13:30 GMT'), ('Via', '1.1 varnish'), ('Age', '3371'), ('Connection', 'close'), ('X-Served-By', 'cache-iad2137-IAD, cache-tyo19938-TYO'), ('X-Cache', 'HIT, HIT'), ('X-Cache-Hits', '2, 5557'), ('X-Timer', 'S1537683210.488422,VS0,VE0'), ('Vary', 'Cookie'), ('Strict-Transport-Security', 'max-age=63072000; includeSubDomains')] 11 12 nginx
一般我们判断请求是否成功时都会用到这个状态码
- read():
1 response = urllib.request.urlopen('https://www.python.org') 2 print(response.read().decode('utf-8')) 3 4 5 》》》输出: 6 <!doctype html> 7 <!--[if lt IE 7]> <html class="no-js ie6 lt-ie7 lt-ie8 lt-ie9"> <![endif]--> 8 <!--[if IE 7]> <html class="no-js ie7 lt-ie8 lt-ie9"> <![endif]--> 9 <!--[if IE 8]> <html class="no-js ie8 lt-ie9"> <![endif]--> 10 <!--[if gt IE 8]><!--><html class="no-js" lang="en" dir="ltr"> <!--<![endif]--> 11 .........
read()获取响应体的内容,但是是以字节流的形式,所以需要我们解码转换成 utf-8
(三)Request()
在上面的例子里,urlopen()的参数就是一个url地址。
但是如果需要执行更复杂的操作,比如增加HTTP报头,则必须创建一个Request实例来作为urlopen()的参数,而需要访问的url地址则作为Request实例的参数。
先举个简单的例子:
1 #先实例化一个request对象,然后将网址当作参数传给他,再将其当作参数给urlopen 2 request = urllib.request.Request('http://python.org') 3 response = urllib.request.urlopen(request) 4 print(response.read().decode('utf-8'))
结果跟上一段直接将url给urlopen()一样,但是,新建的Request实例,除了必须要有url参数之外,还可以设置另外两个参数:
1.data(默认为空):是伴随url提交的数据(比如post的数据),同时HTTP请求将从“GET”方式改为“POST”方式。
2.headers(默认为空):是一个字典,包含了需要发送的HTTP报头的键值对。
有些网页为了防止别人恶意采集其信息所以进行了一些反爬虫的设置,而我们又想进行爬取。这时设置一些headers信息(当前网页的User-Agent),模拟成浏览器去访问这些网站
看下列代码:
1 from urllib import request, parse 2 3 url = 'http://httpbin.org/post' 4 headers = { 5 'User-Agent':'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)', 6 'Host':'httpbin.org' 7 } 8 9 dict = { 10 'name':'Germey' 11 } 12 13 data = bytes(parse.urlencode(dict), encoding='utf-8') 14 req =request.Request(url=url, data=data, headers=headers) 15 response = request.urlopen(req) 16 print(response.read().decode('utf-8')) 17 18 》》》输出: 19 { 20 "args": {}, 21 "data": "", 22 "files": {}, 23 "form": { 24 "name": "Germey" 25 }, 26 "headers": { 27 "Accept-Encoding": "identity", 28 "Connect-Time": "1", 29 "Connection": "close", 30 "Content-Length": "11", 31 "Content-Type": "application/x-www-form-urlencoded", 32 "Host": "httpbin.org", 33 "Total-Route-Time": "0", 34 "User-Agent": "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)", 35 "Via": "1.1 vegur", 36 "X-Request-Id": "f96e736e-0b8a-4ab4-9dcc-a970fcd2fbbf" 37 }, 38 "json": null, 39 "origin": "219.238.82.169", 40 "url": "http://httpbin.org/post" 41 }
(四)Handler
使用各种处理器Handler,可在API文档中查看,可以辅助我们处理各种操作,不必深究。
1.设置代理
使用同一个IP去爬取同一个网站上的网页,久了之后会被该网站服务器屏蔽。这时我们就需要代理服务器,这里我们以ProxyHandler为例
1 import urllib.request 2 3 proxy_handler = urllib.request.ProxyHandler({ 4 'http': 'http://127.0.0.1:9743', 5 'https': 'https://127.0.0.1:9743' 6 }) 7 opener = urllib.request.build_opener(proxy_handler) 8 response = opener.open('http://httpbin.org/get') 9 print(response.read())
这样,服务器识别到我们的IP就可以不停的切换,从而避免被封停。
2.Cookie
在爬虫时,维持登陆状态的作用,比如在淘宝登陆状态下,我们把cookie给清除,那我们登陆状态就会被取消。
1 import http.cookiejar,urllib.request 2 3 #声明一个cookie实例,固定用法 4 cookie = http.cookiejar.CookieJar() 5 #把cookie传给HTTPCookieProcessor这个Handler构造一个handler 6 handler = urllib.request.HTTPCookieProcessor(cookie) 7 #再使用bulid_opener()构造出一个opener 8 opener = urllib.request.build_opener(handler) 9 #直接使用opener打开网址 10 response = opener.open('http://www.baidu.com') 11 #在我们收到回应后,cookie会被自动改成我们访问页面的cookie信息 12 for item in cookie: 13 print(item.name + '=' +item.value) 14 15 16 》》》输出: 17 BAIDUID=E77BF84491E332F6F8F1D451AD0063D3:FG=1 18 BIDUPSID=E77BF84491E332F6F8F1D451AD0063D3 19 H_PS_PSSID=1466_21127_22075 20 PSTM=1490198051 21 BDSVRTM=0 22 BD_HOME=0
这样我们就可以把cookie信息读取出来,如果我们想要复用的话就需要把它写入文件,代码实现如下:
1 #声明一个保存cookie信息的文件 2 filename = 'cookie.txt' 3 #声明一个cookie实例,固定用法 4 cookie = http.cookiejar.MozillaCookieJar(filename) 5 #把cookie传给HTTPCookieProcessor这个Handler构造一个handler 6 handler = urllib.request.HTTPCookieProcessor(cookie) 7 #再使用bulid_opener()构造出一个opener 8 opener = urllib.request.build_opener(handler) 9 #直接使用opener打开网址 10 response = opener.open('http://www.baidu.com') 11 #在我们收到回应后,cookie会被自动改成我们访问页面的cookie信息 12 cookie.save(ignore_discard=True, ignore_expires=True)
在读出后,如果我们想要写入cookie,以下列代码实现:
1 #声明一个cookie实例,固定用法 2 cookie = http.cookiejar.MozillaCookieJar() 3 #调用load()读入cookie信息 4 cookie.load('cookie.txt', ignore_discard=True, ignore_expires=True) 5 #把cookie传给HTTPCookieProcessor这个Handler构造一个handler 6 handler = urllib.request.HTTPCookieProcessor(cookie) 7 #再使用bulid_opener()构造出一个opener 8 opener = urllib.request.build_opener(handler) 9 #直接使用opener打开网址 10 response = opener.open('http://www.baidu.com') 11 #在我们收到回应后,cookie会被自动改成我们访问页面的cookie信息 12 print(response.read().decode('utf-8'))
(五)异常处理
当我们在具体访问时,有可能会遇到打不开的情况,比如一些状态码像404,502等等,此时,如果没有异常处理机制,可能就会使得程序终止。
先看一个小实例:
1 from urllib import request, error 2 try: 3 response = request.urlopen('http://cuiqingcai.com/index.htm') 4 except error.URLError as e: 5 print(e.reason) 6 7 8 》》》输出: 9 Not Found
具体可以捕获的异常,查看API文档后有三个类,其中URLError是父类,而HTTPError是其子类,还有一个ContentTooShortError
因此下面代码我们更详细的完成异常捕捉:
1 from urllib import request, error 2 3 try: 4 response = request.urlopen('http://cuiqingcai.com/index.htm') 5 except error.HTTPError as e: 6 print(e.reason, e.code, e.headers, sep='\n') 7 except error.URLError as e: 8 print(e.reason) 9 else: 10 print('Request Successfully') 11 12 13 》》》输出: 14 Not Found 15 404 16 Server: nginx/1.10.1 17 Date: Wed, 22 Mar 2017 15:59:55 GMT 18 Content-Type: text/html; charset=UTF-8 19 Transfer-Encoding: chunked 20 Connection: close 21 Vary: Cookie 22 Expires: Wed, 11 Jan 1984 05:00:00 GMT 23 Cache-Control: no-cache, must-revalidate, max-age=0 24 Link: <http://cuiqingcai.com/wp-json/>; rel="https://api.w.org/"
(六)URL解析
1.urlparse:
我们先来看看urlparse的基本语法:
urllib.parse.urlparse(urlstring, scheme='', allow_fragments=True)
默认情况下,他会把我们的url分割成各个部分,如下列格式:
scheme://netloc/path;parameters?query#fragment
在我们需要提取url的内容时会有用,我们看如下示例:
1 from urllib.parse import urlparse 2 3 result = urlparse('http://www.baidu.com/index.html;user?id=5#comment') 4 print(type(result), result) 5 6 》》》输出: 7 <class 'urllib.parse.ParseResult'> 8 ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
上述语法中第二个参数为协议类型,如果url自带有协议类型,将无效,否则,会自动为其加上传入的协议类型:
1 from urllib.parse import urlparse 2 3 result = urlparse('www.baidu.com/index.html;user?id=5#comment', scheme='https') 4 print(result) 5 6 》》》输出: 7 ParseResult(scheme='https', netloc='', path='www.baidu.com/index.html', params='user', query='id=5', fragment='comment')
而第三个参数为是否单独显示fragment,默认为显示,当我们把它设为False时,会把它往前传:
from urllib.parse import urlparse result = urlparse('http://www.baidu.com/index.html;user?id=5#comment', allow_fragments=False) print(result) 》》》输出: ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user', query='id=5#comment', fragment='')
2.urlunparse:
顾名思义,这个就是urlparse的反函数,将各分离部分拼接成一个完整url
1 from urllib.parse import urlunparse 2 3 data = ['http', 'www.baidu.com', 'index.html', 'user', 'a=6', 'comment'] 4 print(urlunparse(data)) 5 6 7 》》》输出: 8 http://www.baidu.com/index.html;user?a=6#comment
3.urljoin:
完成两个url的拼接,原则是第二个url存在的部分会覆盖掉第一个原本的部分,第二个url不存在的部分会保留第一个原来的部分
1 from urllib.parse import urljoin 2 3 print(urljoin('http://www.baidu.com', 'FAQ.html')) 4 print(urljoin('http://www.baidu.com', 'https://cuiqingcai.com/FAQ.html')) 5 print(urljoin('http://www.baidu.com/about.html', 'https://cuiqingcai.com/FAQ.html')) 6 print(urljoin('http://www.baidu.com/about.html', 'https://cuiqingcai.com/FAQ.html?question=2')) 7 print(urljoin('http://www.baidu.com?wd=abc', 'https://cuiqingcai.com/index.php')) 8 9 10 》》》输出: 11 http://www.baidu.com/FAQ.html 12 https://cuiqingcai.com/FAQ.html 13 https://cuiqingcai.com/FAQ.html 14 https://cuiqingcai.com/FAQ.html?question=2 15 https://cuiqingcai.com/index.php
4.urlencode:
可以把一个字典对象转换成Get请求参数
1 from urllib.parse import urlencode 2 3 params = { 4 'name': 'germey', 5 'age': 22 6 } 7 base_url = 'http://www.baidu.com?' 8 url = base_url + urlencode(params) 9 print(url) 10 11 12 》》》输出: 13 http://www.baidu.com?name=germey&age=22
OK至此,我们基本上把url库中的常见的知识给梳理完了,如果对urllib库使用还有问题,可以查看相关文档:
https://docs.python.org/3/library/urllib.html