作者:xiaoyu
微信公众号:Python数据科学
知乎:Python数据分析师


不知何时,微信已经成为我们不可缺少的一部分了,我们的社交圈、关注的新闻或是公众号、还有个人信息或是隐私都被绑定在了一起。既然它这么重要,如果我们可以利用爬虫模拟登录,是不是就意味着我们可以获取这些信息,甚至可以根据需要来对它们进行有效的查看和管理。是的,没错,这完全可以。本篇博主将会给大家分享一下如何模拟登录网页版的微信,并展示模拟登录后获取的好友列表信息

微信模拟登录的过程比较复杂,当然不管怎么样方法都是万变不离其宗,我们还是使用fiddler抓包工具来模拟登录的过程。
好了,下面让我们一步一步的详细讲解一下如何实现的这个复杂的过程。

用fiddler模拟登录的请求

首先,我们在浏览器上打开微信网页版(fiddler已经在这之前打开了),然后我们会看到一个二维码的界面。

然后我们使用手机微信扫描并确认,这时候网页版的微信就登陆了。

好,我们去看看fiddler都给我们抓取了什么信息包。由于过程中发出的请求有点多,这里把抓包按操作进行分解并逐一分析。

1.打开微信网页

这一步骤的抓包是这样的,发现其中login.wx.qq.com的两个链接是我们需要的。

于是点开详细分析一下。

第一个链接如下,是一个get请求,可以看到uri中携带了一些参数appid、redirect_uri、fun、lang、_

GET /jslogin?appid=wx782c26e4  c19acffb&redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&fun=new&lang=zh_CN&_=1520350213674 HTTP/1.1

经过多次抓取发现appid、redirect_uri、fun、lang参数都是固定的,而_是一串变化的数字,我们在之前模拟京东商城的文章提过,它其实是一个时间戳,如果不清楚可以回顾一下Python爬虫实战之(四)| 模拟登录京东商城

知道这些参数,模拟get发送出去就可以了。那么我们为什么要模拟这一步呢?

是因为访问这个链接会有如下的响应,而其中有我们后续需要的重要信息uuid(后面步骤会提到)。

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

2.模拟获取二维码

微信网页提供的登录方式是扫码,我们模拟也无法避开,因此也要进行扫码验证。回到浏览器,使用开发者工具可以轻松找到二维码的链接。


https://login.weixin.qq.com/q...

我们发现最后的字符串是变化的。等等,它和uuid一模一样的。没错,它就是uuid,用来保证二维码的唯一性。

因此,我们将上面提取的uuid拼接到后面就可以得到二维码图片了,然后进行扫码确认操作。

3.识别登录状态

为了识别扫码是否成功,这个步骤我们需要用到上面提到的第二个链接。

GET /cgi-bin/mmwebwx-bin/login?loginicon=true&uuid=Idf_QdW8OQ==&tip=1&r=68288473&_=1520050213675 HTTP/1.1

这个链接也是个get请求,同样携带了一些参数。

实际上在抓包过程发现只要我们不扫描二维码,这个链接就会一直重复发送直到二维码被扫描或者超时。

那么我们如何判断二维码是否被扫描或者已经登陆了呢?

还是通过响应的数据来进行判断的。经分析发现如果二维码一直没被扫,那么响应是这样的:

window.code=408;

但是如果二维码被扫描了,响应是这样的:

window.code=201;window.userAvatar = .....
window.code=200;
window.redirect_uri="https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=AaL_Xd5muLPKNVY_Hzt_uoBs@qrticket_0&uuid=gbJqPdkNSQ==&lang=zh_CN&scan=1520353803";

code=201说明二维码被扫描成功了。
code=200说明是登录成功了。

4.登录

扫描了二维码之后,fiddler上会多出几个新的请求。

你可能发现了,上一步骤中code=200后面有个重定向的uri,这个uri就是此步骤中跳转的登录链接。

GET https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=AaL_Xd5muLPKNVY_Hzt_udBs@qrticket_0&uuid=gbJqPdfNSQ==&lang=zh_CN&scan=1520353803&fun=new&version=v2 HTTP/1.1

通过上一步骤识别登录成功的响应我们可以得到响应里面的所有参数。没错,这些参数正好可以用在正式登录(即跳转链接)的请求中。于是我们利用这些参数再进行一次get请求。携带参数如下:

当然,这个登录请求同样也会返回一些响应代码,响应代码如下:

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

又是一堆参数,简直没完没了啊。别着急,我们已经接近成功了。获取这个响应我们一样需要将其中的参数全部提取出来供下一请求使用。

5.初始化同步

好了,终于到了最后一步了,就是微信的初始化和同步的请求了,初始化信息链接如下:

POST https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=64629109&pass_ticket=4dU5IS9EqtXt5cIV2Gni1tKG7m2V56PXk5XI%252BdjdrIk%253D HTTP/1.1

contact联系链接如下:

GET https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?pass_ticket=4dU5IS9EqtXt5cIV2Gni1tKG7m2V56PXk5XI%252BdjdrIk%253D&r=1520353806102&seq=0&skey=@crypt_a82dd73a_3885c878ae2f4590f7b2b5ee949dd1bd HTTP/1.1

uri中参数pass_ticket,skey在上一步的响应中已获取,直接发送请求即可完成。从这两个链接的响应中,我们就可以得到一些真实有用的信息了。

还有一个同步的请求链接,所需参数可以从上面两个链接响应中提取。但是至此我们通过上面两个链接已经可以获取我们想要的信息,因此可以不必请求这个同步链接。

GEThttps://webpush.wx.qq.com/cgi-bin/mmwebwx-bin/synccheck?r=1520353806125&skey=%40crypt_a82dd73a_3885c878ae2f4590f7b2b5ee949dd1bd&sid=O2Se5s2LJzPebME2&uin=254891255&deviceid=e289448639092966&synckey=1_694936977%7C2_694936979%7C3_694936982%7C1000_1520324882&_=1520353793581 HTTP/1.1

基本的登录过程就是这样,有点复杂,博主总结了个流程图供参考。

代码实现

请求模拟使用requests模块完成,解析使用re。这里需要注意一下,如果运行一直报ssl的错,可以在request请求里面加上了verify=False跳过证书认证来解决。

1.初始化参数

def __init__(self):self.session = requests.session()self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 5.1; rv:33.0) Gecko/20100101 Firefox/33.0'}self.QRImgPath = os.path.split(os.path.realpath(__file__))[0] + os.sep + 'webWeixinQr.jpg'self.uuid = ''self.tip = 0self.base_uri = ''self.redirect_uri = ''self.skey = ''self.wxsid = ''self.wxuin = ''self.pass_ticket = ''self.deviceId = 'e000000000000000'self.BaseRequest = {}self.ContactList = []self.My = []self.SyncKey = ''

定义一个类,初始化实例的所有请求参数,定义二维码的路径。

2.请求uuid

def getUUID(self):url = 'https://login.weixin.qq.com/jslogin'params = {'appid': 'wx782c26e4c19acffb','redirect_uri': 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage','fun': 'new','lang': 'zh_CN','_': int(time.time() * 1000),  # 时间戳}response = self.session.get(url, params=params)target = response.content.decode('utf-8')pattern = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)"'ob = re.search(pattern, target)  # 正则提取uuidcode = ob.group(1)self.uuid = ob.group(2)if code == '200':  # 判断请求是否成功return Truereturn False

使用正则对相应进行提取获取uuid,通过code判断请求是否成功,响应如下:

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

3.模拟获取二维码

def showQRImage(self):url = 'https://login.weixin.qq.com/qrcode/' + self.uuidresponse = self.session.get(url)self.tip = 1with open(self.QRImgPath, 'wb') as f:f.write(response.content)f.close()# 打开二维码if sys.platform.find('darwin') >= 0:subprocess.call(['open', self.QRImgPath])  # 苹果系统elif sys.platform.find('linux') >= 0:subprocess.call(['xdg-open', self.QRImgPath])  # linux系统else:os.startfile(self.QRImgPath)  # windows系统print('请使用微信扫描二维码登录')

使用uuid请求二维码图片,并根据操作系统自动打开。

4.识别登录状态

def checkLogin(self):url = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s' % (self.tip, self.uuid, int(time.time() * 1000))response = self.session.get(url)target = response.content.decode('utf-8')pattern = r'window.code=(\d+);'ob = re.search(pattern, target)code = ob.group(1)if code == '201':  # 已扫描print('成功扫描,请在手机上点击确认登录')self.tip = 0elif code == '200':  # 已登录print('正在登录中...')regx = r'window.redirect_uri="(\S+?)";'ob = re.search(regx, target)self.redirect_uri = ob.group(1) + '&fun=new'self.base_uri = self.redirect_uri[:self.redirect_uri.rfind('/')]elif code == '408':  # 超时passreturn code

响应如下:

window.code=200;
window.redirect_uri="https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=AaL_Xd5muLPKNVY_Hzt_uoBs@qrticket_0&

根据响应中的code代码识别登录状态。
408:超时
201:已扫描
200:已登录

5.登录

def login(self):response = self.session.get(self.redirect_uri, verify=False)data = response.content.decode('utf-8')doc = xml.dom.minidom.parseString(data)root = doc.documentElement# 提取响应中的参数for node in root.childNodes:if node.nodeName == 'skey':self.skey = node.childNodes[0].dataelif node.nodeName == 'wxsid':self.wxsid = node.childNodes[0].dataelif node.nodeName == 'wxuin':self.wxuin = node.childNodes[0].dataelif node.nodeName == 'pass_ticket':self.pass_ticket = node.childNodes[0].dataif not all((self.skey, self.wxsid, self.wxuin, self.pass_ticket)):return Falseself.BaseRequest = {'Uin': int(self.wxuin),'Sid': self.wxsid,'Skey': self.skey,'DeviceID': self.deviceId,}return True

请求跳转的登录链接,提取响应代码参数,响应如下:

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

6.初始化获取信息

def webwxinit(self):url = self.base_uri + \'/webwxinit?pass_ticket=%s&skey=%s&r=%s' % (self.pass_ticket, self.skey, int(time.time() * 1000))params = {'BaseRequest': self.BaseRequest}h = self.headersh['ContentType'] = 'application/json; charset=UTF-8'response = self.session.post(url, data=json.dumps(params), headers=h, verify=False)data = response.content.decode('utf-8')print(data)dic = json.loads(data)self.ContactList = dic['ContactList']self.My = dic['User']SyncKeyList = []for item in dic['SyncKey']['List']:SyncKeyList.append('%s_%s' % (item['Key'], item['Val']))self.SyncKey = '|'.join(SyncKeyList)ErrMsg = dic['BaseResponse']['ErrMsg']Ret = dic['BaseResponse']['Ret']if Ret != 0:return Falsereturn True

请求初始化的链接,获取初始化响应数据。

def webwxgetcontact(self):url = self.base_uri + \'/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' % (self.pass_ticket, self.skey, int(time.time()))h = self.headersh['ContentType'] = 'application/json; charset=UTF-8'response = self.session.get(url, headers=h, verify=False)data = response.content.decode('utf-8')# print(data)dic = json.loads(data)MemberList = dic['MemberList']# 倒序遍历,不然删除的时候出问题..SpecialUsers = ["newsapp", "fmessage", "filehelper", "weibo", "qqmail", "tmessage", "qmessage", "qqsync","floatbottle", "lbsapp", "shakeapp", "medianote", "qqfriend", "readerapp", "blogapp","facebookapp", "masssendapp","meishiapp", "feedsapp", "voip", "blogappweixin", "weixin", "brandsessionholder","weixinreminder", "wxid_novlwrv3lqwv11", "gh_22b87fa7cb3c", "officialaccounts","notification_messages", "wxitil", "userexperience_alarm"]for i in range(len(MemberList) - 1, -1, -1):Member = MemberList[i]if Member['VerifyFlag'] & 8 != 0:  # 公众号/服务号MemberList.remove(Member)elif Member['UserName'] in SpecialUsers:  # 特殊账号MemberList.remove(Member)elif Member['UserName'].find('@@') != -1:  # 群聊MemberList.remove(Member)elif Member['UserName'] == self.My['UserName']:  # 自己MemberList.remove(Member)return MemberList

请求contact的链接,获取联系人、公众号、群聊以及个人信息。响应代码为json格式,如下:

{
"BaseResponse": {
"Ret": 0,
"ErrMsg": ""
}
,
"Count": 11,
"ContactList": [{
"Uin": 0,
"UserName": "filehelper",
"NickName": "文件传输助手",
"HeadImgUrl": "/cgi-bin/mmwebwx-bin/webwxgeticon?seq=621637626&username=filehelper&skey=@crypt_a82dd73a_7e8e1054c011e8d71d0b542f39c7db85",
"ContactFlag": 3,
"MemberCount": 0,
"MemberList": [],
"RemarkName": "",
"HideInputBarFlag": 0,
"Sex": 0,
"Signature": "",
"VerifyFlag": 0,
"OwnerUin": 0,
"PYInitial": "WJCSZS",
"PYQuanPin": "wenjianchuanshuzhushou",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"StarFriend": 0,
"AppAccountFlag": 0,
"Statues": 0,
"AttrStatus": 0,
"Province": "",
"City": "",
"Alias": "",
"SnsFlag": 0,
"UniFriend": 0,
"DisplayName": "",
"ChatRoomId": 0,
"KeyWord": "fil",
"EncryChatRoomId": "",
"IsOwner": 0
}
,{...}
...

根据响应中字段信息做信息操作,这里是获取好友列表,所以将其它字段如公众号、群聊、自己都去掉了,只保留好友信息。

7.主函数运行

def main(self):if not self.getUUID():print('获取uuid失败')returnself.showQRImage()time.sleep(1)while self.checkLogin() != '200':passos.remove(self.QRImgPath)if not self.login():print('登录失败')return# 登录完成, 下面查询好友if not self.webwxinit():print('初始化失败')returnMemberList = self.webwxgetcontact()print('通讯录共%s位好友' % len(MemberList))for x in MemberList:sex = '未知' if x['Sex'] == 0 else '男' if x['Sex'] == 1 else '女'print('昵称:%s, 性别:%s, 备注:%s, 签名:%s' % (x['NickName'], sex, x['RemarkName'], x['Signature']))

模拟登录结果

好友列表如下:

当然,好友列表只是个例子,我们也可以对其它信息进行查看和管理或者数据分析。

总结

本篇与大家分享了网页版微信的模拟登录过程。尽管过程中请求多有点复杂,但是只要我们仔细分析还是可以一步一步实现的,希望对大家有帮助,代码已上传到github:https://github.com/xiaoyusmd/...

完毕。


关注微信公众号Python数据科学,获取 120G 人工智能 学习资料。

Python爬虫实战之(五)| 模拟登录wechat 1相关推荐

  1. Python爬虫实战(5):模拟登录淘宝并获取所有订单(1)

    经过多次尝试,模拟登录淘宝终于成功了,实在是不容易,淘宝的登录加密和验证太复杂了,煞费苦心,在此写出来和大家一起分享,希望大家支持. 本篇内容 1. python模拟登录淘宝网页 2. 获取登录用户的 ...

  2. 爬虫实战篇(模拟登录)

    爬虫实战篇(模拟登录) 阅读目录 (1).登录实质 (2).什么是模拟登陆 (3).实现方式 (4).实例讲解(模拟登录去哪儿网)–这里我们用第二种实现方式 (1).登录实质 互联网上的部分网站需要登 ...

  3. python爬虫token_Python入门:模拟登录(二)或注册之requests处理带token请求

    转自http://blog.csdn.net/foryouslgme/article/details/51822209 首先说一下使用Python模拟登录或注册时,对于带token的页面怎么登录注册模 ...

  4. python爬虫学习笔记2模拟登录与数据库

    前言 为了加入学校里面一个技术小组,我接受了写一个爬取学校网站通知公告的任务.这个任务比以前写的爬虫更难的地方在于,需要模拟登录才能获得页面,以及将得到的数据存入数据库. 本文按照日期来记录我完成任务 ...

  5. python爬取今日头条热点新闻事件,Python爬虫实战入门五:获取JS动态内容—爬取今日头条...

    之前我们爬取的网页,多是HTML静态生成的内容,直接从HTML源码中就能找到看到的数据和内容,然而并不是所有的网页都是这样的. 有一些网站的内容由前端的JS动态生成,由于呈现在网页上的内容是由JS生成 ...

  6. Python爬虫实战之(五)| 模拟登录wechat

    作者:xiaoyu 微信公众号:Python数据科学 知乎:Python数据分析师 不知何时,微信已经成为我们不可缺少的一部分了,我们的社交圈.关注的新闻或是公众号.还有个人信息或是隐私都被绑定在了一 ...

  7. Python爬虫实战(5):模拟登录淘宝并获取所有订单

    Python爬虫入门(1):综述 Python爬虫入门(2):爬虫基础了解 Python爬虫入门(3):Urllib库的基本使用 Python爬虫入门(4):Urllib库的高级用法 Python爬虫 ...

  8. Python 爬虫实战,模拟登陆爬取数据

    Python 爬虫实战,模拟登陆爬取数据 从0记录爬取某网站上的资源连接: 模拟登陆 爬取数据 保存到本地 结果演示: 源网站展示: 爬到的本地文件展示: 环境准备: python环境安装 略 安装r ...

  9. Python爬虫实战——签名软件设计(一)python POST模拟网页按钮点击

    Python爬虫实战(windows) 关于本文: 上一篇文章介绍了如何使用python爬虫进行网页图片等素材的抓取.我们以爬虫抓取作为基础,结合GUI设计,再通过自定义方法,就可以实现一些小软件的设 ...

最新文章

  1. 坚持写博客给我带来了什么
  2. TextSwitcher实现文本自动垂直滚动
  3. jenkins无法安装插件问题
  4. hinkphp项目部署到Linux服务器上报错“模板不存在”如何解决
  5. cisco服务器维修,面向终端的AMP控制台的思科维护的排除列表更改
  6. 面试题:根据Unix时间戳计算时间
  7. erp 维护费 要交吗_ERP系统维护费
  8. 【C++基础学习】引起类模板被实例化情形总结
  9. 大幅广告显示隐藏效果
  10. 6599元!索尼Xperia 5 III国行版今日首销:媲美专业微单相机
  11. 手记 《半年工作经验今日头条和美团面试题面经分享》
  12. 新任项目经理的五项必修课 (转)
  13. java 项目开发流程_详解JAVA开发之JAVA项目开发的基本流程
  14. 拆解日本松下的老古董收录机,感受50年前的电路设计,满满的历史感
  15. 权限设计-系统登录用户权限设计
  16. The7主题安装教程Wordpress
  17. java 逃逸分析_JVM之逃逸分析
  18. 轻松解决mscorsvw.exe进程CPU占用高的问题
  19. DWG文件如何转换为PDF黑白文件
  20. Battery_Charing_Discharing:基于MATLAB/Simulink的锂电池充、放电控制,充电控制和放电控制均采用电压电流双闭环控制

热门文章

  1. java mouselistener,Java MouseListener接口
  2. selenium切换窗口 java_WebDriver(Selenium2) 根据新窗口title切换窗口
  3. python查找最长公共前缀_Python实现查找字符串数组最长公共前缀示例
  4. 适配器(Adaptor)模式
  5. servlet获取相对路径 绝对路径
  6. [BZOJ4815][CQOI2017]小Q的表格 数论+分块
  7. 小众的分布式版本管理工具Code Co-op
  8. c#基于socket的UDP服务器和客户端实例
  9. mfc编程消息机制中消息汇总
  10. ios开发之使用多文件上传的简单封装最原始的