python 实现微信自动回复(自动聊天)
微信自动回复其实主要就是登录,接收消息,回复消息三个功能,微信没有提供公开的API,但是可以分析网页版微信通信原理,通过模拟浏览器来实现需要的功能。
下面将给出微信网页版通信原理以及Python代码。
原文地址(本人):https://blog.csdn.net/a5878989/article/details/54974249
介绍
微信自动回复其实主要就是登录,接收消息,回复消息三个功能,微信没有提供公开的API,但是可以分析网页版微信通信原理,通过模拟浏览器来实现需要的功能。
下面将给出微信网页版通信原理以及Python代码。
分析
-获取uuid:
Param _ (13位时间戳)
Response window.QRLogin.code = 200; window.QRLogin.uuid = “4YyQFP2Daw==”;
-获取二维码:
Param 4YyQFP2Daw== 即上面的uuid
Response 二维码图片
-监听是否扫描二维码以及是否确认登录:
GET https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid=4YyQFP2Daw==
Param uuid 同上
Response
window.code=200;window.redirect_uri=”https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=ARxD7GSdBYtNHOxhK0BF0ek-@qrticket_0&uuid=4YyQFP2Daw==&lang=zh_CN&scan=1486743186″;
code:
408 无响应
201 扫描二维码但没有登录(此时响应数据中还包含用户头像图片base64编码的字符串,UserAvatar)
200 登录
redirect_uri 为接下来需要请求的地址
-获取后续访问所需要的key等
Param URL为上次返回的redirect_uri 参数已经带上了
Response
<error>
<ret>0</ret>
<message/>
<skey>@crypt_828c27e0_e98d62f6954235194f2b1252943f25ad</skey>
<wxsid>0zEvAdWKm9ZZgYVn</wxsid>
<wxuin>1564527827</wxuin>
<pass_ticket>OLxGHwqL%2BWNArxvXaqjDy06qzdrSojq6DJwiBF19sgw2CibZSJBv1WwOXAfKnLIg</pass_ticket>
<isgrayscale>1</isgrayscale>
< /error>
-初始化
POST https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=-485039295&lang=zh_CN&pass_ticket=OLxGHwqL%2BWNArxvXaqjDy06qzdrSojq6DJwiBF19sgw2CibZSJBv1WwOXAfKnLIg
Param r ( – + 9位随机数),pass_ticket,{“BaseRequest”: {“Uin”: “1564527827”, “Skey”: “@crypt_828c27e0_e98d62f6954235194f2b1252943f25ad”, “DeviceID”: “e924318232435460”, “Sid”: “0zEvAdWKm9ZZgYVn”}} 第三个参数其中为json数据,DeviceID为(e + 15位随机数)
Response 返回json,包含用户自己的信息,最近联系人,订阅的公众号消息等等;这里只需要关注 UserName=@821c154488cdddbfb04141aa8f681174305d21d67a24cfd6eca3e77a152e52ff 每位用户都有一个UserName,但是每次登陆UserName都是重新分配的,SyncKey 为一组key ,后面接收消息需要将其作为参数,同时每次接收接收消息时,也会返回一组SyncKey作为在下一次请求的参数,以此类推
-状态检查
这里会建立一个长连接,每次连接大约20秒左右,若新消息,手机端发出退出网页登录指令,或者状态异常会返回特定的状态码
GET https://wx.qq.com/cgi-bin/mmwebwx-bin/synccheck?r=1486743215000&skey=@crypt_828c27e0_e98d62f6954235194f2b1252943f25ad&sid=0zEvAdWKm9Z
ZgYVn&uin=1564527827&deviceid=e891796429.95749&synckey=1_660530221%7C2_660530488%7C3_660530485%7C1000_1486721341&_=1486740215000
Param
r(时间戳),skey,sid,uin,deviceid,synckey(将SyncKey中的多组key 以 key1_value1|key2_value2 的形式拼接成字符串如:3_660530485|1000_1486721341),_ (时间戳)
Response
window.synccheck={retcode:”0″,selector:”2″}
retcode=0 正常 ,1101 退出登录,1102 会话异常 , selector= 0 无变化 2or6 有消息
-接收消息
若状态检查到有新消息,则请求消息
POST https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=0zEvAdWKm9ZZgYVn&skey=@crypt_828c27e0_e98d62f6954235194f2b1252943f25ad&lang=zh_CN&pass_ticket=OLxGHwqL%2BWNArxvXaqjDy06qzdrSojq6DJwiBF19sgw2CibZSJBv1WwOXAfKnLIg
Param
sid,skey,pass_ticket 以及 json数据 {“SyncKey”: {“Count”: 4, “List”: [{“Key”: 1, “Val”: 660530221}, {“Key”: 2, “Val”: 660530488}, {“Key”: 3, “Val”: 660530485}, {“Key”: 1000, “Val”: 1486721341}]}, “BaseRequest”: {“Sid”: “0zEvAdWKm9ZZgYVn”, “Skey”: “@crypt_828c27e0_e98d62f6954235194f2b1252943f25ad”, “DeviceID”: “e141257009.76972”, “Uin”: “1564527827”}, “rr”: “-888098293”} 其中rr (- + 9位随机数)
Response
json数据包含消息的所有信息,其中关注 FromUserName=@821c154488cdddbfb04141aa8f681174305d21d67a24cfd6eca3e77a152e52ff 消息发送者以及 Content 消息内容
-发送消息
POST https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?lang=zh_CN&pass_ticket=0%2BoUqOWdYEen6oDVFEIv5ncIIaJcWs1LeSi69C8tUTgcp36azGAl6a8uT02PiaHu
Param pass_ticket, json数据{“Msg”: {“FromUserName”: “@9e718026650771acd6d759922e000fafceaa1a5fda83aea7b3b70bc1bd6c3774”, “LocalID”: “14867488199507670”, “ClientMsgId”: “14867488199507670”, “ToUserName”: “@9e718026650771acd6d759922e000fafceaa1a5fda83aea7b3b70bc1bd6c3774”, “Content”: “消息内容”, “Type”: “1”}, “BaseRequest”: {“Sid”: “5Qn7rswOtPRHFw92”, “Skey”: “@crypt_828c27e0_ad386b3d4d68a282eda03d7d5b2d3104”, “DeviceID”: “e397471984070243”, “Uin”: “1564527827”}, “Scene”: “0”} 其中LocalID,ClientMsgId 为13位时间戳加上5位随机数
Response 返回响应的状态码,发送成功会返回 LocalID 和 ClientMsgID
以上就是我们需要的知道的,当然其他比如读取所有联系人等都是大同小异,这里就不多赘述了。
到这里还有关键的一步,那就是如何根据收到的消息自动回复,当然是接入其他可以聊天的程序了,这里为了方便我使用的simsimi聊天机器人的外链,它不需要登录等操作,连request header 都不用伪造^0^,直接将接受的消息post过去,将返回的消息作为微信回复消息;当然也可以接入更智能的机器人。
-获取自动回复的消息
POST http://www.niurenqushi.com/api/simsimi/
Param txt (发送的消息)
Response {“code”:100000,”text”:”消息”}
代码
运行以下代码,会自动弹出二维码图片,手机扫码登录之后开始运行,手机端发送退出登录指令时结束。
重点在于流程和思路,代码也比较糙,注释也就不加了,多指教^0dfd^
# -*- coding:utf-8 -*- #author:fengw import urllib,urllib2,cStringIO,re,sys,os,cookielib,ssl,requests,time,json,random,threading,warnings from PIL import Image from matplotlib import pyplot as plt import xml.etree.cElementTree as et reload(sys) sys.setdefaultencoding(\'utf-8\') warnings.filterwarnings("ignore") def get_device_id(): return \'e\'+str(random.random()*10000000000)[0:10]+str(random.random()*100000)[0:5] def qrcode_img(): response=urllib2.urlopen(QRCODE_KEY_URL).read() p=re.compile(r\'(\d+(\.\d+)?)\') code=p.findall(response)[0][0] if code ==\'200\': p=re.compile(r\'\"(.*)\"\') qrcode_key=p.findall(response)[0] qrcode_img_url=QRCODE_IMG_BASE_URL+qrcode_key global CHECK_LOGIN_STATUS_BASE_URL CHECK_LOGIN_STATUS_BASE_URL=CHECK_LOGIN_STATUS_BASE_URL+qrcode_key qrcode_img=Image.open(cStringIO.StringIO(urllib2.urlopen(qrcode_img_url).read())) plt.ion() plt.figure() plt.imshow(qrcode_img) plt.figure() plt.close(2) else : print \'sorry,request qrcode failed...\' time.sleep(2) os._exit(0) def listen_login(): run=True times=0 msg=\'please scan the qrcode\' while run: times+=1 print msg response=urllib2.urlopen(CHECK_LOGIN_STATUS_BASE_URL).read() p=re.compile(r\'(\d+(\.\d+)?)\') code=p.findall(response)[0][0] if code==\'201\': msg= \'please login...\' plt.close() if code==\'200\': run=False plt.close() print \'login sucess,running....\' p=re.compile(r\'\"(.*)\"\') redirect_url=p.findall(response)[0] response=conn.get(url=redirect_url,allow_redirects=False,verify=False) msg=response.text global ret,message,skey,wxsid,wxuin,pass_ticket,isgrayscale xml=et.fromstring(msg) ret=xml[0].text message=xml[1].text skey=xml[2].text wxsid=xml[3].text wxuin=xml[4].text pass_ticket=xml[5].text isgrayscale=xml[6].text if times==20: run=False def update_synckey(msg): global synckey,syncheck_key synckey=str(msg[\'SyncKey\']).replace("u\'","\'") for k_v in msg[\'SyncKey\'][\'List\']: syncheck_key+=\'|\'+str(k_v[\'Key\'])+\'_\'+str(k_v[\'Val\']) syncheck_key=syncheck_key[1:] def wx_init(): url=\'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=-485039295&lang=zh_CN&pass_ticket=\'+pass_ticket data={\'BaseRequest\':{\'DeviceID\':\'%s\'%get_device_id(),\'Sid\':\'%s\'%wxsid,\'Skey\':\'%s\'%skey,\'Uin\':\'%s\'%wxuin}} res=conn.post(url=url,headers=headers,data=json.dumps(data),verify=False) response=res.text msg=json.loads(response) global user user=msg[\'User\'][\'UserName\'] update_synckey(msg) def get_contact_list(): base_url=\'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?lang=zh_CN&seq=0\' base_url+=\'&pass_ticket=\'+pass_ticket+\'&r=\'+str(int(time.time())*1000)+\'&skey=\'+skey response=urllib2.urlopen(base_url).read() data=json.loads(response) f=open(r\'d:/linklist.txt\',\'w\') for friend in data[\'MemberList\']: msg=friend[\'NickName\']+","+friend[\'RemarkName\']+"\n" f.write(msg.encode(\'utf-8\')) f.close() def get_auto_reply(send_msg): url=\'http://www.niurenqushi.com/api/simsimi/\' data={\'txt\':\'%s\'%send_msg} res=conn.post(url=url,data=data) res.encoding=\'utf-8\' return json.loads(res.text,\'\')[\'text\'] def reply_msg(content,touser): url=\'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?lang=zh_CN&pass_ticket=\'+pass_ticket #touser=\'filehelper\' ClientMsgId=str(int(time.time()))+str(random.random()*10000000)[0:7] print \'recive msg :\',content sendmsg=get_auto_reply(content) data={\'BaseRequest\':{\'Uin\':\'%s\'%wxuin,\'Sid\':\'%s\'%wxsid,\'Skey\':\'%s\'%skey,\'DeviceID\':\'%s\'%get_device_id()},\'Msg\':{\'ClientMsgId\':\'%s\'%ClientMsgId,\'Content\':\'%s\'%sendmsg.encode(\'utf-8\'),\'FromUserName\':\'%s\'%user,\'LocalID\':ClientMsgId,\'ToUserName\':\'%s\'%touser,\'Type\':\'1\'},\'Scene\':\'0\'} data=json.dumps(data,ensure_ascii=False) res=conn.post(url=url,headers=headers,data=data.encode(\'utf-8\'),verify=False) print \'reply:\',sendmsg def recive_msg(): base_url=\'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync\' base_url+=\'?sid=\'+wxsid+\'&skey=\'+skey+\'&lang=\'+\'zh_CN\'+\'&pass_ticket=\'+pass_ticket while True: try: rr=\'-\'+str(random.random()*1000000000)[0:9] data={\'BaseRequest\':{\'Uin\':\'%s\'%wxuin,\'Sid\':\'%s\'%wxsid,\'Skey\':\'%s\'%skey,\'DeviceID\':\'%s\'%get_device_id()},\'SyncKey\':eval(synckey),\'rr\':\'%s\'%rr} res=conn.post(url=base_url,headers=headers,data=json.dumps(data),verify=False) res.encoding=\'utf-8\' response=res.text if response==None: continue data=json.loads(response) update_synckey(data) for msg in data[\'AddMsgList\']: content=msg[\'Content\'] fromuser=msg[\'FromUserName\'] if fromuser==user: continue if content[0:4]==\'<\': continue #print \'recived msg:\',content.decode(\'unicode_escape\'),\'from user :\',fromuser threading.Thread(target=reply_msg,args=(content,fromuser)).start() time.sleep(2) except Exception as e : pass def sync_check(): listen=True base_url=\'https://webpush.wx.qq.com/cgi-bin/mmwebwx-bin/synccheck\' base_url+=\'?r=\'+str(int(time.time())*1000)+\'&skey=\'+skey+\'&sid=\'+wxsid+\'&uin=\'+wxuin+\'&deviceid=\'+get_device_id()+\'&synckey=\'+syncheck_key+\'&_=\'+str(int(time.time())*1000-3000000) request = urllib2.Request(url=base_url, headers=headers) while listen: try: res=conn.get(url=base_url,headers=headers,verify=False) response=res.text p=re.compile(r\'(\d+(\.\d+)?)\') retcode=p.findall(response)[0][0] if retcode==\'1101\' or retcode==\'1102\': print \'login out ...\' listen=False os._exit(0) time.sleep(2) except Exception: pass if __name__ == \'__main__\': ssl._create_default_https_context = ssl._create_unverified_context QRCODE_KEY_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&_=\'+str(int(time.time())*1000) QRCODE_IMG_BASE_URL=\'https://login.weixin.qq.com/qrcode/\' CHECK_LOGIN_STATUS_BASE_URL=\'https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid=\' ret,message,skey,wxsid,wxuin,pass_ticket,isgrayscale=\'\',\'\',\'\',\'\',\'\',\'\',\'\' synckey,user=\'\',\'\' syncheck_key=\'\' cookie=cookielib.CookieJar() handler=urllib2.HTTPCookieProcessor(cookie) debug_h=urllib2.HTTPSHandler(debuglevel=0) opener=urllib2.build_opener(handler,debug_h) urllib2.install_opener(opener) conn=requests.session() headers = { \'Host\': \'wx.qq.com\', \'Connection\': \'keep-alive\', \'Accept\': \'application/json, text/plain, */*\', \'Origin\': \'https://wx.qq.com\', \'User-Agent\': \'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0\', \'Content-Type\': \'application/json;\', \'Accept-Encoding\': \'gzip, deflate, br\', \'Accept-Language\': \'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3\' } #获取微信二维码并显示 qrcode_img() #监听用户扫描二维码和登录动作 listen_login() #微信初始化 wx_init() #开启子线程监听登录状态 check_status_task=threading.Thread(target=sync_check) check_status_task.start() #get_contact_list() #主线程监听消息 recive_msg()