爬虫笔记【2】如何在爬虫中进行HTTP Basic Authentication所适合的用户名和密码认证?
登陆网页前遇到的要求输入用户名和密码的程序,通常称为身份认证程序。HTTP 认证可以保护一个作用域(成为一个 realm)内的资源不受非法访问。当一个请求要求取得受保护的资源时,网页服务器回应一个 401 Unauthorized error 错误码。这个回应包含了一个指定验证方法和领域的 WWW-Authenticate 头信息。把这个领域想象成一个存储着用户名和密码的数据库,它将被用来标识受保护资源的有效用户。比如网站使用 http basic auth 时,尝试访问该网站上标识为 “Private Files”的资源,服务器相应可能是:WWW-Authenticate:Basic realm = "Private Files"。
- 首先,我们需要了解到HTTP 规范中定义了两种认证模式:Basic Auth 和 Digest Auth,认证的基本过程是:
一、Basic Auth
- 客户请求访问网页
- 服务器返回 401 错误,要求认证。
- 客户端重新提交请求并附以认证信息,这部分信息将被编码;
- 服务器检查信息,通过则以正常服务页面,否则返回 401 错误
二、Digest Auth (使用 MD5 散列算法处理密钥)
- 客户发送请求
- 收到一个 401 消息,包含一个 Challenge 和一个 Nonce
- 客户将用户名密码和 401 消息返回的 challenge 一起 MD5 加密后传给服务器
- 服务器检查是否合法。
若 Server 采用 Basic Auth 保护资源,那么你访问这些被保护资源时,就会看到一个用户认证表单,要求输入用户名和密码。用户输入后,用户名和密码都会以 Base64 编码形式发送给服务器。
第一次服务器返回 401 错误时,会返回 headers 字典信息,其中会包含信息:WWW-Authenticate:Basic realm = "cPanel"。我们假定一直用户名和密码,之后利用一定的编码格式将 realm 名,用户名,密码等信息编码;编码之后就可以传递服务器,认证就可通过。
认证步骤:
1、提前登录该网页,登陆后复制其url和headers表单信息。
url = 'https://ssr3.scrape.center/page/1'
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'
}
2、设置用户名和密码
username = 'admin'
password = 'admin'
3、发起请求,拿到返回的headers字典信息。
request = urllib.request.Request(url,headers=headers)
- 若我们将 username 写错,则运行结果为:HTTP Error 401: UNAUTHORIZED
4、创建一个密码管理器,将url、用户名和密码进行添加。
# 创建密码管理器
passmgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
# 因为我们一开始为空值,在这里也放的是空值
# 添加后将始终对URL使用此用户名/密码组合
passmgr.add_password(None, url, username, password)
5、创建AuthHandler,模拟客户端向服务器提交认证信息
# 创建AuthHandler
authhandler = urllib.request.HTTPBasicAuthHandler(passmgr)
6、定义opener对象,调用Handler并加载返回的内容。
- 自定义的 Opener 对象都由 OpenerDirector 加载不同的 Handler 来生成。
- 自定义 opener 需要先初始化一个 OpenerDirector,使用 build_opener 方法实现,这是一个使用调用单一函数调用多个 Handlers 生成 opener 实例的方法。
opener = urllib.request.build_opener(authhandler)
with opener.open(request) as response:
print(response.read().decode('utf-8'))
这样就可以正常返回源代码数据了。
案例:
HTTPS用户认证抓取电影网站信息完整代码
# HTTP basic auth case
# https://ssr3.scrape.center 提供了 Auth demo
# 使用前先登陆该网站,进行设置后可获得相应示例的 request url,复制该 url 到本程序作为初始访问的 url。
from lxml import etree
import urllib.request
import urllib.error
import urllib.parse
class Dian_ying(object):
def __init__(self):
self.url = 'https://ssr3.scrape.center/page/{}'
self.headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.0.0 Safari/527.36'
}
self.number = 1
def get_data(self):
for i in range(1,11):
url = self.url.format(i)
username = 'admin'
password = 'admin'
# 若我们将 username 写错,如username = 'wxhh1'
# 则运行结果为:HTTP Error 401: UNAUTHORIZED
request = urllib.request.Request(url, headers=self.headers)
# Create a password manager
# 创建密码管理器
passmgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
# 因为我们一开始放的是空值
# 添加后将始终对URL使用此用户名/密码组合
passmgr.add_password(None, url, username, password)
# 创建AuthHandler
authhandler = urllib.request.HTTPBasicAuthHandler(passmgr)
opener = urllib.request.build_opener(authhandler)
with opener.open(request) as response:
yield response.read().decode('utf-8')
def parse_data(self,resp):
for resps in resp:
html = etree.HTML(resps)
data_list = html.xpath('//div[@class="el-col el-col-18 el-col-offset-3"]//div[@class="el-card item m-t is-hover-shadow"]')
for data in data_list:
movie_name = data.xpath('./div/div[1]/div[2]/a/h2/text()')[0]
categories = "".join(data.xpath('./div/div[1]/div[2]/div[1]/button/span/text()'))
info_times = "".join(data.xpath('./div/div[1]/div[2]/div[2]/span/text()'))
Release_time = "".join(data.xpath('./div/div[1]/div[2]/div[3]/span/text()'))
score = "".join(data.xpath('./div/div[1]/div[3]/p[1]/text()')).strip()
print(movie_name,categories,info_times,Release_time,score)
print(f"第{self.number}页爬取完成!")
self.number +=1
def run(self):
resp = self.get_data()
self.parse_data(resp)
if __name__ == '__main__':
pro = Dian_ying()
pro.run()