前言

这是我爬虫的通用思路,每一个网页都可以这样套公式分析,无非就三步

  1. 理思路
  2. 抓包分析
  3. 复现代码

具体分析

理思路

打个比方,以我们学校的电费登陆作为例子

例子

有两个登陆选项

有两个登陆选项

这里选择统一身份认证点击由此登入

跳转到网页登陆

跳转登录

输入账号密码

跳转回业务网页

跳转登录

结束

理一下逻辑图

跳转登录

抓包分析

我使用的抓包工具是charles

关于charles的配置可以看另外一篇文章

首先判断登陆以后的状态

因为我抓得多了,很快就能得出结论。

很明显登陆以后是由cookie来保持登陆状态的,那么我们只要直接分析跳转后登陆的逻辑,然后再获取跳转回来的链接,访问这个链接即可获得cookie实现逻辑

如果是完全不知道什么逻辑,那首先可以从点击按钮跳转的那一刻开始抓包

点击按钮以后按时间顺序排列的抓包的请求如下

序列

首先是访问了

http://cwsf.whut.edu.cn/casLogin

这个http返回状态为302

302

重定向位置为

Location: http://zhlgd.whut.edu.cn/tpass/login?service=http%3A%2F%2Fcwsf.whut.edu.cn%2FcasLogin

再从时间顺序可以看到下一个正好对得上就是302的链接

序列

那这里跟进去,随便翻翻

登陆页面

很明显是登陆的页面

流程图

再看刚才的流程图,下一步就是输入账号密码登陆,那么我们就抓包这个登陆的请求

流程图

随便输然后观察产生的http请求记录

http记录

这里产生了两个请求

第一个请求暂且不管

第二个是post请求,可以推断他就是将账号密码发送到后端的请求

我们再想想思路,前端我们输入了账号密码,按下了登陆按钮

我们登陆需要的数据就是

  1. 账号
  2. 密码

仔细观察一下第二个post请求

登陆请求

表单中有5个值,如果解决了这5个值,将请求POST到后端,我们是不是就可以实现登陆了呢,那么我们这里分析一下这5个值

ps:为什么是表单呢?因为http请求头中的Content-type已经规定了发送数据的格式为application/x-www-form-urlencoded,有兴趣可以自己看一下http协议

Content-type

接着说这5个值,我第一次看的时候也看不出来这些是什么,那么我们回到浏览器中去看看

按F12进入浏览器的调试模式

点击这个选择器

调试模式选择器

把鼠标放到账号密码上看看

调试模式选择器

按左键

这里调试器跳转到html对应的html语句
调试

换位思考一下如果你是前端,你要实现登陆,是不是在登录按钮写js函数,填入账号密码应该是没有任何的js逻辑的,所以我们这里用选择器点击登陆按钮找到登陆的js源代码

登陆

跟进这个event

跟进这个event

跟进这个event

这里跟到了js代码,稍微上下看一下,就能发现了

跟进这个event

这里有个我们感兴趣的post请求,他出现了ulpl,上面还定义了lt,这里就是我们要找的参数,对应我们刚才找到的post请求的参数

登陆请求

上面这个lt是使用id选择器查找的html元素,那么我们回到html中找到这个元素

lt

现在找到了其中一个元素lt,我们还要考虑他是否是变化的,我们刷新一下网页,看他是否是变化的,如果是,那么我们直接引用;如果不是,那么我们每次都取他的lt值作为参数

刷新后lt

很明显他是会变化的,实际上不刷新也很好判断他是变化的,如果判断不出来,后面跳转他就是靠这个返回到原来的页面实现cookie登陆的,就可以知道只能是变化的

然后接下来ul和pl

我们在这里下断点,然后再点击登陆,看看他是如何运作的

下断点

点击登陆,调试器会在这里中断

记住要断那个encrypt函数,点encrypt函数下断

下断点

分析这段js

	$.ajax({
		 url : "rsa",
		 dataType : "json",
		 type : "POST",
		 success:function(data){
			var encrypt = new JSEncrypt();
			encrypt.setPublicKey(data.publicKey);
			$("#ul").val(encrypt.encrypt(u));
			$("#pl").val(encrypt.encrypt(p));
			$("#loginForm")[0].submit();
		 }
	});

这里是向/rsa接口发送了一个请求,在返回数据后将data.publicKey传入到encrypt.setPublicKey()中,再调用了encrypt.encrypt(u),使用id选择器将这个值存入了页面的ul中

rsa接口很明显是刚才的第一个http请求

请求

请求返回结果

{
	"publicKey": "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJdvbyudr+Od9CoAuh46D6DjLgZ5DL9iVNTZK4cAVgaQjmvvC0ASGA/URgnSfyswgdI1/9LsNDPmYi2Xdrxrn7UCAwEAAQ=="
}

然后再看看这个u

u的值

u的值来源

123是从哪来的呢,这不就是用户名吗

总结一下,得出结论ul就是u经过rsa加密得来的加密username,u即明文用户名,下面的pl就是同理的密码

既然公钥有了,那登陆部分就可以直接实现了

至于接下来的部分,只要分析正确登陆后的跳转就可以知道

他就是一个302跳转到底就直接能得到cookie

最终跳转

最终跳转

复现代码

代码我就不写了,请看我的项目

位于第30行的whut_login函数

武理电费登录

    def whut_login(self, service, username, password): # service是跳转过来时候的params
        self.sessions.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35",
        }) # 更换请求头的User-Agent,隐藏爬虫身份
        html = self.sessions.get("http://zhlgd.whut.edu.cn/tpass/login", params={
            "service": service
        })  # 获取html,以便接下来获取tpass
        etree.HTMLParser(encoding="utf-8")
        # tree = etree.parse(local_file_path)
        tree = etree.HTML(html._content.decode("utf-8"))
        tpass = dict(tree.xpath('//*[@id="lt"]')[0].attrib)["value"]  # 获取tpass
        # des = strEnc(username + password + tpass, "1", "2", "3") # 这个是之前的加密逻辑,最近刚更新的加密
        self.sessions.headers.update({})
        self.sessions.cookies.set(domain="whut.edu.cn", path="/", name="cas_hash", value=""),设置cookie
        # print(tpass)
        result = self.sessions.post(
            url="http://zhlgd.whut.edu.cn/tpass/login",
            params={
                "service": service
            },
            #  下面对应参数
            data={
                "rsa": "",
                "ul": encrypt(username), # 请看下面的encrypt函数定义
                "pl": encrypt(password),
                "lt": tpass,
                "execution": "e1s1",
                "_eventId": "submit",
            }, verify=False, allow_redirects=False) # allow_redirects设为False,无论登陆成功与否先不跳转
        if result.headers.get("location") is None:  # 判断是否有location,来看登陆是否成功,如果失败返回False
            return False
        return result.headers["location"] # 成功返回登陆成功后的跳转链接

利用公钥加密

def encrypt(plain):  # 你不会以为加个js我就不会写了吧 这个注释是我github里面写的,在我发布这个以后登录加密逻辑就改了,不知道是不是巧合,我就在项目这写下这个注释
    rsakey = RSA.importKey('''-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJdvbyudr+Od9CoAuh46D6DjLgZ5DL9i
VNTZK4cAVgaQjmvvC0ASGA/URgnSfyswgdI1/9LsNDPmYi2Xdrxrn7UCAwEAAQ==
-----END PUBLIC KEY-----''')
    cipher = Cipher_pkcs1_v1_5.new(rsakey)
    cipher_text = b64encode(cipher.encrypt(plain.encode('utf-8')))
    # print(cipher_text.decode('utf-8'))
    return cipher_text.decode('utf-8')