如果使用 Python3(Flask) 一步一步模拟一个网页微信客户端

Web Weixin Pipeline

       +--------------+     +---------------+   +---------------+
       |              |     |               |   |               |
       |   Get UUID   |     |  Get Contact  |   | Status Notify |
       |              |     |               |   |               |
       +-------+------+     +-------^-------+   +-------^-------+
               |                    |                   |
               |                    +-------+  +--------+
               |                            |  |
       +-------v------+               +-----+--+------+      +--------------+
       |              |               |               |      |              |
       |  Get QRCode  |               |  Weixin Init  +------>  Sync Check  <----+
       |              |               |               |      |              |    |
       +-------+------+               +-------^-------+      +-------+------+    |
               |                              |                      |           |
               |                              |                      +-----------+
               |                              |                      |
       +-------v------+               +-------+--------+     +-------v-------+
       |              | Confirm Login |                |     |               |
+------>    Login     +---------------> New Login Page |     |  Weixin Sync  |
|      |              |               |                |     |               |
|      +------+-------+               +----------------+     +---------------+
|             |
|QRCode Scaned|
+-------------+

一、获取登录的二维码

1.1、打开浏览器输入下面网址

https://wx.qq.com/

按下F12打开开发调试模式。
984981-20190123100651785-239982164.png
984981-20190123100739901-613907484.png

我们可以看到产生二维码的图片的URL为https://login.weixin.qq.com/qrcode/wbO9FUkKHg==,但是需要后面的一个参数wbO9FUkKHg==,这个随机码是怎么产生的呢,我们再继续寻找。
984981-20190123100947801-334904676.png
现在我们应该清楚了,浏览器先请求这个地址获取生成二维码的uuid,然后再把uuid传入之前的url生成二维码。

1.2、梳理原理

获取uuidURL代码如下,多次尝试发现最后一个数字是一直变动的,是时间戳:

https://login.wx.qq.com/jslogin?appid=wx782c26e4c19acffb&redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&fun=new&lang=zh_CN&_=1548209541594

生成二维码的URL如下,后面的参数就是上面URL产生的uuid

https://login.weixin.qq.com/qrcode/weE4D106jA==
API获取 UUID
urlhttps://login.weixin.qq.com/jslogin
methodPOST
dataURL Encode
paramsappid: 应用ID
fun: new 应用类型
lang: zh_CN 语言
**_**: 时间戳

返回数据(String):

window.QRLogin.code = 200; window.QRLogin.uuid = "xxx"

注:这里的appid就是在微信开放平台注册的应用的AppID。网页版微信有两个AppID,早期的是wx782c26e4c19acffb,在微信客户端上显示为应用名称为Web微信;现在用的是wxeb7ec651dd0aefa9,显示名称为微信网页版

API生成二维码
urlhttps://login.weixin.qq.com/l/ uuid
methodGET

1.3、代码实现

要实现这个功能,需要对 Python 的模块 Flask 有一些了解。
我们创建一个wechat.py,代码如下:

#!/usr/bin/python3.6
# -*- coding: UTF-8 -*-

# wangzan18@126.com
# 2018-10-16

from flask import Flask, render_template
import time
import requests
import re


app = Flask(__name__)


@app.route('/login', methods=['GET', 'POST'])
def login():
    """
    获取登录的二维码
    :return:
    """
    if request.method == 'GET':
        ctime = time.time() * 1000   # 模拟一个相同的时间戳
        base_url = 'https://login.wx.qq.com/jslogin?appid=wx782c26e4c19acffb&' \
               'redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&' \
               'fun=new&lang=zh_CN&_={0}'
        url = base_url.format(ctime)   # 字符串拼接,生成新的url
        response = requests.get(url)   # 向新的url发送get请求
        qcode = re.findall('uuid = "(.*)";', response.text)[0]   # 获取二维码的参数uuid
        return render_template('login.html', qcode=qcode)    # 把uuid传给前端模板
    else:
        pass


if __name__ == '__main__':
    app.run(debug=True, host="0.0.0.0")

因为要把参数传给前端模板,所以我们要创建模板文件,按照 Flask 的格式要求,我们在当前目录创建一个文件夹templates,在文件夹下面创建login.html,内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Wechat_login</title>
</head>
<body>
   <div style="width: 300px;margin: 0 auto">
        <h1 style="text-align: center">微信登录</h1>
        <img id="img" style="height: 300px;width: 300px;" src="https://login.weixin.qq.com/qrcode/{{qcode}}" alt="">
   </div>
</body>
</html>

1.4、启动测试

现在我们启动微信,访问http://192.168.1.86:5000/login
984981-20190123103010486-2132390640.png
每次刷新都是可以变更二维码的。

二、扫码成功

2.1、扫码状态

目前我们扫码之后页面没有任何变化,那到底是因为触发了页面的变化呢,我们继续来探究,我们看到生成二维码之后,浏览器一直请求某个地址,这个地址其实就是服务器返回我们的扫码状态。
984981-20190123111541902-1459909934.png
开始请求一直处于pending状态,当在一定时间内(25s)探测不要用户扫码就中断,并给出一个返回值408,然后发起下一个探测。
984981-20190123111722372-230032771.png
通过我们测试下来,返回值主要有三种状态,如下

  • 用户为扫码:返回值为window.code=408
  • 用户扫码,没有点击登录:返回值为window.code=201;window.userAvatar = xxxx
  • 用户扫码,并点击登录:window.code=200;window.redirect_uri= xxxx
API二维码扫描登录
urlhttps://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login
methodGET
paramstip: 1 未扫描 0 已扫描
uuid: xxx
**_**: 时间戳

返回数据(String):

window.code=xxx;

xxx:
    408 登陆超时
    201 扫描成功
    200 确认登录

当返回200时,还会有
window.redirect_uri="https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=xxx&uuid=xxx&lang=xxx&scan=xxx";

2.2、原理状态梳理

用户没有扫码,超时状态。
984981-20190123113732275-1036097340.png
用户扫码,没有登录,返回一个用户头像的数据,并且立刻重新发起请求,探测用户是否点击确定。
984981-20190123113817367-1848018440.png
扫码之后头像的数据如下:
984981-20190123113930563-1894090151.png

2.3、代码实现

探测是否有用户扫码的url如下:

https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid=YagAJmDN2w==&tip=0&r=-2025309768&_=1548213510530

url里面主要的几个参数,如uuid,时间戳等我们都是可以通过前面获取到的,在之前的wechat.py增加一个函数check_login和对应的 api 接口来检查扫码状态:



#!/usr/bin/python3.6
# -*- coding: UTF-8 -*-

# wangzan18@126.com
# 2018-10-16

from flask import Flask, render_template, request, session, jsonify
import time
import requests
import re


app = Flask(__name__)
app.secret_key = 'wangzan18'


@app.route('/login', methods=['GET', 'POST'])
def login():
    """
    获取登录的二维码
    :return:
    """
    if request.method == 'GET':
        ctime = time.time() * 1000   # 模拟一个相同的时间戳
        base_url = 'https://login.wx.qq.com/jslogin?appid=wx782c26e4c19acffb&' \
               'redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&' \
               'fun=new&lang=zh_CN&_={0}'
        url = base_url.format(ctime)   # 字符串拼接,生成新的url
        response = requests.get(url)   # 向新的url发送get请求
        qcode = re.findall('uuid = "(.*)";', response.text)[0]   # 获取二维码的参数uuid
        session['qcode'] = qcode
        return render_template('login.html', qcode=qcode)    # 把uuid传给前端模板
    else:
        pass

@app.route('/check_login')
def check_login():
    """
    检查用户是否扫码登录
    :return:
    """
    response = {'code': 408}  # 默认用户没有扫码
    qcode = session.get('qcode')  # 获取用户前面获取的 uuid,用于放到下面的 url 里面
    ctime = time.time() * 1000
    check_url = "https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid={0}&tip=0&r=-1437802572&_={1}".format(qcode, ctime)
    ret = requests.get(check_url)  # 扫码返回值
    if 'code=201' in ret.text:
        # 扫码成功
        src = re.findall("userAvatar = '(.*)';", ret.text)[0]  # 获取扫码用户头像
        response['code'] = 201  # 获取扫码的返回值,放到字典response
        response['src'] = src  # 获取用户头像数据,放到字典response
    return jsonify(response)


if __name__ == '__main__':
    app.run(debug=True, host="0.0.0.0")

函数已经写好了,那函数怎么调用了,我们需要在login.html增加一段ajax代码,去请求调用我们api接口,login.html代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Wechat_login</title>
</head>
<body>
   <div style="width: 300px;margin: 0 auto">
        <h1 style="text-align: center">微信登录</h1>
        <img id="img" style="height: 300px;width: 300px;" src="https://login.weixin.qq.com/qrcode/{{qcode}}" alt="">
   </div>

    <script src="/static/jquery-1.12.4.js"></script>
    <script>
        $(function () {
            checkLogin();
        })
        function checkLogin() {
            $.ajax({
                url:'/check_login',
                type:'GET',
                dataType:'JSON',
                success:function (arg) {
                    if(arg.code === 201){
                        // 扫码
                        $('#img').attr('src',arg.src);
                        checkLogin();
                    }else{
                        checkLogin();
                    }
                }
            })
        }
    </script>
</body>
</html>

我们在ajax代码中看到这里已经写了,用户扫码登录之后就跳转到index,目前这个我们还没有写,先不去验证。
注意代码里面我们使用到一个/static/jquery-1.12.4.js,这个是一个公共标准的 js 文件,大家可以去互联网获取。

2.4、扫码验证

我们同样打开我们的地址,并且打开F12调试按钮,然后进行一下扫码,看看如何变化。
984981-20190123134544986-379995585.png
我们可以看到,浏览器一直在进行探测登录状态,和官方的一样,那我们扫码查看一下。
984981-20190123134655991-947964620.png
我们可以看到,扫码之后自动获取了头像并且展示出来,因为我们还没有设定登录成功之后的 api,即使登录也不会有什么跳转。

三、确认登录

3.1、页面调试

我们继续打开官方的微信地址,并且打开调试模式,我们查看一下登录成功之后进行了哪些操作。

用户登录之后,会让我们重定向到一个新的地址获取初始化的一些参数。
984981-20190123114047410-1116976294.png
跳转到新的地址之后,我们获取到一个 xml 结构的数据,这个数据是用来进行初始化需要的。
请求的地址为https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=AXwgRhcMRSlLwikCn2I_PPn5@qrticket_0&uuid=gdH7PHqg4A==&lang=zh_CN&scan=1548214671&fun=new&version=v2&lang=zh_CN
984981-20190123114401156-1743431362.png

APIwebwxnewloginpage
urlhttps://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage
methodGET
paramsticket: xxx
uuid: xxx
lang: zh_CN 语言
scan: xxx
fun: new

返回数据(XML):

<error>
    <ret>0</ret>
    <message>OK</message>
    <skey>xxx</skey>
    <wxsid>xxx</wxsid>
    <wxuin>xxx</wxuin>
    <pass_ticket>xxx</pass_ticket>
    <isgrayscale>1</isgrayscale>
</error>
APIwebwxinit微信初始化
urlhttps://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?pass_ticket=xxx&skey=xxx&r=xxx
methodPOST
dataJSON
headerContentType: application/json; charset=UTF-8
params{
     BaseRequest: {
         Uin: xxx,
         Sid: xxx,
         Skey: xxx,
         DeviceID: xxx,
     }
}

返回数据(JSON):

{
    "BaseResponse": {
        "Ret": 0,
        "ErrMsg": ""
    },
    "Count": 11,
    "ContactList": [...],
    "SyncKey": {
        "Count": 4,
        "List": [
            {
                "Key": 1,
                "Val": 635705559
            },
            ...
        ]
    },
    "User": {
        "Uin": xxx,
        "UserName": xxx,
        "NickName": xxx,
        "HeadImgUrl": xxx,
        "RemarkName": "",
        "PYInitial": "",
        "PYQuanPin": "",
        "RemarkPYInitial": "",
        "RemarkPYQuanPin": "",
        "HideInputBarFlag": 0,
        "StarFriend": 0,
        "Sex": 1,
        "Signature": "Apt-get install B",
        "AppAccountFlag": 0,
        "VerifyFlag": 0,
        "ContactFlag": 0,
        "WebWxPluginSwitch": 0,
        "HeadImgFlag": 1,
        "SnsFlag": 17
    },
    "ChatSet": xxx,
    "SKey": xxx,
    "ClientVersion": 369297683,
    "SystemTime": 1453124908,
    "GrayScale": 1,
    "InviteStartCount": 40,
    "MPSubscribeMsgCount": 2,
    "MPSubscribeMsgList": [...],
    "ClickReportInterval": 600000
}

进行初始化,初始化的 url 如下:

https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=-2026305663&lang=zh_CN&pass_ticket=Ja8PaHs1heLZzSQihsRnNF%252Fu%252FzoHJQ%252BUHNV7u7N13K9iTJVaM70wfaINLcm4dqcF

这是一个POST请求,需要的参数正好是我们上面获取到的,如下图所示:
984981-20190123114745583-798413934.png
并且初始化之后返回用户通讯录的一些信息,如下:
984981-20190123141525658-629302057.png

3.2、代码实现

我们需要在函数check_login里面新增扫码成功之后的调整,以及跳转之后的页面indexwechat.py代码如下:


from flask import Flask, render_template, request, session, jsonify
import time
import requests
import re
from bs4 import BeautifulSoup

app = Flask(__name__)
app.secret_key = 'wangzan18'


def xml_parser(text):
    """
    格式化xml数据,修改成我们需要的格式
    :param text:
    :return:
    """
    dic = {}
    soup = BeautifulSoup(text, 'html.parser')
    div = soup.find(name='error')
    for item in div.find_all(recursive=False):
        dic[item.name] = item.text
    return dic


@app.route('/login', methods=['GET', 'POST'])
def login():
    """
    获取登录的二维码
    :return:
    """
    if request.method == 'GET':
        ctime = time.time() * 1000   # 模拟一个相同的时间戳
        base_url = 'https://login.wx.qq.com/jslogin?appid=wx782c26e4c19acffb&' \
               'redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&' \
               'fun=new&lang=zh_CN&_={0}'
        url = base_url.format(ctime)   # 字符串拼接,生成新的url
        response = requests.get(url)   # 向新的url发送get请求
        qcode = re.findall('uuid = "(.*)";', response.text)[0]  # 获取二维码的参数
        session['qcode'] = qcode
        return render_template('login.html', qcode=qcode)
    else:
        pass


@app.route('/check_login')
def check_login():
    """
    检查用户是否扫码登录
    :return:
    """
    response = {'code': 408}  # 默认用户没有扫码
    qcode = session.get('qcode')  # 获取用户前面获取的 uuid,用于放到下面的 url 里面
    ctime = time.time() * 1000
    check_url = "https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid={0}&tip=0&r=-1437802572&_={1}".format(qcode, ctime)
    ret = requests.get(check_url)  # 扫码返回值
    if 'code=201' in ret.text:
        # 扫码成功
        src = re.findall("userAvatar = '(.*)';", ret.text)[0]  # 获取扫码用户头像
        response['code'] = 201  # 获取扫码的返回值,放到字典response
        response['src'] = src  # 获取用户头像数据,放到字典response
    elif 'code=200' in ret.text:
        # 确认登录
        redirect_uri = re.findall('redirect_uri="(.*)";', ret.text)[0]   # 获取跳转的url

        # 向redirect_uri地址发送请求,获取凭证相关信息
        redirect_uri = redirect_uri + "&fun=new&version=v2&lang=zh_CN"
        ticket_ret = requests.get(redirect_uri)  # 获取xml参数
        ticket_dict = xml_parser(ticket_ret.text)  # 解析参数变成我们需要的格式
        session['ticket_dict'] = ticket_dict   # 或许我们初始化需要的ticket
        response['code'] = 200
    return jsonify(response)


@app.route('/index')
def index():
    ticket_dict = session.get('ticket_dict')  # 获取初始化需要的ticket
    init_url = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=-1438401779&lang=zh_CN&pass_ticket={0}".format(
        ticket_dict.get('pass_ticket'))   # 调整好初始化的url
    data_dict = {
        "BaseRequest": {
               "Sid": ticket_dict.get('wxsid'),
               "Uin": ticket_dict.get('wxuin'),
               "Skey": ticket_dict.get('skey'),
           }
    }    # post需要的参数
    init_ret = requests.post(url=init_url, json=data_dict)  # 微信登录初始化
    init_ret.encoding = 'utf-8'
    user_dict = init_ret.json()  # 储存返回的json数据
    return render_template('index.html', user_dict=user_dict)  # 把返回的数据传给index.html


if __name__ == '__main__':
    app.run(debug=True, host="0.0.0.0")

然后我们需要在login.html里面添加登录成功之后跳转到接口/index,接口我们已经写好,完整代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Wechat_login</title>
</head>
<body>
   <div style="width: 300px;margin: 0 auto">
        <h1 style="text-align: center">微信登录</h1>
        <img id="img" style="height: 300px;width: 300px;" src="https://login.weixin.qq.com/qrcode/{{qcode}}" alt="">
   </div>

    <script src="/static/jquery-1.12.4.js"></script>
    <script>
        $(function () {
            checkLogin();
        })
        function checkLogin() {
            $.ajax({
                url:'/check_login',
                type:'GET',
                dataType:'JSON',
                success:function (arg) {
                    if(arg.code === 201){
                        // 扫码
                        $('#img').attr('src',arg.src);
                        checkLogin();
                    }else if(arg.code === 200){
                        // 重定向到用户列表
                        location.href = '/index'
                    }else{
                        checkLogin();
                    }
                }
            })
        }
    </script>
</body>
</html>

用户登录成功之后调用接口/index,接口会把页面定向到index.html,其index.html完整代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Wechat</title>
</head>
<body>
  <h1>欢迎登录:{{user_dict.User.NickName}}</h1>
    <h3>最近联系人</h3>
    <ul>
        {% for user in user_dict.ContactList%}
        <li>{{user.NickName}}</li>
        {% endfor %}
    </ul>
    <h3>微信订阅号</h3>
    <ul>
        {% for sub in user_dict.MPSubscribeMsgList %}
        {% for artlist in sub.MPArticleList %}
        <li>{{artlist.Title}}</li>
        {% endfor %}
        {% endfor %}
    </ul>
</body>
</html>

3.3、登录查看

我们点击确认登录之后,就跳转到我们的index.html页面,效果如下:
984981-20190123182452403-1558209578.png

转载于:https://www.cnblogs.com/wzlinux/p/10307542.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值