Python爬虫实战,DecryptLogin模块,Python模拟登录之生成QQ个人专属报告


前言

这次我们借助自己开源的DecryptLogin库做一件有趣的事,生成QQ个人专属报告。
就是把QQ中和自己相关的数据爬取下来并进行可视化~

开发工具

** Python版本:**3.6.4

** 相关模块:**

DecryptLogin模块;

wordcloud模块;

requests模块;

pillow模块;

以及一些python自带的模块。

环境搭建

安装Python并添加到环境变量,pip安装需要的相关模块即可。

原理简介

这里我们简单介绍一下实现原理。首先,我们需要借助自己开源的库来实现模拟登录QQ空间,QQ群以及我的QQ中心。

而QQ群和我的QQ中心的模拟登录原理和QQ空间大同小异,这里就不再赘述了啊,对了,库的安装方式参见很久以前的文章:

推荐pycharm安装第三方库的一些方法

这里我们来着重讲一讲QQ个人数据爬取部分的原理。最基本的自然是我们自己填写的个人信息啦,这个在我的QQ中心可以找到,比如:

通过抓包分析,我们可以发现以下链接包含了我们需要的信息:

当然还有其他一些链接。感兴趣的小伙伴可以自己再找找。观察这些请求需要携带的参数:

请求上图userinfo那个链接需要携带的参数:
ldw: 497****************b1f3611dba60b802*******1268dac
r: 0.3504175608072273
请求上图summary那个链接需要携带的参数:
ldw: 497****************b1f3611dba60b802*******1268dac
r: 0.502514472265531

我们很容易发现,请求这些链接都需要携带两个参数,即:

ldw:不知道是啥玩意。
r:看着就像是随机数。

测试后发现ldw这个参数并不是固定不变的,那我们应该如何计算这个参数呢?全局搜索一下,可以发现一个计算ldw的函数:

乍一眼看,这是啥玩意啊T_T。好像不好算?别急,作为新时代熟读兵法的优秀少年,应该懂得“借刀杀人”的道理。我们再仔细看看ldw到底是啥,找到相关的js文件,可以发现一个注释:

搜一下有没有相关的运算,结果发现了这个:

借助注释,合理猜测一下ldw的值可能可以用bkn的值代替,bkn很好算,js代码一目了然。试着验证一下,竟然真的可以T_T。于是我们就可以抓取到个人的基本信息了,代码实现如下:

'''获取登录账户的个人资料'''
def getPersonalInfo(self, filename='personal_info.pkl'):
  personal_info = dict()
  summary_url = 'https://id.qq.com/cgi-bin/summary?'
  userinfo_url = 'https://id.qq.com/cgi-bin/userinfo?'
  bkn = self.__skey2bkn(self.session_id_all_cookies['skey'])
  params = {
        'r': str(random.random()),
        'ldw': str(bkn)
      }
  headers = {
        'referer': 'https://id.qq.com/myself/myself.html?ver=10049&',
        'accept': '*/*',
        'accept-encoding': 'gzip, deflate, br',
        'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
        'cache-control': 'no-cache',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'
      }
  res = self.session_id.get(summary_url, headers=headers, params=params, verify=False)
  res.encoding = 'utf-8'
  personal_info.update(res.json())
  params = {
        'r': str(random.random()),
        'ldw': str(bkn)
      }
  headers = {
        'referer': 'https://id.qq.com/myself/myself.html?ver=10045&',
        'accept': '*/*',
        'accept-encoding': 'gzip, deflate, br',
        'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
        'cache-control': 'no-cache',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'
      }
  while True:
    res = self.session_id.get(userinfo_url, headers=headers, params=params, verify=False)
    res.encoding = 'utf-8'
    if res.text:
      break
  personal_info.update(res.json())
  personal_info_parsed = {
              '昵称': personal_info['nick'],
              '出生日期': '-'.join([str(personal_info['bir_y']), str(personal_info['bir_m']).zfill(2), str(personal_info['bir_d']).zfill(2)]),
              '年龄': personal_info['age'],
              'Q龄': personal_info['qq_age'],
              '账号等级': personal_info['level'],
              '等级排名': personal_info['level_rank'],
              '好友数量': personal_info['friend_count'],
              '单向好友数量': personal_info['odd_count'],
              '已备注好友数量': personal_info['remark_count'],
              '好友分组数量': personal_info['group_count'],
              '最近联系人数量': personal_info['chat_count'],
              '工作': personal_info['work'],
              '个性签名': personal_info['commt']
            }
  saveData2Pkl(personal_info_parsed, os.path.join(self.dirpath, filename))
  return personal_info_parsed

然后利用抓取的个人信息,我们可以制作一张简易的QQ名片,效果参见效果展示部分。

接下来,我们看看能不能抓取点其他数据。比如所有的QQ好友数据?通过观察,发现这些数据可以在QQ群官网里找到:

请求这个链接需要的参数是bkn,这个我们前面已经说过怎么算了,所以就直接上代码啦(这里我们只能获取昵称和QQ号,好友的详细信息是无法获取的,这个以后有机会再搞吧):

'''抓取QQ好友数据'''
def getQQFriendsInfo(self, filename='friends_info.pkl'):
  friends_info = dict()
  get_friend_list_url = 'https://qun.qq.com/cgi-bin/qun_mgr/get_friend_list'
  bkn = self.__skey2bkn(self.session_qun_all_cookies['skey'])
  data = {'bkn': bkn}
  headers = {
        'referer': 'https://qun.qq.com/member.html',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'
      }
  res = self.session_qun.post(get_friend_list_url, data=data, verify=False, headers=headers)
  for key, value in res.json()['result'].items():
    for mem in value['mems']:
      qq_number = mem['uin']
      nickname = mem['name']
      friends_info[qq_number] = [nickname]
  saveData2Pkl(friends_info, os.path.join(self.dirpath, filename))
  return friends_info

然后将所有好友的昵称做个词云?效果还是效果展示部分(隐私问题,我进行了模糊处理)。

最后再获取一些最近的操作信息呗,比如最近30天退出的群,最近一年删除的好友,以及最近我关心谁,最近谁关心我等等,原理和之前差不多,就不赘述了,代码实现如下:

'''获取近期的操作数据'''
def getRecentOperationsInfo(self, filename='recent_operation_info.pkl'):
  recent_operation_info = dict()
  bkn = self.__skey2bkn(self.session_qun_all_cookies['skey'])
  # 近30天退出的群
  data = {'bkn': bkn}
  headers = {
          'accept': 'application/json, text/javascript, */*; q=0.01',
          'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36',
          'origin': 'https://huifu.qq.com',
          'referer' : 'https://huifu.qq.com/recovery/index.html?frag=0',
          'content-type': 'text/plain'
        }
  gr_grouplist_url = 'https://huifu.qq.com/cgi-bin/gr_grouplist'
  res = self.session_qun.post(gr_grouplist_url, data=data, headers=headers, verify=False)
  group_info = res.json()
  recent_operation_info['过去30天我退出的群个数'] = len(group_info.get('ls', []))
  recent_operation_info['过去30天我退出的群'] = []
  if 'ls' in group_info.keys():
    for key, value in group_info.items():
      recent_operation_info['过去30天我退出的群'].append(value['n'])
  # 近一年删除的好友
  params = {
        'bkn': str(bkn),
        'ts': str(int(time.time())),
        'g_tk': str(bkn),
        'data': '{"11053":{"iAppId":1,"iKeyType":1,"sClientIp":"","sSessionKey":"%s","sUin":"%s"}}' % (self.session_qun_all_cookies['skey'], self.username)
      }
  headers = {
          'accept': 'application/json, text/javascript, */*; q=0.01',
          'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36',
          'origin': 'https://huifu.qq.com',
          'referer' : 'https://huifu.qq.com/recovery/index.html?frag=1',
          'content-type': 'text/plain'
        }
  srfentry_url = 'https://proxy.vip.qq.com/cgi-bin/srfentry.fcgi?'
  res = self.session_qun.get(srfentry_url, params=params, headers=headers, verify=False)
  del_friend_list = res.json()['11053']['data']['delFriendList']
  recent_operation_info['过去一年我删除的好友个数'] = len(del_friend_list)
  recent_operation_info['过去一年我删除的好友'] = []
  if len(del_friend_list) > 0:
    recent_operation_info['过去一年我删除的好友'] = del_friend_list['364']['vecUin']
  # 谁在意我
  bkn = self.__skey2bkn(self.session_zone_all_cookies['skey'])
  headers = {
          'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'
        }
  res = self.session_zone.get('https://user.qzone.qq.com/%s' % self.username, verify=False, headers=headers)
  qzonetoken = re.findall(r'{ try{return "(.+?)";', res.text)[0]
  params = {
        'uin': str(self.username),
        'do': '2',
        'rd': str(random.random()),
        'fupdate': '1',
        'clean': '0',
        'g_tk': str(bkn),
        'qzonetoken': str(qzonetoken)
      }
  ship_url = 'https://rc.qzone.qq.com/proxy/domain/r.qzone.qq.com/cgi-bin/tfriend/friend_ship_manager.cgi?'
  res = self.session_zone.get(ship_url, verify=False, headers=headers, params=params)
  text = (res.text).replace('_Callback(','')
  who_care_me = json.loads(text[:len(text)-2])['data']['items_list']
  if len(who_care_me) > 5:
    who_care_me = who_care_me[:5]
  recent_operation_info['谁在意我'] = [[each['uin'], each['score']] for each in who_care_me]
  # 我在意谁
  params = {
        'uin': str(self.username),
        'do': '1',
        'rd': str(random.random()),
        'fupdate': '1',
        'clean': '1',
        'g_tk': str(bkn),
        'qzonetoken': str(qzonetoken)
      }
  res = self.session_zone.get(ship_url, verify=False, headers=headers, params=params)
  text = (res.text).replace('_Callback(','')
  i_care_who = json.loads(text[:len(text)-2])['data']['items_list']
  if len(i_care_who) > 5:
    i_care_who = i_care_who[:5]
  recent_operation_info['我在意谁'] = [[each['uin'], each['score']] for each in i_care_who]
  saveData2Pkl(recent_operation_info, os.path.join(self.dirpath, filename))
  return recent_operation_info

然后和前面一样,也做个卡片吧~效果还是参见效果展示部分。

OK,为了避免推文的时候又比较晚T_T,今天就到这吧~完整源代码详见相关文件。

效果展示

这里把抓取到的数据进行可视化。

1. QQ名片

2. 好友昵称词云

3. 我的近期操作

文章到这里就结束了,感谢你的观看,关注我每天分享Python模拟登录系列,下篇文章分享下载B站指定UP主的所有视频