首先,网页版微信登录大致分为以下几个流程(都是大家可以通过抓包得到):

1、登陆主页后,会生成一个UUID,这是个用户标识,在后面请求二维码会用到

    def get_uuid(self):
'''获取uuid'''
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&_={}'.format(get_time())
response = self.session.get(url).text
self.uuid = re.findall(r'uuid = "(.*?)"',response)[0] # 文本较少,用正则匹配即可
return self.uuid

2、请求二维码图片

    def qrcode(self):
url = 'https://login.weixin.qq.com/qrcode/{}'.format(self.uuid)
response = self.session.get(url).content # 请求得到二维码,由于是图片,得到字节码即可
with open ('qrcode.jpg','wb') as f:
f.write(response) # 把二维码保存到一张图片里
im = Image.open('qrcode.jpg')
im.show() # 把二维码图片展示出来

3、扫描二维码,得到重定向的链接

    def get_redirect_uri(self):
while True: # 由于需要不断请求,让我们有时间来扫描二维码
url = 'https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid={}&tip=0&r=-2109595288&_={}'.format(self.uuid,get_time())
result = self.session.get(url,allow_redirects=False)
code = re.findall(r'window.code=(.*?);',result.text)[0]
if code == '': # 没有扫描的时候是400,扫描之后就是200
print('已成功扫描二维码!')
break
self.redirect_uri = re.findall(r'window.redirect_uri="(.*?)"',result.text)[0] # 得到一个链接,请求之后会得到一些有用的参数

这个url里面有个参数在前面请求应答中没有的,在js中可以找到,大概是个13位的时间戳

4、请求上面得到的链接,得到一些必要的参数(这些参数在后面的登录、收发消息都是必需的),注意这里一定要不允许重定向,因为请求这个url的时候,会跳转到一个初始化的链接,这样我们将不能正确获得这些参数。

    def get_require_data(self):
result = self.session.get(self.redirect_uri,allow_redirects=False).text # 这里注意一定要去掉重定向
self.skey = re.findall(r'<skey>(.*?)</skey>',result)[0] # 这里用Beautifulsoup也能解析,文本是xml格式的,用匹配标签就可以得到
self.wxsid = re.findall(r'<wxsid>(.*?)</wxsid>',result)[0]
self.wxuin = re.findall(r'<wxuin>(.*?)</wxuin>',result)[0]
self.pass_ticket = re.findall(r'<pass_ticket>(.*?)</pass_ticket>',result)[0]

5、发送登录请求

    def login(self):
url = 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=-2109580211&pass_ticket={}'.format(self.pass_ticket)
params = {
"BaseRequest":{
"Uin":self.wxuin,
"Sid":self.wxsid,
"Skey":self.skey,
"DeviceID":get_DeviceID(), # DeviceID这个参数在前面的应答中没有找到,它是js生成的,通过读js代码,我们可以构造出来
}
}
result = self.session.post(url,data=json.dumps(params,ensure_ascii=False))
result.encoding = 'utf-8'
data = result.json()
user = data['User']
nickname = user['NickName']
username = user['UserName']
self.user_list[nickname] = username # 把自己账号的昵称和用户编号保存起来,方便后面收发消息
self.synckey_list = data['SyncKey'] # synckey 用于后面同步消息
print('已成功登录!!')
self.synckey = format_synckey(self.synckey_list['List']) # 把 synckey构造成查询字符串的模式

至此,就完成了基本的登陆认证过程,其实大部分的登录都差不多,可能有得是通过验证码和账号密码加密的方式,这些到后面再说

6、得到好友列表并保存起来

    def get_userlist(self):
'''把用户的好友列表保存起来,方便后面收发消息'''
url = 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?pass_ticket={}&r={}&seq=0&skey={}'.format(self.pass_ticket,get_time(),self.skey)
response = self.session.get(url)
response.encoding = 'utf-8'
result = response.json()
memberlist = result['MemberList']
for member in memberlist:
nickname = member['NickName']
username = member['UserName']
self.user_list[nickname] = username # 按照昵称-用户编号的一一对应关系保存

下面就到了比较繁琐的环节了,收发消息:

我们在登录的时候就得到一个synckey了,这个东西在每次收消息的请求中都要带上,而且每次收发消息,这个值都会向服务器请求更新一次。那么我们怎么确定正在收发消息呢,微信网页版用的是轮询的方式发送一个请求,这个请求的响应大概是{retcode:”0″,selector:”0″}这样的,如果selector变得不为0了,说明有消息要收发。所以我们也可以通过不断地发送请求,然后判断响应里的selector的值来处理。

1、更新synckey

    def get_new_synckey(self):  # 得到最新的synckey 发送或者接收消息后都有这个操作
url = 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid={}&skey={}&pass_ticket={}'.format(self.wxsid,self.skey,self.pass_ticket)
data = {"BaseRequest":{"Uin":self.wxuin,"Sid":self.wxsid,"Skey":self.skey,"DeviceID":get_DeviceID()},"SyncKey":self.synckey_list,"rr":2112925520}
response = self.session.post(url,data=json.dumps(data,ensure_ascii=False))
response.encoding = 'utf8'
result = response.json()
self.synckey_list = result['SyncKey']
self.synckey = format_synckey(self.synckey_list['List'])

2、检查selector值是否不为0

    def sync_check(self):  # 检查是否有新消息
while True:
# 注意这里的synckey是在url里面,所以要进行urlencode,而且synckey改变之后,url也会变,所以url要放在while True里面
url = 'https://webpush.wx.qq.com/cgi-bin/mmwebwx-bin/synccheck?r={}&skey={}&sid={}&uin={}&deviceid={}&{}'.format(get_time(),self.skey,self.wxsid,self.wxuin,get_DeviceID(),self.synckey)
response = self.session.get(url)
response.encoding = 'utf8'
result = response.text
# print('正在轮询是否有新消息...')
selector = re.findall(r'selector:"(.*?)"',result)[0]
if selector != '':
self.get_msg() # 我这里发消息是在另一个线程,收消息在这个线程
self.get_new_synckey() # 每次都需要更新

3、接受消息

    def get_msg(self):
'''接收消息'''
url = ' https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid={}&skey={}&pass_ticket={}'.format(self.wxsid,self.skey,self.pass_ticket)
data = {
"BaseRequest":{
"Uin":self.wxuin,
"Sid":self.wxsid,
"Skey":self.skey,
"DeviceID":get_DeviceID()},
"SyncKey":self.synckey_list,
"rr":-2123282759}
response = self.session.post(url,data=json.dumps(data))
response.encoding = 'utf-8'
result = response.json()
self.synckey_list = result['SyncKey'] # 接受消息的时候,响应里面有这个值,在更新synckey的时候,参数和响应里都有synckey
self.synckey = format_synckey(self.synckey_list['List'])
msglist = result['AddMsgList']
for msg in msglist:
if msg['ToUserName'] == self.user_list['XXXX']: # 这里面填你自己微信的昵称,不过加上之后不会受到群消息,大家可以试试不加这个判断
fromName = msg['FromUserName']
for k,v in self.user_list.items():
if v == fromName:
fromNickName = k
content = msg['Content']
print('来自{}的消息:{}'.format(fromNickName,content))

4、发送消息

    def send_msg(self):
url = 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?pass_ticket={}'.format(self.pass_ticket)
while True:
msg = input('>>>>>')
if msg == 'q':
break
data = {
"BaseRequest":{
"Uin":self.wxuin,
"Sid":self.wxsid,
"Skey":self.skey,
"DeviceID":get_DeviceID()},
"Msg":{
"Type":1,
"Content":msg,
"FromUserName":self.user_list['xxx'], # 这里填你自己的昵称
"ToUserName":self.user_list['xxx'], # 这里填你想发送消息的好友的昵称,你也可以用input键盘输入的方式
"LocalID":get_time(),
"ClientMsgId":get_time()},
"Scene":0
} self.session.post(url,data=(json.dumps(data,ensure_ascii=False)).encode('utf-8'))
self.get_new_synckey() # 发送消息之后也要更新synckey

5、把发送消息和接受消息写成多线程的方式

大致流程就是这样啦!下面展示全部代码:

 import time
import re
import random
import requests
import urllib3
import json
from urllib import parse
from PIL import Image
from threading import Thread urllib3.disable_warnings() def get_time():
return str(int(time.time()*1000)) def get_DeviceID():
return 'e'+str(round(random.random(),15))[2:17] def format_synckey(synckey_list):
'''
把列表形式转成查询字符串
'''
tem_synckey = ''
for synckey in synckey_list:
tem_synckey += str(synckey['Key']) + '_' +str(synckey['Val']) + '|'
new_synckey = {'synckey':tem_synckey.rstrip('|')}
return parse.urlencode(new_synckey) class WeChat():
def __init__(self):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36'
} # 请求头信息
proxies = {
'http': '192.168.105.71:80',
'https': '192.168.105.71:80'
} # 使用代理
self.user_list = {}
self.session = requests.session()
self.session.headers = headers
self.session.proxies = proxies
self.session.verify = False def get_uuid(self):
'''获取uuid'''
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&_={}'.format(get_time())
response = self.session.get(url).text
self.uuid = re.findall(r'uuid = "(.*?)"',response)[0] # 文本较少,用正则匹配即可
return self.uuid def qrcode(self):
url = 'https://login.weixin.qq.com/qrcode/{}'.format(self.uuid)
response = self.session.get(url).content # 请求得到二维码,由于是图片,得到字节码即可
with open ('qrcode.jpg','wb') as f:
f.write(response) # 把二维码保存到一张图片里
im = Image.open('qrcode.jpg')
im.show() # 把二维码图片展示出来 def get_redirect_uri(self):
while True: # 由于需要不断请求,让我们有时间来扫描二维码
url = 'https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid={}&tip=0&r=-2109595288&_={}'.format(self.uuid,get_time())
result = self.session.get(url,allow_redirects=False)
code = re.findall(r'window.code=(.*?);',result.text)[0]
if code == '': # 没有扫描的时候是400,扫描之后就是200
print('已成功扫描二维码!')
break
self.redirect_uri = re.findall(r'window.redirect_uri="(.*?)"',result.text)[0] # 得到一个链接,请求之后会得到一些有用的参数 def get_require_data(self):
result = self.session.get(self.redirect_uri,allow_redirects=False).text # 这里注意一定要去掉重定向
self.skey = re.findall(r'<skey>(.*?)</skey>',result)[0] # 这里用Beautifulsoup也能解析,文本是xml格式的,用匹配标签就可以得到
self.wxsid = re.findall(r'<wxsid>(.*?)</wxsid>',result)[0]
self.wxuin = re.findall(r'<wxuin>(.*?)</wxuin>',result)[0]
self.pass_ticket = re.findall(r'<pass_ticket>(.*?)</pass_ticket>',result)[0] def login(self):
url = 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=-2109580211&pass_ticket={}'.format(self.pass_ticket)
params = {
"BaseRequest":{
"Uin":self.wxuin,
"Sid":self.wxsid,
"Skey":self.skey,
"DeviceID":get_DeviceID(), # DeviceID这个参数在前面的应答中没有找到,它是js生成的
}
}
result = self.session.post(url,data=json.dumps(params,ensure_ascii=False))
result.encoding = 'utf-8'
data = result.json()
user = data['User']
nickname = user['NickName']
username = user['UserName']
self.user_list[nickname] = username # 把自己账号的昵称和用户编号保存起来,方便后面收发消息
self.synckey_list = data['SyncKey'] # synckey 用于后面同步消息
print('已成功登录!!')
self.synckey = format_synckey(self.synckey_list['List']) # 把 synckey构造成查询字符串的模式 def get_userlist(self):
'''把用户的好友列表保存起来,方便后面收发消息'''
url = 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?pass_ticket={}&r={}&seq=0&skey={}'.format(self.pass_ticket,get_time(),self.skey)
response = self.session.get(url)
response.encoding = 'utf-8'
result = response.json()
memberlist = result['MemberList']
for member in memberlist:
nickname = member['NickName']
username = member['UserName']
self.user_list[nickname] = username # 按照昵称-用户编号的一一对应关系保存 def get_new_synckey(self): # 得到最新的synckey 发送或者接收消息后都有这个操作
url = 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid={}&skey={}&pass_ticket={}'.format(self.wxsid,self.skey,self.pass_ticket)
data = {"BaseRequest":{"Uin":self.wxuin,"Sid":self.wxsid,"Skey":self.skey,"DeviceID":get_DeviceID()},"SyncKey":self.synckey_list,"rr":2112925520}
response = self.session.post(url,data=json.dumps(data,ensure_ascii=False))
response.encoding = 'utf8'
result = response.json()
self.synckey_list = result['SyncKey']
self.synckey = format_synckey(self.synckey_list['List']) def sync_check(self): # 检查是否有新消息
while True:
# 注意这里的synckey是在url里面,所以要进行urlencode,而且synckey改变之后,url也会变,所以url要放在while True里面
url = 'https://webpush.wx.qq.com/cgi-bin/mmwebwx-bin/synccheck?r={}&skey={}&sid={}&uin={}&deviceid={}&{}'.format(get_time(),self.skey,self.wxsid,self.wxuin,get_DeviceID(),self.synckey)
response = self.session.get(url)
response.encoding = 'utf8'
result = response.text
# print('正在轮询是否有新消息...')
selector = re.findall(r'selector:"(.*?)"',result)[0]
if selector != '':
self.get_msg() # 我这里发消息是在另一个线程,收消息在这个线程
self.get_new_synckey() # 每次都需要更新 def send_msg(self):
url = 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?pass_ticket={}'.format(self.pass_ticket)
while True:
msg = input('>>>>>')
if msg == 'q':
break
data = {
"BaseRequest":{
"Uin":self.wxuin,
"Sid":self.wxsid,
"Skey":self.skey,
"DeviceID":get_DeviceID()},
"Msg":{
"Type":1,
"Content":msg,
"FromUserName":self.user_list['xxx'], # 这里填你自己微信昵称
"ToUserName":self.user_list['xxx'], # 这里填你想发送消息的好友的昵称,你也可以用input键盘输入的方式
"LocalID":get_time(),
"ClientMsgId":get_time()},
"Scene":0
} self.session.post(url,data=(json.dumps(data,ensure_ascii=False)).encode('utf-8'))
self.get_new_synckey() # 发送消息之后也会更新synckey def get_msg(self):
'''接收消息'''
url = ' https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid={}&skey={}&pass_ticket={}'.format(self.wxsid,self.skey,self.pass_ticket)
data = {
"BaseRequest":{
"Uin":self.wxuin,
"Sid":self.wxsid,
"Skey":self.skey,
"DeviceID":get_DeviceID()},
"SyncKey":self.synckey_list,
"rr":-2123282759}
response = self.session.post(url,data=json.dumps(data))
response.encoding = 'utf-8'
result = response.json()
self.synckey_list = result['SyncKey'] # 接受消息的时候,响应里面有这个值,在更新synckey的时候,参数和响应里都有synckey
self.synckey = format_synckey(self.synckey_list['List'])
msglist = result['AddMsgList']
for msg in msglist:
if msg['ToUserName'] == self.user_list['XXXX']: # 这里面填你自己微信的昵称,不过加上之后不会受到群消息,大家可以试试不加这个判断
fromName = msg['FromUserName']
for k,v in self.user_list.items():
if v == fromName:
fromNickName = k
content = msg['Content']
print('来自{}的消息:{}'.format(fromNickName,content)) def main(self):
self.get_uuid()
self.qrcode()
self.get_redirect_uri()
self.get_require_data()
self.login()
self.get_userlist()
send_msg = Thread(target=self.send_msg)
recieve_msg = Thread(target=self.sync_check)
send_msg.start()
recieve_msg.start()
send_msg.join()
recieve_msg.join() if __name__ == '__main__':
wechat = WeChat()
wechat.main()

在这理,我只实现了文本消息,至于表情以及图片,还没有实现,大家可以完善一下。

用requests登录微信网页版,并接收发送消息的更多相关文章

  1. 微信网页版APP - 网页微信客户端电脑版体验

    微信网页版很早就出来了,解决了很多人上班不能玩手机的问题.微信电脑版-网页微信客户端,直接安装在桌面的微信网页版,免去了开浏览器的麻烦.双击就启动了,和其他的应用程序一样:运行过程中可以隐藏在桌面右下 ...

  2. CROW-5 WEB APP引擎商业计划书(HTML5方向)-微信网页版微信公众平台登录-水仙谷

    CROW-5 WEB APP引擎商业计划书(HTML5方向)-微信网页版微信公众平台登录-水仙谷 CROW-5 WEB APP引擎商业计划书(HTML5方向)

  3. HTML阻止iframe跳转页面并使用iframe在页面内嵌微信网页版

    昨天看到这篇文章[置顶]开源组件NanUI一周年 - 使用HTML/CSS/JS来构建.Net Winform应用程序界面 就想弄一个winform结合html5的一个小东西,突有兴致,想在里面嵌套一 ...

  4. js实现类似微信网页版在可编辑的div中粘贴内容时过滤剪贴板的内容,光标始终在粘贴内容后面,以及将光标定位到最后的方法

    过滤剪贴板内容以及定位可编辑div光标的方法: <!DOCTYPE html><html lang="en"><head>  <meta ...

  5. 基于.Net平台C#的微信网页版API

    git上有很多类似的项目,但大多都是python和js的,为了便于.Net windows平台的使用,我重构了一个.Net版本的,已整理开源 https://github.com/leestar54/ ...

  6. vue仿微信网页版|vue+web端聊天室|仿微信客户端vue版

    一.项目介绍 基于Vue2.5.6+Vuex+vue-cli+vue-router+vue-gemini-scrollbar+swiper+elementUI等技术混合架构开发的仿微信web端聊天室— ...

  7. JAVA微信公众号网页开发——将接收的消息转发到微信自带的客服系统

    如果公众号处于开发模式,普通微信用户向公众号发消息时,微信服务器会先将消息POST到开发者填写的url上,无法直接推送给微信自带的客服功能.如果需要把用户推送的普通消息推送到客服功能中,就需要进行代码 ...

  8. 开源微信Http协议Sdk【实现登录/获取好友列表/修改备注/发送消息】

    基于微信Http协议封装的一个Sdk,目前实现了以下功能:. 1:扫码登录(检测二维码扫描状态) 2:获取最近联系人.群组.所有联系人 3:修改好友备注 4:给好友发送消息 暂且这么多,也没多余的时间 ...

  9. 【开源】SpringBoot&Netty实现仿微信网页版项目更新

    阅读本文约“2.3分钟” 项目更新啦!V1.3.0 还记得那个聊天室的小项目吗? SpringBoot 加 Netty 实现聊天室 没错,这次已经完整进行了版本的替换,酥酥聊天室! 基于原项目的改动, ...

随机推荐

  1. opencv中彩色图转换成灰度图rgb2gray

    imread函数读入图像: 只需要将imread的第二个参数置为0即可. Mat imread(const string& filename, intflags=1 ); 第一个参数是载入图片 ...

  2. go语言获取字符串元素的个数

    1:获取字符串字节的个数,并按字节挨个输出 package main import ( "fmt" ) func main() { var str string = "a ...

  3. 那些可爱的 Linux 命令

    环境 root@15b883:~# uname -a ##需要是Ubuntu环境 Linux 15b883 --generic #- :: UTC x86_64 x86_64 x86_64 GNU/L ...

  4. 面试题:Spring的理解

    答案:D AOP为Aspect Oriented Programming的缩写, 意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. AOP是OOP的延续,是软件开发 ...

  5. JS HTML DOM---Document对象

    Document 对象 当浏览器载入 HTML 文档, 它就会成为 document 对象. document 对象是HTML文档的根节点与所有其他节点(元素节点,文本节点,属性节点, 注释节点). ...

  6. java.lang.ClassFormatError: Truncated class file

    之前跑的很好的程序,因为我本地IDE出了问题的原因,倒是编译的错误的class文件,结果点击的时候报这样的错误,后来重新clean了工程,重新打包解压启动,问题依旧. 解决办法: 把tomcat的wo ...

  7. hctf2016 fheap学习(FlappyPig队伍的解法)

    目录 漏洞原理 二次释放 如何在第二次释放前修改函数地址 fastbin的特性 修改函数指针流程 如何获得进程的加载基址 格式化字符串漏洞 确定printf函数在代码段中偏移 printf函数输出想要 ...

  8. js内置数据类型

    JS 中分为七种内置类型,七种内置类型又分为两大类型:基本类型和对象(Object). 基本类型有六种: number , string , boolean , null , undefined ,  ...

  9. 内置函数: zip 用法

    描述 zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表. 如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操作符 ...

  10. (4.13)SQL Server profile使用、数据库优化引擎顾问使用

    SQL Server profile使用技巧 介绍 经常会有人问profile工具该怎么使用?有没有方法获取性能差的sql的问题.自从转mysql我自己也差不多2年没有使用profile,忽然prof ...