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

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

1. 用fiddler模拟登录的请求

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

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

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

1.打开微信网页

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

于是点开详细分析一下。

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

GET /jslogin?appid=wx782c26e4c19acffb&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/qrcode/AdgAWNry-w==

我们发现最后的字符串是变化的。等等,它和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%253DHTTP/1.1

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

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

2. 代码实现

请求模拟使用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 = 0
    self.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)  # 正则提取uuid
    code = ob.group(1)
    self.uuid = ob.group(2)
    if code == '200':  # 判断请求是否成功
        return True
    return False

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

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

3.模拟获取二维码

def showQRImage(self):
    url = 'https://login.weixin.qq.com/qrcode/' + self.uuid
    response = self.session.get(url)
    self.tip = 1
    with 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 = 0
    elif 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':  # 超时
        pass
    return 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].data
        elif node.nodeName == 'wxsid':
            self.wxsid = node.childNodes[0].data
        elif node.nodeName == 'wxuin':
            self.wxuin = node.childNodes[0].data
        elif node.nodeName == 'pass_ticket':
            self.pass_ticket = node.childNodes[0].data
    if not all((self.skey, self.wxsid, self.wxuin, self.pass_ticket)):
        return False
    self.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.headers
    h['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 False
    return 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.headers
    h['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失败')
        return
    self.showQRImage()
    time.sleep(1)
    while self.checkLogin() != '200':
        pass
    os.remove(self.QRImgPath)
    if not self.login():
        print('登录失败')
        return
    # 登录完成, 下面查询好友
    if not self.webwxinit():
        print('初始化失败')
        return
    MemberList = 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']))

3. 模拟登录结果

好友列表如下:

这里只截取一部分好友信息,大家看看签名有没有上榜的。当然,好友列表只是个例子,我们也可以对其它信息进行查看和管理或者数据分析

http://www.aibbt.com/a/18044.html

4. 总结

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

完毕。

 

Python爬虫之模拟登录微信wechat的更多相关文章

  1. Python爬虫-百度模拟登录(二)

    上一篇-Python爬虫-百度模拟登录(一) 接上一篇的继续 参数 codestring codestring jxG9506c1811b44e2fd0220153643013f7e6b1898075 ...

  2. Python爬虫-百度模拟登录(一)

    千呼万唤屎出来呀,百度模拟登录终于要呈现在大家眼前了,最近比较忙,晚上又得早点休息,这篇文章写了好几天才完成.这个成功以后,我打算试试百度网盘的其他接口实现.看看能不能把服务器文件上传到网盘,好歹也有 ...

  3. Python 爬虫之模拟登录

    最近应朋友要求,帮忙爬取了小红书创作平台的数据,感觉整个过程很有意思,因此记录一下.在这之前自己没怎么爬过需要账户登录的网站数据,所以刚开始去看小红书的登录认证时一头雾水,等到一步步走下来,最终成功, ...

  4. curl 模拟登录微信公众平台带验证码

    这段时间一直写个项目, 从切图到前端到后台都要搞定,真tm累. 今天下午手残,不停用错误的密码去模拟登录微信公众平台,结果后来出现验证码,瞬间悲剧(菜鸟从来没搞过带验证码的). 研究了一下,发现其实很 ...

  5. 测试开发Python培训:模拟登录新浪微博-技术篇

    测试开发Python培训:模拟登录新浪微博-技术篇   一般一个初学者项目的起点就是登陆功能的自动化,而面临的项目不同实现的技术难度是不一样的,poptest在做测试开发培训中更加关注技术难点,掌握技 ...

  6. Python爬虫常用之登录(二) 浏览器模拟登录

    浏览器模拟登录的主要技术点在于: 1.如何使用python的浏览器操作工具selenium 2.简单看一下网页,找到帐号密码对应的框框,要知道python开启的浏览器如何定位到这些 一.使用selen ...

  7. Python post请求模拟登录淘宝并爬取商品列表

    一.前言 大概是一个月前就开始做淘宝的爬虫了,从最开始的用selenium用户配置到selenium模拟登录,再到这次的post请求模拟登录.一共是三篇博客,记录了我爬取淘宝网的经历.期间也有朋友向我 ...

  8. Java爬虫——人人网模拟登录

    人人网登录地址:http://www.renren.com/ 此处登录没有考虑验证码验证码. 首先对登录方法进行分析 有两种方法. 一)在Elements中分析源码 发现登录点击后的事件是http:/ ...

  9. python爬虫scrapy之登录知乎

    下面我们看看用scrapy模拟登录的基本写法: 注意:我们经常调试代码的时候基本都用chrome浏览器,但是我就因为用了谷歌浏览器(它总是登录的时候不提示我用验证码,误导我以为登录时不需要验证码,其实 ...

随机推荐

  1. JS中的内置对象简介与简单的属性方法

    JS中的数组: 1.数组的概念: 数组是在内存中连续存储的多个有序元素的结构,元素的顺序称为下标,通过下标查找对应元素 2.数组的声明: ①通过字面量声明var arr1 = [,,,,] JS中同一 ...

  2. 2018/1/27 Zookeeper实现分布式锁

    public class DistributedClient { // 超时时间 private static final int SESSION_TIMEOUT = 5000; // zookeep ...

  3. vmware安装centos7

    VMware下安装CentOS7.2 http://www.mamicode.com/info-detail-1455647.html centos7.2配置网络 http://blog.csdn.n ...

  4. docker 报错:x509: certificate has expired or is not yet valid

    环境:centos 7 程序:docker 下载镜像报错: # docker pull centos Pulling repository centos FATA[0004] Get https:// ...

  5. Python世界里的赋值运算符

    Python赋值运算符 以下假设变量a为10,变量b为20: "=" 的作用是把右边的数值赋值给左边的变量 示例1:编程实现145893秒是几天几小时几分钟几秒钟? total = ...

  6. 广告等第三方应用嵌入到web页面方案 之 使用js片段

    在自己的项目中嵌入过广告的朋友们可能都用过百度联盟, 只需要嵌入如下一段js代码片段, 就可以在自己的项目中嵌入广告, 来获得收益. <script type="text javasc ...

  7. 利用while语句,条件为输入的字符不为'\n'.

    题目:输入一行字符,分别统计出其中英文字母.空格.数字和其它字符的个数. 1.程序分析:利用while语句,条件为输入的字符不为'\n'. 一个很简单的问题,其实换种方式就能完成,但是我就想怎么着才能 ...

  8. Spring实现无需注解实现自动注入

    xml配置 过程:设置自动装配的包-->使用include-filter过滤type选择为regex为正则表达式-->expression是表达是式也就是限制条件 <?xml ver ...

  9. python实现三级菜单

    一.要求: 1.一开始打印出所有省份和提示 2.用户输入省份以此查询城市 3.在按照输出的城市名提示用户输入,最后输出用户所查询的区县名 4.随时输入"back"可以返回上一级菜单 ...

  10. 4.3 lambda表达式

    函数,封装的代码块可以很复杂,也可以很简单.当函数的代码块简单到只有一个表达式,就可以考虑用lambda表达式,也称匿名函数. 1 lambda表达式基础 #首先需要声明一点:lambda是表达式而非 ...