〇、前情提要
给爷爬。
参考:
- heartbreaker97/spider_qzone_friends
https://github.com/heartbreaker97/spider_qzone_friends - python爬虫-登录QQ空间获取COOKIES
https://www.bilibili.com/video/BV1w7411W7YR - python爬虫-QQ好友列表获取
https://www.bilibili.com/video/BV157411W7cx - python爬虫-说说点赞和评论列表获取
https://www.bilibili.com/video/BV1L7411W7ce
一、README.md
qq空间爬虫生成好友关系网
使用方法:
- 将cookie.py的chromedriver的地址修改成自己的(设置了环境变量的可不设置),账号密码按照提示修改后运行,生成cookie
- 运行spider.py爬取数据并初步分析将结果生成txt文件保存至本地
- 运行show_relation下的analysis.py,得到好友关系值文件
- 将show_relation中的relation.php按照其中提示修改第三步得到的txt文件的路径,再将整文件夹放置本地搭建的web服务器下,并在http://echarts.baidu.com/download.html 下载echarts插件,放在该文件夹下在浏览器上打开relation.php
效果图,为了显示效果和隐私问题,把名字给去了
局部效果
爬虫太慢,代码还在持续修改重构中,针对没有搭建本地web服务器的显示结果
二、登录QQ空间获取COOKIES-cookie.py
from selenium import webdriver
from time import sleep
import json
怎么使用selenium,及其驱动和浏览器匹配情况。
driver = webdriver.Chrome()
自动打开Chrome浏览器
driver.get('https://user.qzone.qq.com/你的qq/main')
自动登录你的qq主页
driver.switch_to.frame('login_frame')
找到登录的iframe
driver.switch_to.frame('login_frame')
safari审查元素,账号密码登录为< a >,其id为switcher_plogin
driver.find_element_by_id('switcher_plogin').click()
找到该元素点击,模拟用户浏览器行为
跳转到账号密码登录后,两个< input >的id分别为u和p
driver.find_element_by_id('u').send_keys('你的qq') driver.find_element_by_id('p').send_keys('你的密码')
driver.find_element_by_id('login_button').click()
模拟用户按了登录按钮,此时登录的cookies被保存。
cookies = driver.get_cookies()
cookie_dic = {}
for cookie in cookies:if 'name' in cookie and 'value' in cookie:cookie_dic[cookie['name']] = cookie['value']with open('cookie_dict.txt', 'w') as f:json.dump(cookie_dic, f)
最后得到的cookie_dict文件如下所示:
例如ptui_loginuin为qq号…
from selenium import webdriver
from time import sleep
import json#chromedriver = 'C:/Users/13180/AppData/Local/Google/Chrome/Application/chromedriver.exe'
#driver = webdriver.Chrome(chromedriver)
driver = webdriver.Chrome()
driver.get('https://user.qzone.qq.com/你的qq/main')
driver.switch_to.frame('login_frame')
#找到账号密码登陆的地方
driver.find_element_by_id('switcher_plogin').click()
driver.find_element_by_id('u').send_keys('你的qq')
driver.find_element_by_id('p').send_keys('你的密码')
driver.find_element_by_id('login_button').click()
#保存本地的cookie
sleep(1)
cookies = driver.get_cookies()
cookie_dic = {}
for cookie in cookies:if 'name' in cookie and 'value' in cookie:cookie_dic[cookie['name']] = cookie['value']with open('cookie_dict.txt', 'w') as f:json.dump(cookie_dic, f)
三、QQ好友列表获取
进入空间后,点击说说,点击@,在Network里会弹出一个新的response(来自qq服务器)
双击该response,进入preview,data会返回很多信息,uin为qq号,name为qq昵称,remark为注释
通过程序来获取这样一个网址,然后可以得到一个相应
相应有如下参数:g_tk为最关键的参数
以下方法提供如何求gtk的值,求出后网址便构造完毕
#算出来gtkdef get_gtk(self):p_skey = cookie['p_skey']h = 5381for i in p_skey:h += (h << 5) + ord(i)g_tk = h & 2147483647return g_tk
就可以找出好友列表,通过所有好友列表,进行一个请求,把你需要的信息存入字典,即可获得你想要的信息。
#找出好友列表def get_friend(self):url_friend = 'https://user.qzone.qq.com/proxy/domain/r.qzone.qq.com/cgi-bin/tfriend/friend_ship_manager.cgi?'g_tk = self.get_gtk()uin= self.get_uin()data = {'uin': uin,'do' : 1,'g_tk': g_tk}data_encode = urllib.parse.urlencode(data)url_friend += data_encoderes = requests.get(url_friend, headers = header, cookies = cookie)friend_json = re.findall('\((.*)\)',res.text,re.S)[0]friend_dict = json.loads(friend_json)friend_result_list = []#循环将好友的姓名qq号存入字典中for friend in friend_dict['data']['items_list']:friend_result_list.append([friend['name'],friend['uin']])#得到的好友字典是{name: qqNum}格式的return friend_result_listdef get(self, friend_list):self.find_topic(friend_list[1], friend_list[0])
四、说说点赞和评论列表获取
接下来循环进入每个好友的qq空间,有可能访问受限(设置私密),
先清除所有网络项目,再点击说说进入,右侧又会弹出新的网络项目。
需要获取的为说说的评论数量与点赞数量。
先来看点赞数据。
进入该url后,只获取了共同好友的数据。有几条说说就会得到几条数据,其中的list即为共同好友的名单,like为总共点赞数。
再来看评论情况
则能看到content对应评论内容,name对应昵称,uin对应qq号。
在计算亲密度时,点赞+1,评论+3,分值越高,亲密度越高。
其他参数也可获取,其中最重要的为g_tk。
最后可获取到关系网。
import urllib
import requests
import json
import re
import threadpool
import time
import operator as opclass Qzone:#算出来gtkdef get_gtk(self):p_skey = cookie['p_skey']h = 5381for i in p_skey:h += (h << 5) + ord(i)g_tk = h & 2147483647return g_tk#得到uindef get_uin(self):uin = cookie['ptui_loginuin']return uindef write_comment(self,m,qq,name):g_tk = self.get_gtk()if 'content' not in m.keys():return 0if m['content'] is None:return 0# 查看是否有评论if ('commentlist' in m.keys()):if m['commentlist'] is None:return 0for comment in m['commentlist']:# 计算#self.cal_relationship(comment['uin'],name,comment['name'],3)#判断共同好友,是的话则写入if comment['uin'] in qq_list:file_comment.write(name+'$|$'+comment['name']+'\n')# 如果评论大于20条的话需要再进入新的界面,为了写入评论方便,所以在这里写# 正常来说说说评论都不超过40条,所以这里写的最多的就是40条了,懒得改了if m['cmtnum'] > 20:data_more_20 = {'uin': qq,'topicId': str(qq) + '_' + m['tid'],'ftype': 0,'sort': 0,'order': 20,'start': 20,'num': 20,'t1_source': 'undefined','callback': '_preloadCallback','code_version': 1,'format': 'jsonp','need_private_comment': 1,'g_tk': g_tk,}url_more_20 = 'https://h5.qzone.qq.com/proxy/domain/taotao.qzone.qq.com/cgi-bin/emotion_cgi_getcmtreply_v6?'url_more_20 += urllib.parse.urlencode(data_more_20)res_more_20 = requests.get(url_more_20, headers=header, cookies=cookie)# 新的json格式解析,这里返回的json数据又不一样了,需要新的解析# print(url_more)# 匹配出_Callback之后的内容r_more_20 = re.findall('\((.*)\)', res_more_20.text)[0]m_more_20 = json.loads(r_more_20)# 得到的m_more_20的字典结构 dict_keys(['code', 'data', 'message', 'result', 'right', 'smoothpolicy', 'subcode'])data = m_more_20['data']if 'comments' not in data.keys():return 0for comment in data['comments']:# 判断共同好友,是的话则写入if comment['poster']['id'] in qq_list:file_comment.write(name + '$|$' + comment['poster']['name']+'\n')# self.cal_relationship(comment['poster']['id'], name, comment['poster']['name'], 3)'''#记录说说def write_like(self,m, qq, name):g_tk = self.get_gtk()uin = self.get_uin()global relationshipsurl_like = 'https://user.qzone.qq.com/proxy/domain/users.qzone.qq.com/cgi-bin/likes/get_like_list_app?'if m['content'] is None:return 0tid = m['tid']# 如果说说是转发的,那么关系网可能会错误,因为得到的点赞列表是源说说的,那么可能会出现A,B都是自己的好友,而A,B他们不是好友,# A转发了一天烂大街的说说,然后几经转发后B也看到了,就点赞,误认为AB是好友那么直接跳过点赞if 'rt_uin' in m.keys() and 'rt_tid' in m.keys():return 0data_like = {'uin': uin, # 注意这里的uin是自己的qq'unikey': 'http://user.qzone.qq.com/' + str(qq) + '/mood/' + str(tid) + '.1','begin_uin': 0,'query_count': 60,'if_first_page': 1,'g_tk': g_tk}data_like_encode = urllib.parse.urlencode(data_like)url_like += data_like_encoderes = requests.get(url_like, headers=header, cookies=cookie)# 踩坑,这里必须加不然会乱码!!!!石家庄å想这样子的乱码!res.encoding = 'UTF-8'r = re.findall('\((.*)\)', res.text, re.S)[0]# 将json数据变成字典格式r = json.loads(r)if 'data' not in r.keys():return 0if 'like_uin_info' not in r['data'].keys():return 0for like in r['data']['like_uin_info']:if like['fuin'] in qq_list:file_like.write(name + '$|$' + like['nick'] + '\n')#找到说说模块#pos是当前页面第一个说说的排序,一页20个def find_topic(self, qq, name):page = 1#conti循环的标志,当为false时退出循环conti = Truepos = 0g_tk = self.get_gtk()uin = self.get_uin()while conti:#url必须在循环内,每次循环必须重置url = 'https://user.qzone.qq.com/proxy/domain/taotao.qq.com/cgi-bin/emotion_cgi_msglist_v6?'data = {'uin': qq,'pos': pos,'num': 20,'hostUin': uin,'replynum': 100,'callback': '_preloadCallback','code_version': 1,'format': 'jsonp','need_private_comment': 1,'g_tk': g_tk,}#下次翻页#下次翻页pos += 20#构造访问说说页面的HTTP报文内容url += urllib.parse.urlencode(data)res = requests.get(url,headers=header,cookies=cookie)print('读取 '+name+' 的第 '+str(page)+' 页说说成功')page += 1#匹配出_preloadCallback之后的内容if len(re.findall('\((.*)\)', res.text)) == 0:#记录下没有权限的file_denied.write(name + ': ' + str(qq) + '\n')continuer = re.findall('\((.*)\)', res.text)[0]#将json数据变成字典格式msg = json.loads(r)#如果没有说说就返回if 'msglist' not in msg:return 0#这里爬说说结束,注意和上面的区别,一个是不存在键值,一个是存在键,但值类型为Noneif msg['msglist'] == None:print('\n'+name+'的空间无更多说说'+'\n')return 0#得到的说说相关内容都在msglist(list类型)里面,msglist[i]是字典类型,可利用keys方法查看结构#说说内容conlist[0]['con'],另外转发的说说在conlist[1/2/3....]#每一条说说就是mfor m in msg['msglist']:#每一条说说下根据点赞计算关系值#self.cal_relationship_by_like(m, qq, name)#记录共同好友点赞记录self.write_like(m, qq, name)#如果评论数大于10,则需要点进查看全部评论if m['cmtnum'] < 10:##这里特殊,如果转发了说说并且没有配文字,而且原说说被删了,就会出现错误if m['conlist'] is None:continueself.write_comment(m,qq, name)# 如果评论数大于10,则需要点进查看全部评论else:data_more = {'uin': qq,'tid': m['tid'],'ftype': 0,'sort': 0,'pos': 0,'num': 20,'t1_source': 'undefined','callback': '_preloadCallback','code_version': 1,'format': 'jsonp','need_private_comment': 1,'g_tk': g_tk,}url_more = 'https://user.qzone.qq.com/proxy/domain/taotao.qq.com/cgi-bin/emotion_cgi_msgdetail_v6?'url_more += urllib.parse.urlencode(data_more)res_more = requests.get(url_more, headers=header, cookies=cookie)# print(url_more)# 匹配出_preloadCallback之后的内容r_more = re.findall('\((.*)\)', res_more.text)[0]# print(res_more.text)m_more = json.loads(r_more)# 写入txt文件self.write_comment(m_more, qq, name)#得到好友qqdef get_qq(self):qq_list = []friend_list = self.get_friend()for friend in friend_list:qq_list.append(friend[1])return qq_list#找出好友列表def get_friend(self):url_friend = 'https://user.qzone.qq.com/proxy/domain/r.qzone.qq.com/cgi-bin/tfriend/friend_ship_manager.cgi?'g_tk = self.get_gtk()uin= self.get_uin()data = {'uin': uin,'do' : 1,'g_tk': g_tk}data_encode = urllib.parse.urlencode(data)url_friend += data_encoderes = requests.get(url_friend, headers = header, cookies = cookie)friend_json = re.findall('\((.*)\)',res.text,re.S)[0]friend_dict = json.loads(friend_json)friend_result_list = []#循环将好友的姓名qq号存入字典中for friend in friend_dict['data']['items_list']:friend_result_list.append([friend['name'],friend['uin']])#得到的好友字典是{name: qqNum}格式的return friend_result_listdef get(self, friend_list):self.find_topic(friend_list[1], friend_list[0])#开始def start(self):global relationshipsfriend_list = self.get_friend()pool_size = 15pool = threadpool.ThreadPool(pool_size)# 创建工作请求,这里他自己会把list分开reqs = threadpool.makeRequests(self.get,friend_list)# 将工作请求放入队列[pool.putRequest(req) for req in reqs]# for req in requests:# pool.putRequest(req)pool.wait()if __name__ == '__main__':qzone = Qzone()#将关系设置为全局变量以供方便调用relationships = []header = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:61.0) Gecko/20100101 Firefox/61.0","Accepted-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3","Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"}with open('cookie_dict.txt','r') as f:cookie = json.load(f)#得到qq列表,qq_list = qzone.get_qq()# 记录设置空间权限的死鬼file_denied = open('denied_list.txt', 'w', encoding='UTF-8')#记录共同好友点赞和评论的记录file_comment = open('comment.txt','w',encoding='UTF-8')file_like = open('like.txt', 'w', encoding='UTF-8')qzone.start()file_denied.close()file_comment.close()file_like.close()