[原]Python爬虫编程思想(11):用urllib请求基础验证页面
2021-07-20 23:14:13 Author: blog.csdn.net(查看原文) 阅读量:100 收藏

        有一些页面并不是我们想访问就能访问的,这些页面都会将各种验证,例如,在第一次访问网页之前,会先弹出如图1所示的登录对话框,只有正确输入用户名和密码才能访问页面,否则会直接跳到错误页面。

        这种验证被称为基础验证,是HTTP验证的一种。输入的用户名和密码会通过Authorization请求头字段发送给服务端,在访问给页面时,如果服务端检测到HTTP请求头中没有Authorization字段时,会设置名为WWW-Authenticate的HTTP响应头字段,同时返回401响应码。WWW-Authenticate字段值的格式如下:

'Basic realm=需要验证的范围'

        其中Basic表示基础验证,也就是需要输入用户名和密码。realm部分表示需要验证的范围,是一个字符串,详细的用法在本节后面的部分介绍。

        当浏览器遇到401状态码,并检测到是基础验证时就会弹出如图3-8所示的验证对话框,正确输入用户名和密码后,浏览器会向服务端发送Authorization请求头字段,该字段的值是Basic encode,其中encode是用户名和密码的Base64编码。由于基础验证的用户名和密码都是以明文发送(尽管被进行了Base64编码,但Base64编码是可逆的)的,所以建议该页面使用HTTPS,这样传输的数据就会使用SSL加密,以保证数据传输过程的安全。

        下面的例子使用Flask编写了一个Web服务器,用于模拟基础验证页面。并使用普通设置HTTP请求头的方式和Handler方式请求这个基础验证页面。

        Flask是基于Python的Web框架,可以用于编写Web应用程序,在使用之前需要使用如下的命令安装。

pip install flask        

本例编写的第一个程序是这个支持基础验证的Web服务器,代码如下:

from flask import Flask
from flask import request
import base64
app = Flask(__name__)
# 判断客户端是否提交了用户名和密码,如果未提交,设置状态码为401,并设置WWW-Authenticate响应头
# auth:Authorization请求头字段的值, response:响应对象
def hasAuth(auth,response):
    if auth == None or auth.strip() == "":
        # 设置响应状态码为401 
        response.status_code = 401
        # 设置响应头的WWW-Authenticate字段,其中localhost是需要验证的范围
        response.headers["WWW-Authenticate"] = 'Basic realm="localhost"'
        # 返回False,表示客户端未提交用户名和密码 
        return False
    return True
# 根路由
@app.route("/")
def index():
    # 创建响应对象,并指定未输入用户名和密码(单击”取消“按钮)或输入错误后的返回内容 
    response = app.make_response('username or password error')
    # 输出所有的HTTP请求头
    print(request.headers)
    # 得到Authorization请求头的值
    auth = request.headers.get('Authorization')
    # 输出Authorization请求头的值
    print('Authorization:',auth)
    # 如果客户端提交了用户名和密码,进行验证
    if hasAuth(auth, response):
        # 将用户名和密码按Base64编码格式解码,这里按空格拆分成两个值,第一个是Basic,第二个是
        # Base64编码后的用户名和密码
        auth = str(base64.b64decode(auth.split(' ')[1]),'utf-8')
        # 用户名和密码之间用冒号(:)分隔,所以需要将它们拆开
        values = auth.split(':')
        # 获取用户名
        username = values[0]
        # 获取密码
        password = values[1]
        print('username:',username)
        print('password:',password)
        # 判断用户名和密码是否正确,如果正确,返回success
        if username == 'bill' and password == '1234':
            return "success"
    return response

if __name__ == '__main__':
    app.run()

        本例为了方便,将用户名和密码分别固定为bill和1234,现在运行AuthServer.py,然后在浏览器中输入http://127.0.0.1:5000,就会弹出如图3-8所示的验证对话框,正确输入用户名和密码后,浏览器会输出success。当再次访问http://127.0.0.1:5000时,浏览器会使用刚才输入的用户名和密码通过Authorization请求头字段发送给服务端,所以不管用户名和密码输入的是否正确,只要单击了“登录”按钮,下次再访问给页面时都不会再弹出如图3-8所示的登录验证对话框。由于用户名和密码都是通过临时Cookie保存的,所以一旦浏览器关闭,再次打开后,访问http://127.0.0.1:5000仍然会弹出如图3-8所示的登录验证对话框。

        由于用户名和密码是通过HTTP请求头的Authorization字段发送给服务端的,所以在访问该页面时可以直接设置Authorization字段。

from urllib import request
import base64
url = 'http://localhost:5000'
headers = {
    'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36',
    'Host':'localhost:5000',
    # 设置Authorization请求字段头,并指定Base64编码的用户名和密码
    'Authorization': 'Basic ' +
                       str(base64.b64encode(bytes('bill:1234','utf-8')),'utf-8')

}
req = request.Request(url = url,headers=headers,method="GET")
response=request.urlopen(req)
print(response.read().decode('utf-8'))

        执行这段代码后,会在Console中输出success。AuthServer在Console中也会输出如图2所示的信息。

        urllib中提供了一些Handler类,这些类可以用来处理各种类型的页面请求,这些类通常都是BaseHandler的子类,例如,HTTPBasicAuthHandler用于处理管理认证,HTTPCookieProcessor用于处理Cookie。本例会使用HTTPBasicAuthHandler处理页面的基础验证。

        使用HTTPBasicAuthHandler的关键是build_opener函数,该函数与urlopen类似,只是可以完成更复杂的工作。实际上,urlopen函数相当于类库为你封装好了及其常用的请求方法,利用它们可以完成基本的请求,但如果要完成更复杂的请求,就需要使用build_opener函数。除此之外,还需要使用HTTPPasswordMgrWithDefaultRealm对象封装请求字段数据,也就是用户名、密码和realm。

from urllib.request import
     HTTPPasswordMgrWithDefaultRealm,HTTPBasicAuthHandler,build_opener
from urllib.error import URLError
username = 'bill'
password = '1234'
url = 'http://localhost:5000'

p = HTTPPasswordMgrWithDefaultRealm()
# 封装realm,url、用户名和密码
p.add_password('localhost',url,username,password)
auth_handler = HTTPBasicAuthHandler(p)
# 发送HTTP请求
opener = build_opener(auth_handler)

result = opener.open(url)
# 获取服务端响应数据
html = result.read().decode('utf-8')
print(html)

运行程序,在Console中会输出success。在AuthServer的Console中会输出如图3所示的信息。

        从图3的输出内容可以看出,客户端发送了两次HTTP请求,第1次没有Authorization字段,这是服务端会返回401错误,如果客户端是浏览器,那么就会弹出如图1所示的验证对话框,但现在客户端是爬虫,所以干脆直接提供现成的用户名和密码,这就是第2次HTTP请求的目的。

add_password函数的第1个参数是realm,如果指定这个参数,那么必须与服务端设置WWW-Authenticate字段时指定的realm相同,本例是localhost。如果add_password函数的第1个参数是None,那么服务端会忽略realm。如果指定的realm不一致,就会抛出如图4所示的异常。

        所以为了安全起见,在发送HTTP请求时可以用try...except语句,以避免程序崩溃。 

try:
    result = opener.open(url)
    html = result.read().decode('utf-8')
    print(html)
except URLError as e:
    print(e.reason)

文章来源: https://blog.csdn.net/nokiaguy/article/details/118945854
如有侵权请联系:admin#unsafe.sh