所谓爬虫, 就是模拟浏览器向服务端发送请求,获取响应。
今天来学第一个有关python的爬虫请求库, urllib, 使用此库,即可发送http请求, 获取想要的数据。
在python2 中, 有 urllib 和 urllib2 两个请求库来实现http 请求发送, 但是在Python3 中, 只有urllib.
urlopen()方法发送基本请求 (了解):
使用urllib 库中的 request 模块, 可以方便的发送请求并得到响应
以百度为例:
from urllib import request
response = request.urlopen("http://www.baidu.com")
print(response.read().decode('utf-8'))
以上示例解析为:
1. 从 urllib 库中导入 request模块
2. 使用request 模块调用其中的urlopen 并将url地址传入发送一个请求, 使用一个变量接受响应
3. 调用read() 方法并指定字符集即可打印获取的百度源码。
有些小伙伴可能活感到疑惑, 为什么 response 这个变量可以调用 read() 这个方法呢?
我们来看一下这个变量是什么类型的:
from urllib import request
response = request.urlopen("http://www.baidu.com")
# print(response.read().decode('utf-8'))
print(type(response)) # <class 'http.client.HTTPResponse'>
可以发现, 当我们获取到响应之后, 返回的是一个HTTPResponse 的对象,既然是一个对象,那么此对象中肯定也封装好了相应的属性及方法供我们使用了。
此对象中,主要包括: read, readinto, getheader, getheaders, fileno 等方法, 以及 msg, version, status, reason, debuglever, closed 等属性。
以上案例中, 我们只是简单的获取了一下百度的源码数据, 如果我们想再链接中传递一些参数,该怎么实现呢? 先熟悉一下urlopen 方法的具体参数:
urlopen(url, data=None, [timeout,]*, cafile=None, capath=None, cadefault=False, context=None)
data 参数:
在使用data 参数的时候, 需要使用urllib 库中的 parse 模块中的urlencode 将数据进行字符串转换处理并且使用byte() 方法将其转换为byte 类型,才可以传入到data 参数中, 例如:
from urllib import request, parse
data = bytes(parse.urlencode({'name': 'germey'}), encoding='utf-8')
response = request.urlopen('https://www.httpbin.org/post', data=data)
print(response.read().decode())
timeout 参数:
timeout 参数就是设置请求请求超时,单位为秒,如果超过了设置的超时时间则抛出异常, 如果不设置则使用默认的超时时间
response = request.urlopen('https://www.httpbin.org/post', data=data, timeout=5)
除了这两个参数,还有一些额外的参数, 只是不经常用, 例如, context 参数, 该参数必须是 ssl.SSLContext 类型,用来制定 SSL 的设置, cafile 和 capath 这两个参数分别是指定ca证书以及证书的路径位置, cadefault 参数被弃用了, 默认值是False.
Request 请求类:(重点)
使用urlopen() 方法只能发送基本请求, 如若需要构建一个完整的请求, 例如:传入headers, 构建ua, 那么它就力不从心了
让我们感受一下Request 类的用法:
from urllib import request as req
request = req.Request('http://www.baidu.com')
response = req.urlopen(request)
print(response.read().decode())
可以看到, 依旧使用的是urlopen() 方法, 但是其url参数不再是一个url地址, 而是一个Request 请求类, 通过这个请求类可以灵活的构造请求参数。
Request 类参数说明:
Request(url, data=None, headers={}, origin_req_host=None,
unverifiable=False, method=None)
url 则就是请求的url路径, data 是需要传递的请求数据, 类型依旧是一个bytes 类型, 当然,和前面的使用是一样的, headers 则就是请求头,一般用来做ua 伪装。 origin_req_host则是请求方的IP地址, unverifiable 则表示此请求是否是无法验证的, 如果此请求没有权限获取数据, 则unverifiable为True 默认为False, method 则是一个字符串, 表示请求方式,例如:GET,POST, PUT, DELETE
高级用法:
如果涉及到Cookie 处理, 代理设置等, 则需要借助该模块提供的各个 Handler 来实现,
首先来了解一下 urllib.request模块中的 BaseHandler 类, 它是所有Handler 的父类, 提供了基本的方法,
各个Handler 介绍如下:
HTTPDefaultErrorHandler 用于处理HTTP 响应错误, 所有错误都会抛出HTTPError 类型的异常。
HTTPRedirectHandler 用于处理重定向
HTTPCookieProcessor 用于处理Cookie
ProxyHandler 用于处理代理, 代理默认为空。
HTTPPasswordMgr 用于管理密码, 它维护着用户名密码的对照表
HTTPBasicAuthHandler 用于管理认证, 如果一个链接在打开时需要验证, 那么可以借助它来完成
另外还有 一个很重要的类是OpenerDirector, 我们可以将其称之为 Opener, 因为之前的Request 类已经封装好了基本的业务, 而且其底层调用的也是Opener, 这里我们需要实现更高级的请求, 则需要调用底层方法, 来实现高级请求的发送
Opener类提供了open方法, 我们可以利用Handler 来构建一个Opener类,下面让我们用一个小小的验证请求来体验一下:
在访问某些站点的时候, 会遇到这种情况, 那么我们就需要启动身份验证了。
我们可以通过 HTTPBasiAuthHandler 模块来完成此类的验证, 例如:
from urllib.request import HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, build_opener
from urllib.error import URLError
username = 'admin'
password = 'admin'
url = 'https://ssr3.scrape.center'
p = HTTPPasswordMgrWithDefaultRealm() # 创建p 对象
p.add_password(None, url, username, password) # 使用此对象将url,用户以及密码添加进来, 进行验证
auth_handler = HTTPBasicAuthHandler(p) # 构造handler 对象
opener = build_opener(auth_handler) # 使用handler 对象创建opener
try:
result = opener.open(url)
response = result.read().decode('utf-8')
print(response)
except URLError as e:
print(e.reason)
代理:
使用代理的话, 则需要使用到 ProxyHandler 对象
from urllib.request import ProxyHandler, build_opener
from urllib.error import URLError
# 构建handler 对象
proxy_handler = ProxyHandler({
'http': 'http://127.0.0.1:8080',
'https': 'https://127.0.0.1:8080'
})
# 构建opener
opener = build_opener(proxy_handler)
try:
response = opener.open('https://www.baidu.com')
print(response.read().decode('utf-8'))
except URLError as e:
print(e.reason)
Cookie:
处理cookie也是需要用到相关的 Handler
获取cookie:
from http import cookiejar
from urllib import request
# 创建cookiejar 对象,用来捕获cookie
cookie = cookiejar.CookieJar()
# 创建handler对象, 捕获cookie
handler = request.HTTPCookieProcessor(cookie)
# 构建opener
opener = request.build_opener(handler)
response = opener.open('https://www.baidu.com')
for item in cookie:
print(item.name + "=" + item.value)
同样的, 我们可以将cookie保存至一个txt 的文本文件中:
from urllib import request
from http import cookiejar
filename = 'cookie.txt'
# 创建cookiejar 对象, 并指定将cookie保存的位置
cookie = cookiejar.MozillaCookieJar(filename)
# 创建handler 对象, 捕获cookie
handler = request.HTTPCookieProcessor(cookie)
opener = request.build_opener(handler)
response = opener.open('https://www.baidu.com')
cookie.save(ignore_discard=True, ignore_expires=True)
MozillaCookieJar 类是CookieJar 的子类, 通过这个子类, 我们可以方便的将cookie以Mozilla型的浏览器Cookie 格式进行一个文本保存。
另外, 还有一个LWPCookieJar 同样可以读取和保存Cookie, 只是它会将其保存为 LWP格式
from urllib import request
from http import cookiejar
filename = 'cookie.txt'
cookie = cookiejar.LWPCookieJar(filename)
handler = request.HTTPCookieProcessor(cookie)
opener = request.build_opener(handler)
response = opener.open('https://www.baidu.com')
cookie.save(ignore_discard=True, ignore_expires=True)
保存我们已经了解了, 那么如何读取呢:
from urllib import request
from http import cookiejar
# 创建cookerjar 对象
cookie = cookiejar.LWPCookieJar()
# 读取cookie
cookie.load('cookie.txt', ignore_discard=True, ignore_expires=True)
handler = request.HTTPCookieProcessor(cookie)
opener = request.build_opener(handler)
response = opener.open('https://www.baidu.com')
print(response.read().decode('utf-8'))
异常处理:
在urllib 库的 error 模块中, 封装了由request 模块产生的所有异常功能, 所有由request 模块参数的异常都会抛给error 模块中封装好的异常业务处理功能
URLError:
此 URLError 拥有一个 reason 属性, 调用它就可以将错误信息给打印出来
from urllib import request, error
try:
response = request.urlopen('https://cuiqingcai.com/404')
except error.URLError as e:
print(e.reason)
HTTPError:
HTTPError 是 URLerror 的子类,它提供了3个属性, 分别是:
code : 响应状态码
reason : 错误信息
headers : 返回响应头
from urllib import request, error
try:
response = request.urlopen('https://cuiqingcai.com/404')
except error.HTTPError as e:
print(e.reason, e.code, e.headers, sep='\n')
解析链接:
前面有提到过, urllib这个库还提供了一个 parse 模块, 使用此模块, 我们可以方便的去处理Url 链接地址, 比如 分割, 合并 , 抽取等操作
urlparse:
此方法可以识别url和分段,
from urllib.parse import urlparse
result = urlparse('https://www.baidu.com/index.html;user?id=5#comment')
# <class 'urllib.parse.ParseResult'>
print(type(result))
# ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
print(result)
使用此方法对百度链接解析一个解析分段后, 我们可以发现, 返回的是一个ParseResult 的对象,
urlunparse:
urlunparse 是用来构造url的, 其参数是一个可迭代对象,并且必须传入6为数,
from urllib.parse import urlunparse
data = ['https', 'www.baidu.com', 'index.html', 'user', 'a=6', 'comment']
#https://www.baidu.com/index.html;user?a=6#comment
print(urlunparse(data))
由此可见, 将一个可迭代对象传入到urlunparse 中, 它可以自动的构造一个完整的URL地址
urlsplit:
此方法和urlparse 方法很相似, 但是它只返回五个结果:
# SplitResult(scheme='https', netloc='www.baidu.com', path='/index.html;user', query='id=5', fragment='comment')
print(urlsplit('https://www.baidu.com/index.html;user?id=5#comment'))
urlunspit:
此方法和urlunparse 很相似,但是它只需要传入5个参数即可, 当然传递的参数同样是一个可迭代对象。
data = ['https', 'www.baidu.com', 'index.html', 'a=6', 'comment']
# https://www.baidu.com/index.html?a=6#comment
print(urlunsplit(data))
urljoin:
在写爬虫中, 此方法还是用的比较多的,请务必掌握,使用此方法, 我们可以声明一个base_url, 然后将其与其它的URL进行一个组合, 此方法会自动分析URL结构,并且组合成一个新的URL返回。
from urllib.parse import urljoin
# https://www.baidu.com/index.html
print(urljoin('https://www.baidu.com', 'index.html'))
# https://xiaonan.com/index.html
print(urljoin('https://www.baidu.com', 'https://xiaonan.com/index.html'))
# https://xiaonan.com/index.html
print(urljoin('https://www.baidu.com/about.html', 'https://xiaonan.com/index.html'))
# https://xiaonan.com/index.html?query=2
print(urljoin('https://www.baidu.com/about.html', 'https://xiaonan.com/index.html?query=2'))
# https://xiaonan.com/
print(urljoin('https://www.baidu.com?wd=abc', 'https://xiaonan.com/'))
# https://www.baidu.com?category=2#comment
print(urljoin('https://www.baidu.com', '?category=2#comment'))
# www.baidu.com?category=2#comment
print(urljoin('www.baidu.com', '?category=2#comment'))
# https://www.baidu.com?category=2
print(urljoin('https://www.baidu.com#comment', '?category=2'))
urlencode:
使用此方法,在构造get 请求参数的时候是非常有用的, 例如,我们将参数构造成一个字典, 然后使用此方法将其序列化为URL中的查询参数
from urllib.parse import urlencode
params = {
'name': 'xiaonan',
'age': 27
}
base_url = 'https://www.baidu.com'
url = base_url + urlencode(params)
# https://www.baidu.comname=xiaonan&age=27
print(url)
parse_qs:
有了以上的序列化, 自然会有反序列化, 使用此方法 ,可以将URL 查询参数反序列化为字典对象
query = '?name=zhangsan&age=11'
# {'?name': ['zhangsan'], 'age': ['11']}
print(parse_qs(query))
parse_qsl:
此方法可以将参数转化为元祖组成的列表
query = '?name=zhangsan&age=11'
# {'?name': ['zhangsan'], 'age': ['11']}
print(parse_qs(query))
# [('?name', 'zhangsan'), ('age', '11')]
print(parse_qsl(query))
quote:
使用此方法可以将URL中携带的中文参数进行一个 编码操作
from urllib.parse import quote
name= '张三'
address = '河南'
url = f'https://www.baidu.com?name={quote(name)}&age=24&address={quote(address)}'
# https://www.baidu.com?name=%E5%BC%A0%E4%B8%89&age=24&address=%E6%B2%B3%E5%8D%97
print(url)
unquote:
见名之意, quote 为编码, 那么unquote 则为接码了
from urllib.parse import quote, unquote
name= '张三'
address = '河南'
url = f'https://www.baidu.com?name={quote(name)}&age=24&address={quote(address)}'
# https://www.baidu.com?name=%E5%BC%A0%E4%B8%89&age=24&address=%E6%B2%B3%E5%8D%97
print(url)
# https://www.baidu.com?name=张三&age=24&address=河南
print(unquote(url))
完.......