乐之之

知而行乐,乐而行之,天道酬勤,学无止境。
爬虫笔记【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

  1. 客户请求访问网页
  2. 服务器返回 401 错误,要求认证。
  3. 客户端重新提交请求并附以认证信息,这部分信息将被编码;
  4. 服务器检查信息,通过则以正常服务页面,否则返回 401 错误

二、Digest Auth (使用 MD5 散列算法处理密钥)

  1. 客户发送请求
  2. 收到一个 401 消息,包含一个 Challenge 和一个 Nonce
  3. 客户将用户名密码和 401 消息返回的 challenge 一起 MD5 加密后传给服务器
  4. 服务器检查是否合法。

  若 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()
 

posted on 2023-01-03 20:04  乐之之  阅读(448)  评论(0编辑  收藏  举报