欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

模拟登陆改版后的知乎(最新版)

程序员文章站 2022-04-07 09:03:36
...

今天,想着看看视频,把模拟登陆这一块学习学习,以后弄把*,去爬爬FaceBook什么的。就拿知乎练练手吧,可曾想,知乎竟然改版了!!之前的教程书籍对现在的知乎来说,都是扯淡,连页面都找不到了。下面一起谈谈改版后的纸糊的模拟登陆吧。

页面分析

抓包

首先,打开页面:https://www.zhihu.com/signup?next=%2F(登录网址都变了…),F12输入账号密码(记得把密码输错),点击网络所有,剩下的如图所示,得到请求头。发现几个请求头和正常的不一样(如图所示):
模拟登陆改版后的知乎(最新版)
1. authorization: 应该是js生成的。
2. content-type: 第一次看感觉很没有头绪,接下来和请求参数一起看就会一目了然了。
3. x-udidx-sxrftoken:这两个是验证参数,在网页源码中可以找到。
接下来看一下请求参数:
模拟登陆改版后的知乎(最新版)
然后我们结合请求头里 content-type:multipart/form-data; boundary=…---------------22839196617062
其中multipart/form-data就是一种表单提交方式;后面的 boundary=…---------------22839196617062就是参数中的 “分割线”:
模拟登陆改版后的知乎(最新版)
所以,直接不看那一串,参数就相当于:
client_id = c3cef7c66a1843f8b3a9e6a1e3160e20
grant_type = password
等11个参数。其中比如账户、密码这些都是固定的,多次请求后,发现client_idtimestamp(时间戳) 也是固定的,signature是变动的,那么这个signature 是什么东西呢?

确定参数位置

authorization

通过更换不同的账号进行抓包,发现 authorization 的值是不变的,所以可以说明是直接写到js文件里的,为了验证这一点,找了半小时,总算在[https://static.zhihu.com/heifetz/main.app.da67b2ab04cd46a8caa1.js] 里找到这个:

模拟登陆改版后的知乎(最新版)
所以,上面假设得到证实。而且发现,后面那一串和 client_id 的值一样。

signature

同样是上面的那个js文件,[https://static.zhihu.com/heifetz/main.app.da67b2ab04cd46a8caa1.js],找到这样一块代码:

function (e, t, n) {
    "use strict";
    function r(e, t) {
        var n = Date.now(), r = new a.a("SHA-1", "TEXT");
        return r.setHMACKey("d1b964811afb40118a12068ff74a12f4", "TEXT"), r.update(e), r.update(i.a), r.update("com.zhihu.web"), r.update(String(n)), c({
            clientId: i.a,
            grantType: e,
            timestamp: n,
            source: "com.zhihu.web",
            signature: r.getHMAC("HEX")
        }, t)
    }

所以,signature 的值就是一些变量进行 HMAC 得到的值。

x-udidx-sxrftoken

直接查找网站源代码,搜索全局,定位了这两个:
模拟登陆改版后的知乎(最新版)
后续就是使用正则把他们匹配出来。

代码设计

主函数的书写

需要导入的python库有:

import scrapy
from scrapy.http import Request,FormRequest
import base64
import re
import execjs
import time
from PIL import Image
import os

设置爬虫通用的请求头:

    header = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; …) Gecko/20100101 Firefox/58.0',
        'authorization': 'oauth c3cef7c66a1843f8b3a9e6a1e3160e20'
        }

时间戳的和获取方法:

timestamp = int(time.time() * 1000)

客户端 id

client_id = 'c3cef7c66a1843f8b3a9e6a1e3160e20'

定向登录原网站,这里用到 Request()方法

    def start_requests(self):
        url = 'https://www.zhihu.com/signup?next=%2F'
        return [Request(url, meta={'cookiejar': 1}, callback=self.parse)]

定位验证码网站

    def parse(self, response):
        pat = re.compile('xsrf":"(.*?)&')
        self.xtoken = pat.findall(response.body.decode('utf-8', 'ignore'))[0]
        tempurl = 'https://www.zhihu.com/api/v3/oauth/captcha?lang=en'
        yield Request(tempurl, headers=self.header, meta={"cookiejar": True}, callback=self.end)

确认是否需要验证码,无需验证码直接 post 登陆,需要则 put 访问验证码网址获取验证码。改变后的验证码接口改为:[https://www.zhihu.com/api/v3/oauth/captcha?lang=cn].

 def end(self, response):
        data = response.text
        if re.search('true', data):
            self.i = self.i + 1
            print('需要验证码')
            yield Request('https://www.zhihu.com/api/v3/oauth/captcha?lang=cn', method='put', headers=self.header,
                          meta={"cookiejar": True},
                          callback=lambda response, ki='cn': self.parser_captcha(response, ki))
        else:
            print('无需验证码')
            username = '+86手机号'
            password = '你的密码'
            signature = self.add().call('run', 'password', self.timestamp)
            data = {
                'client_id': self.client_id, 'grant_type': 'password',
                'timestamp': str(self.timestamp), 'source': 'com.zhihu.web',
                'signature': signature, 'username': username,
                'password': password, 'captcha': self.captcha,
                'lang': 'en', 'ref_source': 'homepage', 'utm_source': ''
            }
            urls = 'https://www.zhihu.com/api/v3/oauth/sign_in'
            yield FormRequest(urls, method='post', headers=self.header, meta={"cookiejar": response.meta["cookiejar"]},
                              formdata=data, callback=self.next, )

登陆所需方法:验证码模块。(知乎的验证比较变态,需要post两次。)

POST第一次,发送数据给验证码来源网站.

    def parser_captcha(self, response, ki):
        header = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; …) Gecko/20100101 Firefox/58.0',
            'x - udid': 'ACBCVUdOYguPTkCvnaFKpSly5-s9HmXSCPg=',
            'authorization': 'oauth c3cef7c66a1843f8b3a9e6a1e3160e20',
            'X-Xsrftoken': self.xtoken
        }
        print(ki)
        pat = re.compile('(?s)"img_base64":"(.*?)"')
        data = eval(repr(pat.findall(response.body.decode('utf-8', 'ignore'))[0]).replace('\\\\', '\\'))
        k = base64.b64decode(data)
        with open('D:/captcha.jpg', 'wb') as f:
            f.write(k)
            f.close()
        try:
            im = Image.open('D:/captcha.jpg')
            im.show()
            im.close()
        except:
            print(u'请到 %s 目录找到captcha.jpg 手动输入' % os.path.abspath('captcha.jpg'))
        self.captcha = input("please input the captcha\n>")
        form = {'input_text': str(self.captcha)}
        yield FormRequest(response.url, headers=header, meta={'cookiejar': response.meta['cookiejar']}, formdata=form,
                          callback=self.finall)

POST第二次,发送数据给真正的登陆接口。

    def finall(self, response):
        header = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; …) Gecko/20100101 Firefox/58.0',
            'x - udid': 'ACBCVUdOYguPTkCvnaFKpSly5-s9HmXSCPg=',
            'authorization': 'oauth c3cef7c66a1843f8b3a9e6a1e3160e20',
            'X-Xsrftoken': self.xtoken
        }
        username = '+86手机号'
        print('1')
        password = '你的密码'
        timestamp = int(time.time() * 1000)
        signature = self.add().call('run', 'password', timestamp)
        print(signature)
        data = {
            'client_id': self.client_id, 'grant_type': 'password',
            'timestamp': str(timestamp), 'source': 'com.zhihu.web',
            'signature': signature, 'username': username,
            'password': password, 'captcha': str(self.captcha),
            'lang': 'en', 'ref_source': 'homepage', 'utm_source': ''
        }
        urls = 'https://www.zhihu.com/api/v3/oauth/sign_in'
        print(2)
        print(data)
        yield FormRequest(urls, method='post', headers=header, meta={"cookiejar": response.meta["cookiejar"]},
                          formdata=data, callback=self.next, )

js解密方式,获取 signature
直接把这个js文件下载下[https://static.zhihu.com/heifetz/main.app.da67b2ab04cd46a8caa1.js],用 python 执行就可以了。

 def add(self):
        js1 = execjs.compile("""
                      粘贴代码(由于篇幅太长,没展示)
               """)
        return js1

多次运行测试,发现都没返回图片。
模拟登陆改版后的知乎(最新版)
但是不排除有返回的可能性。
今天就到这吧,欢迎留言交流。
欢迎关注我的个人公众号。
模拟登陆改版后的知乎(最新版)