什么是Urllib
Urllib是python内置的HTTP请求库
包括以下模块
urllib.request 请求模块
urllib.error 异常处理模块
urllib.parse url解析模块
urllib.robotparser robots.txt解析模块
urlopen
关于urllib.request.urlopen参数的介绍:
urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)
url参数的使用
先写一个简单的例子:
import urllib.request ''''' Urllib 模块提供了读取web页面数据的接口,我们可以像读取本地文件一样读取www和ftp上的数据 urlopen 方法用来打开一个url read方法 用于读取Url上的数据 ''' response = urllib.request.urlopen('http://www.baidu.com') print(response.read().decode('utf-8'))
urlopen一般常用的有三个参数,它的参数如下:
urllib.requeset.urlopen(url,data,timeout)
response.read()可以获取到网页的内容,如果没有read(),将返回一个object对象
data参数的使用
上述的例子是通过请求百度的get请求获得百度,下面使用urllib的post请求
这里通过http://httpbin.org/post网站演示(该网站可以作为练习使用urllib的一个站点使用,可以
模拟各种请求操作)。
import urllib.parse import urllib.request data = bytes(urllib.parse.urlencode({'word': 'hello'}), encoding='utf8') print(data) response = urllib.request.urlopen('http://httpbin.org/post', data=data) print(response.read())
这里就用到urllib.parse,通过bytes(urllib.parse.urlencode())可以将post数据进行转换放到urllib.request.urlopen的data参数中。这样就完成了一次post请求。
所以如果我们添加data参数的时候就是以post请求方式请求,如果没有data参数就是get请求方式
timeout参数的使用
在某些网络情况不好或者服务器端异常的情况会出现请求慢的情况,或者请求异常,所以这个时候我们需要给
请求设置一个超时时间,而不是让程序一直在等待结果。例子如下:
import urllib.request response = urllib.request.urlopen('http://httpbin.org/get', timeout=1) print(response.read())
运行之后我们看到可以正常的返回结果,接着我们将timeout时间设置为0.1
运行程序会提示如下错误
所以我们需要对异常进行抓取,代码更改为
import socket import urllib.request import urllib.error try: response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1) except urllib.error.URLError as e: if isinstance(e.reason, socket.timeout): print('TIME OUT') #给个异常处理
响应
响应类型、状态码、响应头
import urllib.request response = urllib.request.urlopen('https://www.python.org') print(type(response))
可以看到结果为:<class 'http.client.httpresponse'="">
我们可以通过response.status、response.getheaders().response.getheader("server"),获取状态码以及头部信息
response.read()获得的是响应体的内容
import urllib.request response = urllib.request.urlopen('https://www.python.org') print(type(response)) print(response.status) # 状态码 200 print(response.getheaders()) # 头部信息 print(response.getheader("server")) # 过滤头部信息server
当然上述的urlopen只能用于一些简单的请求,因为它无法添加一些header信息,如果后面写爬虫我们可以知道,很多情况下我们是需要添加头部信息去访问目标站的,这个时候就用到了urllib.request
Request
设置Headers
有很多网站为了防止程序爬虫爬网站造成网站瘫痪,会需要携带一些headers头部信息才能访问,最长见的有user-agent参数
写一个简单的例子:
import urllib.request request = urllib.request.Request('https://python.org') response = urllib.request.urlopen(request) print(response.read().decode('utf-8'))
给请求添加头部信息,从而定制自己请求网站是时的头部信息
from urllib import request, parse url = 'http://httpbin.org/post' headers = { 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)', 'Host': 'httpbin.org' } dict = { 'name': 'zhaofan' } data = bytes(parse.urlencode(dict), encoding='utf8') req = request.Request(url=url, data=data, headers=headers, method='POST') response = request.urlopen(req) print(response.read().decode('utf-8'))
添加请求头的第二种方式
from urllib import request, parse url = 'http://httpbin.org/post' dict = { 'name': 'Germey' } data = bytes(parse.urlencode(dict), encoding='utf8') req = request.Request(url=url, data=data, method='POST') req.add_header('User-Agent', 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)') response = request.urlopen(req) print(response.read().decode('utf-8'))
这种添加方式有个好处是自己可以定义一个请求头字典,然后循环进行添加
高级用法各种handler 代理,ProxyHandler
通过rulllib.request.ProxyHandler()可以设置代理,网站它会检测某一段时间某个IP 的访问次数,如果访问次数过多,它会禁止你的访问,所以这个时候需要通过设置代理来爬取数据
import urllib.request proxy_handler = urllib.request.ProxyHandler({ 'http': 'http://127.0.0.1:9743', 'https': 'https://127.0.0.1:9743' }) opener = urllib.request.build_opener(proxy_handler) response = opener.open('http://httpbin.org/get') print(response.read())
cookie,HTTPCookiProcessor
cookie中保存中我们常见的登录信息,有时候爬取网站需要携带cookie信息访问,这里用到了http.cookijar,用于获取cookie以及存储cookie
import http.cookiejar,urllib.request
cookie = http.cookiejar.CookieJar() handler = urllib.request.HTTPCookieProcessor opener = urllib.request.build_opener(handler) response = opener.open('http://www.baidu.com') for item in cookie: print(item.name+"="+item.value)
同时cookie可以写入到文件中保存,有两种方式
http.cookiejar.MozillaCookieJar()
http.cookiejar.LWPCookieJar()
当然你自己用哪种方式都可以
具体代码例子如下:
http.cookiejar.MozillaCookieJar()方式
保存
import http.cookiejar, urllib.request
filename = "cookie.txt" cookie = http.cookiejar.MozillaCookieJar(filename) handler = urllib.request.HTTPCookieProcessor(cookie) opener = urllib.request.build_opener(handler) response = opener.open('http://www.baidu.com') cookie.save(ignore_discard=True, ignore_expires=True)
同样的如果想要通过获取文件中的cookie获取的话可以通过load方式,当然用哪种方式写入的,就用哪种方式读取。
获取
import http.cookiejar, urllib.request cookie = http.cookiejar.MozillaCookieJar() # 哪种写入的用哪种方式读取 cookie.load('cookie.txt', ignore_discard=True, ignore_expires=True) handler = urllib.request.HTTPCookieProcessor(cookie) opener = urllib.request.build_opener(handler) response = opener.open('http://www.baidu.com') print(response.read().decode('utf-8'))
异常处理
在很多时候我们通过程序访问页面的时候,有的页面可能会出现错误,类似404,500等错误
这个时候就需要我们捕捉异常,下面先写一个简单的例子
from urllib import request,error try: response = request.urlopen("http://pythonsite.com/1111.html") except error.URLError as e: print(e.reason)
上述代码访问的是一个不存在的页面,通过捕捉异常,我们可以打印异常错误
这里我们需要知道的是在urllb异常这里有两个个异常错误:
URLError,HTTPError
HTTPError是URLError的子类
URLError里只有一个属性:reason,即抓异常的时候只能打印错误信息,类似上面的例子
HTTPError里有三个属性:code,reason,headers,即抓异常的时候可以获得code,reson,headers三个信息,例子如
from urllib import request,error try: response = request.urlopen("http://pythonsite.com/1111.html") except error.HTTPError as e: print(e.reason) print(e.code) print(e.headers) except error.URLError as e: print(e.reason) else: print("reqeust successfully")
同时,e.reason其实也可以在做深入的判断,例子如下:
import socket from urllib import error,request try: response = request.urlopen("http://www.pythonsite.com/",timeout=0.001) except error.URLError as e: print(type(e.reason)) if isinstance(e.reason,socket.timeout): print("time out")
URL解析
urlparse
The URL parsing functions focus on splitting a URL string into its components, or on combining URL components into a URL string.
urllib.parse.urlparse(urlstring, scheme='', allow_fragments=True)
功能一:
from urllib.parse import urlparse result = urlparse("http://www.baidu.com/index.html;user?id=5#comment") print(result)
结果为:
ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
这里就是可以对你传入的url地址进行拆分
同时我们是可以指定协议类型:
result = urlparse("www.baidu.com/index.html;user?id=5#comment",scheme="https")
这样拆分的时候协议类型部分就会是你指定的部分,当然如果你的url里面已经带了协议,你再通过scheme指定的协议就不会生效
urlunpars
其实功能和urlparse的功能相反,它是用于拼接,例子如下:
from urllib.parse import urlunparse data = ['http','www.baidu.com','index.html','user','a=123','commit'] print(urlunparse(data))
结果为:
http://www.baidu.com/index.html;user?a=123#commit
urljoin
这个的功能其实是做拼接的,例子如下:
from urllib.parse import urljoin print(urljoin('http://www.baidu.com', 'FAQ.html')) print(urljoin('http://www.baidu.com', 'https://pythonsite.com/FAQ.html')) print(urljoin('http://www.baidu.com/about.html', 'https://pythonsite.com/FAQ.html')) print(urljoin('http://www.baidu.com/about.html', 'https://pythonsite.com/FAQ.html?question=2')) print(urljoin('http://www.baidu.com?wd=abc', 'https://pythonsite.com/index.php')) print(urljoin('http://www.baidu.com', '?category=2#comment')) print(urljoin('www.baidu.com', '?category=2#comment')) print(urljoin('www.baidu.com#comment', '?category=2'))
结果为:
从拼接的结果我们可以看出,拼接的时候后面的优先级高于前面的url
urlencode
这个方法可以将字典转换为url参数,例子如下
from urllib.parse import urlencode params = { "name":"zhaofan", "age":23, } base_url = "http://www.baidu.com?" url = base_url+urlencode(params) print(url)
结果为:
项目实例
一、urllib库
概念:urllib是Python自带的一个用于爬虫的库,其主要作用就是可以通过代码模拟浏览器发送请求。其常被用到的子模块在Python3中的为urllib.request和urllib.parse,在Python2中是urllib和urllib2。
使用流程:
- 指定url
- 基于urllib的request子模块发起请求
- 获取响应中的数据值
- 持久化存储
二、由易到难的爬虫程序
1、第一个简单的爬虫程序:爬取搜狗首页的页面数据
import urllib.request import urllib.parse #1.指定url url = 'https://www.sogou.com/' #2.发起请求:使用urlopen函数发起请求,该函数返回一个响应对象 response = urllib.request.urlopen(url=url) #3.获取响应对象中的页面数据:read函数可以获取响应对象中byte类型的数据值 page_text = response.read() #4.持久化存储:将爬取的页面数据写入文件进行保存 with open('./sogou.html','w',encoding='utf-8') as fp: fp.write(page_text.decode())#使用decode将page_text转成字符串类型
补充书名:
urlopen函数原型: urllib.request.urlopen(url, data=None, timeout=<object object at 0x10af327d0>, *, cafile=None, capath=None, cadefault=False, context=None) 在上述案例中我们只使用了该函数中的第一个参数url。在日常开发中,我们能用的只有url和data这两个参数。 url参数:指定向哪个url发起请求 data参数:可以将post请求中携带的参数封装成字典的形式传递给该参数(暂时不需要理解,后期会讲) urlopen函数返回的响应对象,相关函数调用介绍: response.headers():获取响应头信息 response.getcode():获取响应状态码 response.geturl():获取请求的url response.read():获取响应中的数据值(字节类型)
2、二进制数据的爬虫:爬取网络上的某张图片数据,且存储到磁盘
import urllib.request import urllib.parse #1.指定url url = 'https://pic.qiushibaike.com/system/pictures/12112/121121212/medium/ZOAND29U4NKNEWEF.jpg' #2.发起请求:使用urlopen函数发起请求,该函数返回一个响应对象 response = urllib.request.urlopen(url=url) #3.获取响应对象中的图片二进制类型的数据 img_data = response.read() #4.持久化存储:将爬取的图片写入本地进行保存 with open('./tupian.png','wb') as fp: fp.write(img_data)
import urllib.request import urllib.parse url = 'https://pic.qiushibaike.com/system/pictures/12112/121121212/medium/ZOAND29U4NKNEWEF.jpg' # 函数原型:urllib.request.urlretrieve(url, filename=None) # 作用:对url发起请求,且将响应中的数据值写入磁盘进行存储 urllib.request.urlretrieve(url=url,filename='./img.png')
3、url的特性:
url必须为ASCII编码的数据值。所以我们在爬虫代码中编写url时,如果url中存在非ASCII编码的数据值,则必须对其进行ASCII编码后,该url方可被使用。
案例:爬取使用搜狗根据指定词条搜索到的页面数据(例如爬取词条为‘周杰伦’的页面数据)
import urllib.request import urllib.parse url = 'https://www.sogou.com/web?query=周杰伦' response = urllib.request.urlopen(url=url) data = response.read() with open('./周杰伦.html','wb') as fp: fp.write(data) print('写入文件完毕') #【注意】上述代码中url存在非ascii编码的数据,则该url无效。如果对其发起请求,则会报如下错误: #UnicodeEncodeError: 'ascii' codec can't encode characters in position 15-17: ordinal not in range
import urllib.request import urllib.parse url = 'https://www.sogou.com/web?query=%s' #对url中的非ascii进行编码.quote函数可以对非ascii的数据值进行ascii的编码 word = urllib.parse.quote('周杰伦') #将编码后的数据值拼接回url中 url = format(url%word) response = urllib.request.urlopen(url=url) data = response.read() with open('./周杰伦.html','wb') as fp: fp.write(data) print('写入文件完毕')
import urllib.request import urllib.parse url = 'https://www.sogou.com/web?' #将get请求中url携带的参数封装至字典中 param = { 'query':'周杰伦' } #对url中的非ascii进行编码 param = urllib.parse.urlencode(param) #将编码后的数据值拼接回url中 url += param response = urllib.request.urlopen(url=url) data = response.read() with open('./周杰伦1.html','wb') as fp: fp.write(data) print('写入文件完毕')
4、通过自定义请求对象,用于伪装爬虫程序请求的身份。
之前在讲解http常用请求头信息时,我们讲解过User-Agent参数,简称为UA,该参数的作用是用于表明本次请求载体的身份标识。如果我们通过浏览器发起的请求,则该请求的载体为当前浏览器,则UA参数的值表明的是当前浏览器的身份标识表示的一串数据。如果我们使用爬虫程序发起的一个请求,则该请求的载体为爬虫程序,那么该请求的UA为爬虫程序的身份标识表示的一串数据。有些网站会通过辨别请求的UA来判别该请求的载体是否为爬虫程序,如果为爬虫程序,则不会给该请求返回响应,那么我们的爬虫程序则也无法通过请求爬取到该网站中的数据值,这也是反爬虫的一种初级技术手段。那么为了防止该问题的出现,则我们可以给爬虫程序的UA进行伪装,伪装成某款浏览器的身份标识。
上述案例中,我们是通过 request模块中的 urlopen发起的请求,该请求对象为 urllib中内置的默认请求对象,我们无法对其进行UA进行更改操作。urllib还为我们提供了一种自定义请求对象的方式,我们可以通过自定义请求对象的方式,给该请求对象中的UA进行伪装(更改)操作。
import urllib.request import urllib.parse url = 'https://www.sogou.com/web?' #将get请求中url携带的参数封装至字典中 param = { 'query':'周杰伦' } #对url中的非ascii进行编码 param = urllib.parse.urlencode(param) #将编码后的数据值拼接回url中 url += param #封装自定义的请求头信息的字典: #将浏览器的UA数据获取,封装到一个字典中。该UA值可以通过抓包工具或者浏览器自带的开发者工具中获取某请求,从中获取UA的值 #注意:在headers字典中可以封装任意的请求头信息 headers={ 'User-Agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' } #自定义请求对象,可以在该请求对象中添加自定义的请求头信息 request = urllib.request.Request(url=url,headers=headers) #使用自定义请求对象发起请求 response = urllib.request.urlopen(request) data = response.read() with open('./周杰伦.html','wb') as fp: fp.write(data)
5、携带参数的post请求:
案例:百度翻译发起post请求
import urllib.request import urllib.parse #通过抓包工具抓取post请求的url post_url='https://fanyi.baidu.com/sug' #封装post请求参数 data={ "kw":"dog" } data=urllib.parse.urlencode(data) #自定义请求头信息字典 headers={ "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36" } #自定义请求对象,然后将封装好的post请求参数赋值给Requst方法的data参数。 #data参数:用来存储post请求的参数 request=urllib.request.Request(post_url,data=data.encode(),headers=headers) #自定义的请求对象中的参数(data必须为bytes类型) response=urllib.request.urlopen(request) response.read()
三、urllib模块的高级操作
1、代理
- 什么是代理:代理就是第三方代替本体处理相关事务。例如:生活中的代理:代购,中介,微商......
- 爬虫中为什么需要使用代理?
一些网站会有相应的反爬虫措施,例如很多网站会检测某一段时间某个IP的访问次数,如果访问频率太快以至于看起来不像正常访客,它可能就会会禁止这个IP的访问。所以我们需要设置一些代理IP,每隔一段时间换一个代理IP,就算IP被禁止,依然可以换个IP继续爬取。
代理的分类:
正向代理:代理客户端获取数据。正向代理是为了保护客户端防止被追究责任。
反向代理:代理服务器提供数据。反向代理是为了保护服务器或负责负载均衡。
import urllib.request import urllib.parse #1.创建处理器对象,在其内部封装代理ip和端口 handler=urllib.request.ProxyHandler(proxies={'http':'95.172.58.224:52608'}) #2.创建opener对象,然后使用该对象发起一个请求 opener=urllib.request.build_opener(handler) url='http://www.baidu.com/s?ie=UTF-8&wd=ip' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36', } request = urllib.request.Request(url, headers=headers) #使用opener对象发起请求,该请求对应的ip即为我们设置的代理ip response = opener.open(request) with open('./daili.html','wb') as fp: fp.write(response.read())
2、cookie
引言:有些时候,我们在使用爬虫程序去爬取一些用户相关信息的数据(爬取张三“人人网”个人主页数据)时,如果使用之前requests模块常规操作时,往往达不到我们想要的目的,例如:
import urllib.request import urllib.parse #指定url url = 'http://www.renren.com/289676607/profile' #自定义请求头信息 headers={ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36', } #自定义请求对象 request = urllib.request.Request(url=url,headers=headers) #发起请求 response = urllib.request.urlopen(request) with open('./renren.html','w') as fp: fp.write(response.read().decode())
【注意】上述代码中,我们爬取到的是登录首页面,而不是张三的个人主页也面。why?首先我们来回顾下cookie的相关概念及作用
cookie概念:当用户通过浏览器首次访问一个域名时,访问的web服务器会给客户端发送数据,以保持web服务器与客户端之间的状态保持,这些数据就是cookie。
cookie作用:我们在浏览器中,经常涉及到数据的交换,比如你登录邮箱,登录一个页面。我们经常会在此时设置30天内记住我,或者自动登录选项。那么它们是怎么记录信息的呢,答案就是今天的主角cookie了,Cookie是由HTTP服务器设置的,保存在浏览器中,但HTTP协议是一种无状态协议,在数据交换完毕后,服务器端和客户端的链接就会关闭,每次交换数据都需要建立新的链接。就像我们去超市买东西,没有积分卡的情况下,我们买完东西之后,超市没有我们的任何消费信息,但我们办了积分卡之后,超市就有了我们的消费信息。cookie就像是积分卡,可以保存积分,商品就是我们的信息,超市的系统就像服务器后台,http协议就是交易的过程。
经过cookie的相关介绍,其实你已经知道了为什么上述案例中爬取到的不是张三个人信息页,而是登录页面。那应该如何抓取到张三的个人信息页呢?
思路:
1、我们需要使用爬虫程序对人人网的登录时的请求进行一次抓取,获取请求中的cookie数据
2、在使用个人信息页的url进行请求时,该请求需要携带 1 中的cookie,只有携带了cookie后,服务器才可识别这次请求的用户信息,方可响应回指定的用户信息页数据
cookiejar对象: - 作用:自动保存请求中的cookie数据信息 - 注意:必须和handler和opener一起使用 cookiejar使用流程: - 创建一个cookiejar对象 import http.cookiejar cj = http.cookiejar.CookieJar() - 通过cookiejar创建一个handler handler = urllib.request.HTTPCookieProcessor(cj) - 根据handler创建一个opener opener = urllib.request.build_opener(handler) - 使用opener.open方法去发送请求,且将响应中的cookie存储到openner对象中,后续的请求如果使用openner发起,则请求中就会携带了cookie
使用 cookiejar实现爬取人人网个人主页页面数据:
#使用cookiejar实现人人网的登陆 import urllib.request import urllib.parse import http.cookiejar cj = http.cookiejar.CookieJar() #请求中的cookie会自动存储到cj对象中 #创建处理器对象(携带cookiejar对象的) handler=urllib.request.HTTPCookieProcessor(cj) #创建opener对象 (携带cookiejar对象) opener=urllib.request.build_opener(handler) #要让cookiejar获取请求中的cookie数据值 url='http://www.renren.com/ajaxLogin/login?1=1&uniqueTimestamp=201873958471' #自定义一个请求对象,让该对象作为opener的open函数中的参数 data={ "email":"www.zhangbowudi@qq.com", "icode":"", "origURL":"http://www.renren.com/home", "domain":"renren.com", "key_id":"1", "captcha_type":"web_login", "password":"40dc65b82edd06d064b54a0fc6d202d8a58c4cb3d2942062f0f7dd128511fb9b", "rkey":"41b44b0d062d3ca23119bc8b58983104", 'f':"https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DpPKf2680yRLbbZMVdntJpyPGwrSk2BtpKlEaAuKFTsW%26wd%3D%26eqid%3Deee20f380002988c000000025b7cbb80" } data=urllib.parse.urlencode(data).encode() request=urllib.request.Request(url,data=data) opener.open(request) #获取当前用户的二级子页面 s_url='http://www.renren.com/289676607/profile' #该次请求中就携带了cookie resonse=opener.open(s_url) with open('./renren.html','wb') as fp: fp.write(resonse.read())