如果使用 Python3(Flask) 一步一步模擬一個網頁微信客戶端

[TOC]

一、獲取登錄的二維碼

1.1、打開瀏覽器輸入下面網址

https://wx.qq.com/

按下F12打開開發調試模式。

如果使用 Python3(Flask) 一步一步模擬一個網頁微信客戶端
如果使用 Python3(Flask) 一步一步模擬一個網頁微信客戶端

我們可以看到產生二維碼的圖片的URL爲https://login.weixin.qq.com/qrcode/wbO9FUkKHg==,但是需要後面的一個參數wbO9FUkKHg==,這個隨機碼是怎麼產生的呢,我們再繼續尋找。

如果使用 Python3(Flask) 一步一步模擬一個網頁微信客戶端

現在我們應該清楚了,瀏覽器先請求這個地址獲取生成二維碼的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==

1.3、代碼實現

要實現這個功能,需要對 Python 的模塊 Flask 有一些瞭解。
我們創建一個wechat.py,代碼如下:

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

# [email protected]
# 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

如果使用 Python3(Flask) 一步一步模擬一個網頁微信客戶端

每次刷新都是可以變更二維碼的。

二、掃碼成功

2.1、掃碼狀態

目前我們掃碼之後頁面沒有任何變化,那到底是因爲觸發了頁面的變化呢,我們繼續來探究,我們看到生成二維碼之後,瀏覽器一直請求某個地址,這個地址其實就是服務器返回我們的掃碼狀態。

如果使用 Python3(Flask) 一步一步模擬一個網頁微信客戶端

開始請求一直處於pending狀態,當在一定時間內(25s)探測不要用戶掃碼就中斷,並給出一個返回值408,然後發起下一個探測。

如果使用 Python3(Flask) 一步一步模擬一個網頁微信客戶端

通過我們測試下來,返回值主要有三種狀態,如下

  • 用戶爲掃碼:返回值爲window.code=408
  • 用戶掃碼,沒有點擊登錄:返回值爲window.code=201;window.userAvatar = xxxx
  • 用戶掃碼,並點擊登錄:window.code=200;window.redirect_uri= xxxx

2.2、原理狀態梳理

用戶沒有掃碼,超時狀態。

如果使用 Python3(Flask) 一步一步模擬一個網頁微信客戶端

用戶掃碼,沒有登錄,返回一個用戶頭像的數據,並且立刻重新發起請求,探測用戶是否點擊確定。

如果使用 Python3(Flask) 一步一步模擬一個網頁微信客戶端

掃碼之後頭像的數據如下:

如果使用 Python3(Flask) 一步一步模擬一個網頁微信客戶端

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 -*-

# [email protected]
# 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調試按鈕,然後進行一下掃碼,看看如何變化。

如果使用 Python3(Flask) 一步一步模擬一個網頁微信客戶端

我們可以看到,瀏覽器一直在進行探測登錄狀態,和官方的一樣,那我們掃碼查看一下。

如果使用 Python3(Flask) 一步一步模擬一個網頁微信客戶端

我們可以看到,掃碼之後自動獲取了頭像並且展示出來,因爲我們還沒有設定登錄成功之後的 api,即使登錄也不會有什麼跳轉。

三、確認登錄

3.1、頁面調試

我們繼續打開官方的微信地址,並且打開調試模式,我們查看一下登錄成功之後進行了哪些操作。

用戶登錄之後,會讓我們重定向到一個新的地址獲取初始化的一些參數。

如果使用 Python3(Flask) 一步一步模擬一個網頁微信客戶端

跳轉到新的地址之後,我們獲取到一個 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

如果使用 Python3(Flask) 一步一步模擬一個網頁微信客戶端

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

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

這是一個POST請求,需要的參數正好是我們上面獲取到的,如下圖所示:

如果使用 Python3(Flask) 一步一步模擬一個網頁微信客戶端

並且初始化之後返回用戶通訊錄的一些信息,如下:

如果使用 Python3(Flask) 一步一步模擬一個網頁微信客戶端

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頁面,效果如下:

如果使用 Python3(Flask) 一步一步模擬一個網頁微信客戶端

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章