urlib库
urllib库是python中最基本的网络请求库,可以模拟浏览器的行为,向指定的服务器发送请求,并可以保存服务器返回的数据。
urlopen()
urllib.request模块提供了最基本的构造http请求的方法。利用它可以模拟浏览器的一个请求发起过程,同时它还带有处理授权验证(authentication)、重定向(redirection)、浏览器Cookies以及其他内容。
这里以Python官网为例,我们来把这个网页抓下来
from urllib import request response = request.urlopen('https://www.python.org') print(response.read())
运行结果如下
这里我们只用了两行代码,便完成了Python官网的抓取,输出了网页的源代码。得到源代码之后呢?我们想要的链接、图片地址、文本信息不就可以提取出来了吗?
接下来,看下它返回的到底是什么。利用type()方法输出响应的类型:
from urllib import request response = request.urlopen('https://www.python.org') print(type(response))
输出的结果如下:
<class 'http.client.HTTPResponse'>
可以发现,它是一个HTTPResponse类型的对象,主要包含read(size)、readline()、readlines()、 readinto()、 getheader(name)、 getheaders()、 fileno()等方法, 以及msg、version、status、reason、debuglevel、colsed等属性。
得到这个对象之后,我们把它赋值为response变量,然后就可以调用这些方法和属性,得到返回结果的一系列信息了。
例如,调用read()方法可以得到返回的网页内容,调用status属性可以得到返回结果的状态码,如200代表请求成功,404代表网页未找到等。
利用最基本的urlopen()方法,可以完成最基本的简单网页的GET请求抓取。
如果想给链接传递一些参数,该怎么实现呢?首先看一下urlopen()函数的API:
urllib.request.urlopen(url, data=None, [timeout,]*, cafile=None, capath=None, cadefault=False, context=None)
可以发现,除了第一个参数可以传递url之外,我们还可以传递其他的内容,比如data(附加数据)、timeout(超时时间)等。
下面我们详细说明这几个参数的用法。
-
data参数
data参数是可选的。如果要添加该参数,需要使用bytes()方法将参数转化为字节流编码格式的内容,即bytes类型。另外,如果传递了这个参数,则它请求的方式就不再是GET方式,而是POST方式。
下面用实例来看一下:
import urllib.parse
import urllib.request
data = bytes(urllib.parse.urlencode({'word': 'hello'}), encoding='utf8')
response = urllib.request.urlopen('http://httpbin.org/post', data=data)
print(response.read().decode('utf8'))
这里我们传递了一个参数word, 值是hello。它需要被转码成bytes类型。其中转字节流采用了bytes()方法,该方法的第一个参数需要str(字符串)类型,需要用urllib.parse模块里的urlencode()方法来将参数字典转化为字符串;第二个参数指定编码格式,这里指定为utf8。
这里请求的站点是httpbin.org,它可以提供HTTP请求测试。本次我们请求的URL为http://httpbin.org/post,这个链接可以用来测试POST请求,它可以输出请求的一些信息,其中包含我们传递的data参数。
运行结果如下:
{ "args": {}, "data": "", "files": {}, "form": { "word": "hello" }, "headers": { "Accept-Encoding": "identity", "Content-Length": "10", "Content-Type": "application/x-www-form-urlencoded", "Host": "httpbin.org", "User-Agent": "Python-urllib/3.7" }, "json": null, "origin": "220.170.50.160, 220.170.50.160", "url": "https://httpbin.org/post" }
我们传递的参数出现在了form字段中,这表明是模拟了表单提交的方式,以POST方式传输数据。
-
timeout参数
timeout参数用于设置超时时间,单位为秒。意思就是如果请求超出了设置的这个时间,还没得到响应,就会抛出异常。如果不指定该参数,就会使用全局默认时间。它支持HTTP、HTTPS、FTP请求。
import urllib.request response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1) print(response.read())
运行结果如下:
这里我们设置的超时时间是0.1,程序0.1秒过后,服务器依然没有响应,于是抛出了URLError异常,该异常属于urllib.error模块,错误原因是超时。
-
其他参数
除了data参数和timeout参数外,还有context参数,它必须是ssl.SSLContext类型,用来指定SSL设置。
此外,cafile和capath这两个参数分别指定CA证书和它的路径,这个在请求HTTPS链接会有用。
urlretrieve()
这个函数可以方便的将网页文件(源文件、图片、视频等)保存到本地,以下代码可以非常方便的将百度首页下载到本地
from urllib import request resp = request.urlretrieve('http://www.baidu.com', 'baidu.html')
urlencode()
Ps:单个字符串编码应该使用quote()函数
我们用浏览器发送请求的时候,如果url中包含了中文或者其它特殊字符,那么浏览器会自动的给我们编码。所以当使用代码发送请求,我们需要手动的进行对这些字符进行编码,urlencode()这时候就排上用场了。urlencode()函数可以将存入的字典参数编码为URL查询字符串,即转换成以key1=value1&key2=value2的形式(特殊符号:汉字、&、=等特殊符号编码为%xx)。
from urllib.parse import quote """特殊符号:汉字、&、=等特殊符号编码为%xx """ KEYWORD = '苹果' url = 'https://s.taobao.com/search?q=' + quote(KEYWORD) print(url) #运行结果:https://s.taobao.com/search?q=%E8%8B%B9%E6%9E%9C KEYWORD = '=' url = 'https://s.taobao.com/search?q=' + quote(KEYWORD) print(url) #运行结果:https://s.taobao.com/search?q=%3D """url标准符号:数字字母 """ KEYWORD = 'ipad' url = 'https://s.taobao.com/search?q=' + quote(KEYWORD) print(url) #运行结果:https://s.taobao.com/search?q=ipad KEYWORD = '3346778' url = 'https://s.taobao.com/search?q=' + quote(KEYWORD) print(url) #运行结果:https://s.taobao.com/search?q=3346778
from urllib.parse import urlencode base_url = 'https://m.weibo.cn/api/container/getIndex?' """url标准符号:数字字母""" params1 = { "value":"english", 'page':1 } url1 = base_url + urlencode(params1) print(urlencode(params1)) print(url1) #运行结果 #value=english&page=1 #https://m.weibo.cn/api/container/getIndex?value=english&page=1 """特殊符号:汉字,/,&,=,URL编码转化为%xx的形式""" params2 = { 'name':"王二", 'extra':"/", 'special':'&', 'equal':'=' } url2 = base_url + urlencode(params2) print(urlencode(params2)) print(url2) #运行结果 #name=%E7%8E%8B%E4%BA%8C&extra=%2F&special=%26&equal=%3D #https://m.weibo.cn/api/container/getIndex?name=%E7%8E%8B%E4%BA%8C&extra=%2F&special=%26&equal=%3D print(type(urlencode(params2))) print(type(url2)) #运行结果 #<class 'str'> #<class 'str'>
parse_qs()
可以将编码后的url参数进行解码
from urllib import parse params = {'name': '张三', 'age': 18, 'greet': 'hello world'} qs = parse.urlencode(params) result = parse.parse_qs(qs) print(result) #运行结果:
name=%E5%BC%A0%E4%B8%89&age=18&greet=hello+world
{'name': ['张三'], 'age': ['18'], 'greet': ['hello world']}
urlparse()和urlsplit()
有时候拿到一个url,要想对这个url各个组成部分进行分割,那么这时候可以使用urlpare()或者urlsplit()。示例代码如下:
from urllib import parse url = 'http://www.baidu.com/s?wd=python&username=abc#1' result = parse.urlparse(url) print(result)
运行结果:
ParseResult(scheme='http', netloc='www.baidu.com', path='/s', params='', query='wd=python&username=abc', fragment='1')
我们也可以获取其中的某个属性值
from urllib import parse url = 'http://www.baidu.com/s?wd=python&username=abc#1' result = parse.urlparse(url) print(result) print('scheme:', result.scheme) print('netloc:', result.netloc) print('path', result.path) print('params', result.params) print('query', result.query) print('fragment', result.fragment)
运行结果:
ParseResult(scheme='http', netloc='www.baidu.com', path='/s', params='', query='wd=python&username=abc', fragment='1') scheme: http netloc: www.baidu.com path /s params query wd=python&username=abc fragment 1
我们也可以用urlsplit()来进行分割,其结果和urlparse()运行的结果大致相同,唯一区别是urlsplit()的结果没有params这个属性
from urllib import parse url = 'http://www.baidu.com/s?wd=python&username=abc#1' result = parse.urlsplit(url) print(result) print('scheme:', result.scheme) print('netloc:', result.netloc) print('path', result.path) # print('params', result.params) print('query', result.query) print('fragment', result.fragment)
运行结果:
SplitResult(scheme='http', netloc='www.baidu.com', path='/s', query='wd=python&username=abc', fragment='1') scheme: http netloc: www.baidu.com path /s query wd=python&username=abc fragment
request.Request
我们利用urlopen()方法可以显示最基本请求的发起,但这几个简单的参数 并不足以构建一个完整的请求。如果请求需要加入Headers等信息,就可以利用更 强大的Request类来构建。
首先,我们用实例来感受一下Request的用法:
import urllib.request request = urllib.request.Request('http://python.org') response = urllib.request.urlopen(request) print(response.read().decode('utf8'))
可以发现,我们依然利用urlopen()方法来发送这个请求,只不过这个该方法不再是URL,而是一个Requeset类型的对象。通过构造这个数据结构,一方面我们可以将请求独立成一个对象,另一方面可更加丰富和灵活地配置参数。
它的构造方法如下:
class urllib.request.Request(url, data=None, headers={}, origin_req_host=None, univerifiable=False, method=None)
- 第一个参数url用于请求URL,这是必传参数,其他都是可选参数。
- 第二个参数data如果要传,必须传bytes(字节流)类型的,如果它是字典,可以先用urllib.parse模块里的urlencode()编码。
- 第三个参数是一个字典,它就是请求头,我们可以在构造请求时通过headers参数直接构造,也可以通过调用请求实例的add_header()方法添加。添加请求头常用的用法就是通过修改User-Agent来伪装浏览器,默认的User-Agent是Python-urllib,我们可以通过修改它来伪装浏览器。
- 第四个参数origin_req_host指的是请求方的host名称或者IP地址。
- 第五个参数unverifiable表示这个请求是否是无法验证的,默认是False,意思就是说 用户没有足够的 权限来选择接受这个请求的结果。例如我们请求一个HTML文档的图片,但是我们没有权限自动抓取图像的权限,这时unverifiable的值就是True。
- 第六个参数method是一个字符串,用来指示请求使用的方法。比如GET、POST和PUT等。
from urllib import request from urllib import parse url = 'http://httpbin.org/post' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36', 'Host': 'httpbin.org' } dict = {'name': 'Germey'} data = bytes(parse.urlencode(dict), encoding='utf8') req = request.Request(url=url, headers=headers, data=data, method='POST') response = request.urlopen(req) print(response.read().decode('utf8'))
这个我们通过4个参数构造了一个请求,运行结果如下:
{ "args": {}, "data": "", "files": {}, "form": { "name": "Germey" }, "headers": { "Accept-Encoding": "identity", "Content-Length": "11", "Content-Type": "application/x-www-form-urlencoded", "Host": "httpbin.org", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36" }, "json": null, "origin": "220.170.50.184, 220.170.50.184", "url": "https://httpbin.org/post" }
观察结果可以发现,我们成功设置了data、headers和method。
另外,headers也可以用add_header()方法来添加:
req = request.Request(url=url, data=data, method='POST') req.add_header('User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36')
如此一来,我们就可以更加方便地构造请求,实现请求的发送。
ProxyHandler处理器(代理设置)
很多的网站会检测某一段时间某个IP的访问次数(通过流量统计,系统日志等),如果超过这个阈值,它会禁止这个IP的访问。所以我们可以设置一些代理服务器,每隔一段时间换一个代理,就算IP被禁止,依然可以换个IP继续爬取。urllib中通过ProxyHandler来设置代理服务器,下面代码说明如何自定义opener来使用代理。
from urllib import request # 没有使用代理的 url = 'http://httpbin.org/ip' # resp = request.urlopen(url) # print(resp.read()) # 使用代理 # 1,使用ProxyHandler,传入代理构建一个handler # 2. 使用上面创建的handler构建一个opener # 3. 使用opener发送一个请求
handler = request.ProxyHandler({"http":"125.123.71.211:9999"})
opener = request.build_opener(handler)
resp = opener.open(url)
print(resp.read())
Ps:
- 代理的基本原理:代理实际上指的就是代理服务器,它的功能是代理网络用户去取得网络信息。形象地说,它是网络信息的中转站。
- http://httpbin.org 这个网站可以查看请求的一些参数
- ProxyHandler() 参数是一个字典,字典的key依赖于代理服务器能够接受的类型,一般是"http"或者是"https",值是“ip:port”
运行结果:
b'{\n "origin": "125.123.71.211, 125.123.71.211"\n}\n'
常用的代理:
什么是cookie?
由于http请求是无状态的(HTTP无状态是指HTTP协议对事务处理是没有记忆能力的也就是说服务器不知道客户端是什么状态),也就是说即使第一次和服务器链接并成功登陆后,第二次访问服务器依然不知道当前请求是那个用户。cookie的出现就是为了解决这个问题,第一次登陆后服务器返回一些数据(cookie)给用户浏览器,浏览器将数据保存在本地,当用户发送再次请求时,浏览器会自动的把之前保存在本地的cookie数据发送给服务器,服务器根据用户发送的cookie就能辨别用户了。cookie储存的数据量有限,不同的浏览器有不同储存大小,但一般不超过4kb,因此使用cookie只能储存一些小量的数据。
cookie格式
Set-Cookie: NAME= VALUE; expires = DATE; path = PATH; domain = DOMAIN_NAME;SECURE
- NAME :cookie的名字
- value:cookie值
- expires:过期时间
- path:cookie作用的路径
- domain:cookie作用的域名
- secure:是否只在https下起作用
使用cookielib库和HTTPCookieProcessor模拟登陆
cookie指某些网站为了辨别用户身份们进行会话跟踪而储存在用户本地终端上的数据。Cookie可以保持登陆信息到下次与服务器的会话。
这里以人人网为例。人人网中,要访问个人主页,必须先登陆才能访问。那么我们想要用代码的方式访问,就必须要有正确的Cookie信息。解决的方法有两种,第一种是使用浏览器访问,然后将cookie信息复制下来,放到headers中,进而发送请求。示例代码如下: