python爬虫笔记之二 web微信

it2022-05-05  68

一,目标: 开发一个基于FLASK的简单的web微信 显示最近联系人  所有联系人 并能发消息 收消息

 

二,项目目录

后端python代码写在一个manage.py文件中

static目录保存jQuery文件

templates目录保存html文件 包括:login.html, index.html, user_list.html, send.html

 

 

 

三,开发步骤分析:

0,首先是导入的模块 和配置FLASK

from flask import Flask, request, render_template, session, jsonify import time import requests import re from bs4 import BeautifulSoup import json app = Flask(__name__) app.debug = True app.secret_key = 'dasgdh' View Code

 

1,用浏览器访问我的web微信127.0.0.1:5000/login  后台向web微信要二维码并显示出来 此时 进入长轮询 检测是否有人扫码  

1.1  获得uuid并在前端显示二维码图片

登录真正的web微信登录页,浏览器回向url=https://login.wx.qq.com/jslogin?appid=wx782c26e4c19acffb&redirect_uri=https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage&fun=new&lang=zh_CN&_=1531277583541 发GET请求 它返回的响应中有个 uuid  这个uuid用于接下来获得二维码图片。经过分析发现url中的最后一串数字其实是当前时间戳*1000再取整   在前端将二维码的src=‘https://login.weixin.qq.com/qrcode/’+uuid的值。  最后将uuid与cookie保存起来 将uuid传给前端的login.html

all_cookie = {} @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'GET': ctime = str(int(time.time() * 1000)) qcode_url = 'https://login.wx.qq.com/jslogin?appid=wx782c26e4c19acffb&redirect_uri=https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage&fun=new&lang=zh_CN&_={}'.format(ctime) ret = requests.get(qcode_url) all_cookie.update(ret.cookies.get_dict()) # 加登录cookie到总cookie中 qcode = re.findall('uuid = "(.*)";', ret.text)[0] session['qcode'] = qcode # 将qcode保存在session中 return render_template('login.html', qcode=qcode) else: pass View Code <div style="width:200px;margin:0 auto"> <h1 style="text-align: center">微信登录</h1> <img id="img" style="height:200px;width:200px;" src="https://login.weixin.qq.com/qrcode/{{qcode}}" alt=""> </div> View Code

1.2  实现在前端向后端以长轮询的方式检测是否有人扫码

在前端login.html中用ajax的方式向后端持续发送请求,该后端函数会用当前时间戳和uuid的值构成一个url向web微信服务端以长轮询的方式发请求 微信服务端会返回3种结果 code:408 表示没有扫码 code:201有扫码但没有点确认登录 code:200有扫码且确认登录  后端将code和它的值用字典的并json序列化后发给前端 如果是201替换头像,继续向后端发请求; 如果是200跳到index页面; 否则就是408 什么都不做继续向后端发请求 。前端只有收到code的值为200才结束ajax请求 。

<script src="/static/jquery.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> View Code @app.route('/check_login') def check_login(): ''' 发送get请求检测是否扫码或登录 https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid=AbrV4Dilyw==&tip=0&r=-2115571502&_=1531123902227 :return: ''' response = {'code': 408} ctime = str(int(time.time() * 1000)) qcode = session.get('qcode') check_url = 'https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid={0}&tip=0&r=-2115571502&_={1}'.format(qcode, ctime) ret = requests.get(check_url) all_cookie.update(ret.cookies.get_dict()) # 加cookie 1 print(ret.text) if 'code=201' in ret.text: # 已经扫码 获得头像 all_cookie.update(ret.cookies.get_dict()) # 加cookie 2 src = re.findall("userAvatar = '(.*)';", ret.text)[0] response['code'] = 201 response['src'] = src elif 'code=200' in ret.text: # 点击确认登录 all_cookie.update(ret.cookies.get_dict()) # 加cookie 3 redirect_uri = re.findall('redirect_uri="(.*)";', ret.text)[0] # 向redirect_uri发请求,获取凭证信息 redirect_uri = redirect_uri + "&fun=new&version=v2" ticket_ret = requests.get(redirect_uri) print(ticket_ret.text) # 打印一个xml格式的凭证信息 ticket_dict = xml_parse(ticket_ret.text) print(ticket_dict) # 打印一个字典格式的凭证信息 session['ticket_dict'] = ticket_dict session['ticket_cookie'] = ticket_ret.cookies.get_dict() all_cookie.update(ticket_ret.cookies.get_dict()) # 加cookie 4 response['code'] = 200 return jsonify(response) View Code

 

2,用手机端的微信扫码 成功  将用户头像替代二维码的位置显示出来  再次进入长轮询 等待用户在手机端点击确认登录

同上

 

3,用户确认登录,跳到127.0.0.1:5000/index 显示欢迎信息 用户头像 最近联系人列表  “查看所有联系人“链接    ”发送消息“链接

3.1 确认登录后 首先得到微信发过来的redirect_uri  它看起来像个url 但这是个坑要在它后面加上&fun=new&version=v2构成新的url 向其要凭证信息ticket_ret 它是一个xml格式 通过一个自定义函数转成python的字典格式ticket_dict 并加入到session中 供我们使用   以下代码有部分和上面重复了

def xml_parse(xml): dic = {} soup = BeautifulSoup(xml, 'html.parser') div = soup.find(name='error') for item in div.find_all(recursive=False): dic[item.name] = item.text return dic View Code elif 'code=200' in ret.text: # 点击确认登录 all_cookie.update(ret.cookies.get_dict()) # 加cookie 3 redirect_uri = re.findall('redirect_uri="(.*)";', ret.text)[0] # 向redirect_uri发请求,获取凭证信息 redirect_uri = redirect_uri + "&fun=new&version=v2" ticket_ret = requests.get(redirect_uri) print(ticket_ret.text) # 打印一个xml格式的凭证信息 ticket_dict = xml_parse(ticket_ret.text) print(ticket_dict) # 打印一个字典格式的凭证信息 session['ticket_dict'] = ticket_dict session['ticket_cookie'] = ticket_ret.cookies.get_dict() all_cookie.update(ticket_ret.cookies.get_dict()) # 加cookie 4 response['code'] = 200 View Code

3.2 向微信服务端用POST方式要初始化数据user_dict,参数包括URL和json,都需要用到凭证信息,所以先从session中拿到凭证信息ticket_dict。返回前端index.html带上初始化数据user_dict

@app.route('/index') def index(): ''' 初始化 :return: ''' ticket_dict = session.get('ticket_dict') init_url = 'https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=-2133912801&pass_ticket={}'.format(ticket_dict.get('pass_ticket')) # init_url = 'https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r={}&lang=zh_CN'.format(ticket_dict.get('wxuin) data_dict = { 'BaseRequest': { 'DeviceID': "e868731961319272", 'Sid': ticket_dict.get('wxsid'), 'Skey': ticket_dict.get('skey'), 'Uin': ticket_dict.get('wxuin') } } init_ret = requests.post(url=init_url, json=data_dict) init_ret.encoding = 'utf-8' # 以防乱码 设置编码为UTF-8 # print(init_ret.text) # 一个大字典的字符串形式 user_dict = init_ret.json() # 反序列化成一个大字典 是个很大的字典 # print(user_dict) # for user in user_dict['ContactList']: # ContactList是个列表 保存着最近联系人信息 # print(user.get('NickName')) # 取出最近联系人的昵称 print(user_dict['User']) session['current_user'] = user_dict['User'] session['user_dict_SyncKey'] = user_dict['SyncKey'] # 将用户数据的这个大字典中的‘SyncKey’的值保存在session中 以便后续使用 比如178行的get_msg函数 all_cookie.update(init_ret.cookies.get_dict()) # 加cookie 5 # return 'hahhaha' return render_template('index.html', user_dict=user_dict) View Code

 

3.3 来到前端index.html ,从得到的初始化数据user_dict 中获得想要的欢迎信息 和 最近联系人列表。”查看所有联系人“和 ”发送消息“这2个链接直接用a标签实现,比较麻烦的是得到用户头像,

这里不能用src=*****的方式得到 原因是微信那边。所以通过后端的一个函数来动态的得到头像并返回前端

<body> <h1>欢迎登录微信</h1> <div> <img src="/get_img"> <h2>{{user_dict.User.NickName}}</h2> <h2>{{user_dict.User.UserName}}</h2> </div> <h3>最近联系人:</h3> <ul> {% for friend in user_dict.ContactList%} <li>{{friend.NickName}}</li> {% endfor %} </ul> <a href="/user_list">查看所有联系人</a> <a href="/send">发消息</a> View Code @app.route('/get_img') def getimg(): # 获取头像 current_user = session['current_user'] ticket_cookie = session.get('ticket_cookie') head_url = 'http://wx2.qq.com' + current_user['HeadImgUrl'] img_ret = requests.get(head_url, cookies=ticket_cookie, headers={'Content-Type': 'image/jpeg'}) # print(img_ret.text) all_cookie.update(img_ret.cookies.get_dict()) # 加cookie 6 return img_ret.content View Code

 

4,用户点击 “所有联系人“ 跳到 127.0.0.1:5000/user_list  该页面显示所有联系人的昵称 和UserName(唯一用户识别码) 同时也有个”发送消息“的链接

 同样的带上一大堆的参数向微信要所有联系人数据 再返回给前端显示出来

<div> <div style="width: 30%;float: left;"> <h3>{{wx_user_dict.MemberCount}}</h3> <a href="/send">发消息</a> <ul> {% for item in wx_user_dict.MemberList %} <li>昵称:{{item.NickName}}=====UserName:{{item.UserName}}</li> {% endfor %} </ul>> </div> <div style="width: 70%; float: left;"></div> </div> View Code @app.route('/user_list') def user_list(): ticket_cookie = session.get('ticket_cookie') ticket_dict = session.get('ticket_dict') ctime = str(int(time.time() * 1000)) sk = ticket_dict.get('skey') user_list_url = 'https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?lang=zh_CN&r={0}&skey={1}'.format(ctime, sk) r1 = requests.get(user_list_url, cookies=ticket_cookie) all_cookie.update(r1.cookies.get_dict()) # 加cookie 7 r1.encoding = 'utf-8' wx_user_dict = r1.json() # 获得所有联系人 # for i in wx_user_dict: # print(i) # 这里的i是一个字典中的每个key,说明wx_user_dict是个字典 # print(wx_user_dict['MemberCount']) # 打印好友数量 # for i in wx_user_dict['MemberList']: # print(i) # 打印每个好友信息 return render_template('user_list.html', wx_user_dict=wx_user_dict) View Code

 

5,用户点击”发送消息“链接 跳到 127.0.0.1:5000/send  在页面的第一个输入框中输入目标好友的UserName(可在127.0.0.1:5000/user_list页查询到)第二个框中输入聊天内容 单击“发送”按钮 完成发送

5.1来到send.html页,前端是个form表单里面有3个input 第1个发给谁 第2个发什么 第3个提交

<form method="post"> <p>发给好友(输入UserName):<input type="text" name="to" /></p> <p>请输入消息:<input type="text" name="content" /></p> <input type="submit" value="发送" /> </form> View Code

5.2 来到后端,通过从前端和session中得到的数据作为参数 ,向微信服务器发POST请求,一切顺利的话 发送消息成功 注意:发给谁输入的是对方的UserName(唯一标识符)

@app.route('/send', methods=['GET', 'POST']) def send(): if request.method == 'GET': return render_template('send.html') current_user = session['current_user'] from_user = current_user['UserName'] to = request.form.get('to') content = request.form.get('content') ticket_dict = session.get('ticket_dict') msg_url = 'https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?lang=zh_CN&pass_ticket={}'.format(ticket_dict.get('pass_ticket')) ctime = str(int(time.time() * 1000)) data_dict = { 'BaseRequest': { 'DeviceID': "e868731961319272", 'Sid': ticket_dict.get('wxsid'), 'Skey': ticket_dict.get('skey'), 'Uin': ticket_dict.get('wxuin') }, 'Msg': { 'ClientMsgId': ctime, 'Content':content, 'FromUserName': from_user, 'LocalID': ctime, 'ToUserName': to, 'Type': 1 }, 'Scene': 0 } ret = requests.post( url=msg_url, data=json.dumps(data_dict, ensure_ascii=False).encode('utf-8') # data=bytes(json.dumps(data_dict, ensure_ascii=False), encoding='utf-8') ) return ret.text View Code

 

6,如果页面回到127.0.0.1:5000/index  进入长轮询状态 此时若有收到消息,将会在后台打印出来 格式为  "消息************发送人代码------------->接收人代码(如果是群消息接收人就不一定是自己了)"

6.1 来到index.html页 在前端用ajax实现长轮询来接收微信联系人发来的消息 这里只实现了在后端打印出消息 ,发送人,接收人的功能 。水平有限。

<script src="/static/jquery.js"></script> <script> $(function () { checkMsg(); }); function checkMsg() { $.ajax({ url:'/get_msg', type:'GET', success:function () { checkMsg(); } } ) } </script> View Code

6.2 来到后端 接收消息是个长轮询的方式  分为2个步骤 浏览器先向URL1发请求 根据其返回值来判断是否有收到新消息 如果无 什么都不做 继续长轮询 ,如果有 向URL2发请求 获得消息 发送人 接收人等数据

代码如下: if语句是2个步骤的分割线

@app.route('/get_msg') def get_msg(): sync_url = 'https://webpush.wx2.qq.com/cgi-bin/mmwebwx-bin/synccheck' ticket_dict = session.get('ticket_dict') # print(66666) sync_data_list = [] # 从89行的那个大字典中取值 user_dict_SyncKey = session['user_dict_SyncKey'] # print(user_dict_SyncKey) for item in user_dict_SyncKey['List']: temp = '%s_%s' % (item['Key'], item['Val']) sync_data_list.append(temp) sync_data_str = '|'.join(sync_data_list) # print(sync_data_str) # print(all_cookie) nid = int(time.time()) sync_dict = { 'r': nid, 'skey': ticket_dict['skey'], 'sid': ticket_dict['wxsid'], 'uin': ticket_dict['wxuin'], 'deviceid': 'e868731961319272', 'synckey': sync_data_str } response_sync = requests.get(sync_url, params=sync_dict, cookies=all_cookie) print(response_sync.text) if 'selector:"2"' in response_sync.text: # print('selector:2中') fetch_msg_url = 'https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=%s&skey=%s&lang=zh_CN&pass_ticket=%s' % (ticket_dict['wxsid'], ticket_dict['skey'], ticket_dict['pass_ticket']) form_data = { 'BaseRequest': { 'DeviceID': "e868731961319272", 'Sid': ticket_dict['wxsid'], 'Skey': ticket_dict['skey'], 'Uin': ticket_dict['wxuin'] }, 'SyncKey': user_dict_SyncKey, 'rr': str(time.time()) } response_fetch_msg = requests.post(fetch_msg_url, json=form_data) response_fetch_msg.encoding = 'utf-8' res_fetch_msg_dict = json.loads(response_fetch_msg.text) session['user_dict_SyncKey'] = res_fetch_msg_dict['SyncKey'] for item in res_fetch_msg_dict['AddMsgList']: print(item['Content'], '*********', item['FromUserName'], '------->', item['ToUserName']) # print(999999) # time.sleep(2) return 'OK,' View Code

 

四,心得与感受

通过开发一个自定义的web微信,让没有web经验的我了解了一点FLASK框架下的前后端的知识尤其是他们是怎么交互的。知道了session的作用 还有cookie的作用。

更深入的了解了浏览器与服务器的交互过程,为爬虫的深入学习夯实里基础。很有收获。。。

 

转载于:https://www.cnblogs.com/zhaoshuai5015/p/9291546.html


最新回复(0)