qq好友空间说说爬虫

这篇日志主要记录、总结自己写的一个小爬虫,算是自己学习python的小练习吧。

这个爬虫主要实现了爬取qq好友们2018年的说说、提取关键信息并且存入excel文件的功能。

该爬虫使用的python版本是python3

该爬虫的使用第三方库有seleniumrequestsreopenpyxltime

其中,selenium库用于登陆访问页面,requests库用于向网页发送请求、re库用于匹配、提取返回包含说说信息的内容,openpyxl库用于储存信息,time库用于计算程序运行时间。

程序源码放在了GitHub上iszff/QQ-Crawler

爬虫思路

使用selenium登陆空间

selenium是一个自动化测试工具,它可以寻找、定位、选择网页中的元素,向网页提交一些信息,如输入用户名、密码,也可以获取页面中的信息,如返回当前页面的url、网页源码。

感觉测试攻城狮Anthony_tester Python+Selenium自动化测试从零到框架设计系列 教程写的很棒,适合入门。

进入好友空间说说页面、登陆并获取信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def login_in(url, qq, password):
driver = webdriver.Chrome()
driver.set_page_load_timeout(10)
driver.get(url)
time.sleep(6)
driver.switch_to.frame('login_frame')#通过观察网页源码,发现账号和密码的输入框在<frame>标签下,需要跳入框架中,否则定位不到
driver.find_element_by_xpath('//*[@id="switcher_plogin"]').click()#点击选择
driver.find_element_by_xpath('//*[@id="u"]').clear()
driver.find_element_by_xpath('//*[@id="u"]').send_keys(qq)#输入账号
driver.find_element_by_xpath('//*[@id="p"]').clear()
driver.find_element_by_xpath('//*[@id="p"]').send_keys(password)#输入密码
driver.find_element_by_xpath('//*[@id="login_button"]').click()
time.sleep(6)

html = driver.page_source#返回页面内容,根据内容判断内否进入空间
fw = re.findall(r'主人设置了权限,您可通过以下方式访问',html)#判断好友空间是否限制权限
if len(fw)!=0:
driver.delete_all_cookies()
driver.quit()
return 0,0,0

else:
cookies = driver.get_cookies()#获取cookie
cookie = {}
for elem in cookies:
cookie[elem['name']] = elem['value']
driver.delete_all_cookies()

hashes=5381
for letter in cookie['p_skey']:
hashes += (hashes << 5) + ord(letter)
gtk=str(hashes & 0x7fffffff)

g_qzonetoken = re.findall(r'window\.g\_qzonetoken \= \(function\(\)\{ try\{return \".*?\"',html)
qzonetoken=g_qzonetoken[0].split('"')[-2]

driver.quit()

return cookie, gtk, qzonetoken

通过观察浏览器控制台中的请求头,发现请求连接中有g_tk和qzonetoken这两项

而且两项都是动态变化的,每次刷新都会变化,不过经过多次…尝试,似乎qzonetoken的变化影响不大

qzonetoken包含在页面的源代码中,可以通过正则匹配直接获得

在js内容找出可以找出一个g_tk的加密算法(向大佬们低头)

使用requests向网页发送请求

requests可以通过传递参数向网络发送http请求,可以获得不同类型的Response内容。

官方文档写的很不错。

构造链接

1
2
3
4
5
6
7
8
9
urlOrd = 'https://user.qzone.qq.com/proxy/domain/taotao.qq.com/cgi—bin/emotion_cgi_msglist_v6?uin='
sec='&ftype=0&sort=0&pos='
third='&num=20&replynum=100&g_tk='
forth='&callback=_preloadCallback&code_version=1&format=jsonp&need_private_comment=1&qzonetoken='
fifth='&g_tk='

for ye in range(400):
pos = str(ye*20)
emotionurl=urlOrd+ friend_qq + sec + pos + third + gtk + forth + qzonetoken+ fifth + gtk

观察确定请求url格式,构造请求url

对网页请求

1
2
3
4
def get_emotion(url,cookie):
header= {'accept-encoding': 'gzip, deflate, br', 'accept-language': 'zh-CN,zh;q=0.8', 'host': 'h5.qzone.qq.com', 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'connection': 'keep-alive'}
r = requests.get(url, headers=header, cookies=cookie)
return r.text #返回文本信息

获取好友信息

获取好友信息的爬虫思路和获取说说页面响应的思路基本一致,只是url有所改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def enter_qzone(my_qq, my_password, save_path):#进入网页 获取cookies 构造链接
url = 'https://user.qzone.qq.com/'+my_qq+'/311'
(cookie, gtk, qzonetoken) = login_in(url, my_qq, my_password)

urlOrd = 'https://user.qzone.qq.com/proxy/domain/r.qzone.qq.com/cgi-bin/tfriend/friend_hat_get.cgi?hat_seed=1&uin='
second ='&fupdate=1&g_tk='
third = '&qzonetoken='
fourth = '&g_tk='
haturl= urlOrd + my_qq + second + gtk + third + qzonetoken + fourth + gtk

textpage = get_friendhat(haturl, cookie)
process_save(textpage,save_path)


def get_friendhat(url,cookie):#发送请求 获取响应
header= {'accept-encoding': 'gzip, deflate, br', 'accept-language': 'zh-CN,zh;q=0.8', 'host': 'h5.qzone.qq.com', 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'connection': 'keep-alive'}
r = requests.get(url, headers=header, cookies=cookie)
return r.text

def process_save(text,save_path):#提取信息并储存
friend=re.findall('\".*?\"\:\{\n\"realname\"\:\".*?\"\}',text)
frNum = len(friend)

Name = []
for elem in friend:
n =elem.split('"realname":"')[1].split('"}')[0]
Name.append(n)
QQ = []
for elem in friend:
q = elem.split('"')[1]
QQ.append(q)

wb = load_workbook(save_path)
ws = wb.get_sheet_by_name("friend_info")
wc_name=ws['A2':'A'+str(frNum+2)]
wc_qq=ws['B2':'B'+str(frNum+2)]
for i in range(frNum):
wc_name[i][0].value = Name[i]
wc_qq[i][0].value = QQ[i]

wb.save(save_path)

使用正则表达式re库提取信息

观察响应信息的内容,不难发现它的表达方式是json格式。

可以直接使用Requests库中内置的JSON解码器r.json()

当时写代码的时候…没想起来( ̄。。 ̄),直接暴力的用正则对文本内容进行了正则匹配,不过也达到了提取信息的目的

这里使用的方法是对返回的一个很长很长的字符串的文本信息进行正则匹配,这一块就没什么好说的了,只要对正则的使用比较熟练就很方便。

正则表达式的常用操作符

操作符 说明
. 表示任何单个字符
[ ] 字符集,对单个字符给出取值范围
非字符集,对单个字符给出排除范围
* 前一个字符出现0次或无限次扩展
+ 前一个字符出现1次或无限次扩展
前一个字符0次或1次扩展
| 左右表达式任取其一
{m} 扩展前一个字符m次
{m,n} 扩展签一个字符m至n次(含n)
^ 匹配字符串开头
$ 匹配字符串结尾
() 分组标记,内部只能使用|操作符
\d 数字
\w 单词字符

要注意正则表达式默认的是贪婪匹配,即趋于最大匹配长度,使用*?可以实现最小匹配

另一点需要注意的地方就是转义字符的问题

re库的函数

函数 说明
re.search() 在一个字符串中搜索匹配正则表达式的第一个位置,返回match对象
re.match() 从一个字符串的开始位置起匹配正则表达式,返回match对象
re.findall() 搜索字符串,一列表类型返回全部能匹配的字符串
re.split() 将一个字符串按照正则表达式匹配结果进行分割,返回列表
re.finditer() 搜索字符串,返回匹配结果的迭代类型,每个迭代元素是match对象
re.sub() 在一个字符串中替换所有匹配正则表达式的子串,返回替换后的字符串

re库本生就有很多好用的函数,当时写代码的时候也没怎么想起来( ̄。。 ̄)

当时在返回信息中并没有找到发说说时刻这个信息,created_time对用的是一串数字,通过我咳咳…细心的观察发现这一串数字是秒数,发说说的具体时间是按照秒数计算的。通过对几条说说的观察算出1514649600是2018年1月1日00:00对应的秒数。当时还有专门去算一下对应的0代表的是哪个时间点,算出来是1970年1月1日08:00,一开始还以为是马化腾生日(+_+)。后来也算是偶然知道了unix时间戳是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,很多语言后来也是这样如java、python。

1
2
3
h = postTime.split(',"created_time":')[1].split(',"editMask"')[0]
hour = ( int(h) - 1514649600 ) %86400 /3600#算出一天中发说说的时刻
ssInfo.append(hour)

使用openpyxl将信息存入excel文件中、

openpyxl提供了很多方便操作.xlsx文件的函数,通过Sheet、cell的操作可以访问、读写单元格。

本来是想用数据库的,但是并不会数据库,配置文件弄了半天也没弄好,就选择操作excel文件。

存储信息量太大的话最好不要存在一个文件中,加载会变慢

最终爬取了200多位好友2018年的说说,共5000多条。

储存在excel中的结果

数据可视化

可视化使用了matplotlib

对好友空间是否对自己开放情况进行了统计,绘制饼状图accessablePie

按照月份对好友们发说说条数绘制柱状图monthHist

按照小时对好友们发说说条数绘制柱状图hourHist

本文参照了标标的专栏文章爬取QQ空间

题外话

其实代码写得挺烂的,异常处理做的也不好,跑起来遇到了异常才对应的写异常处理。

另外就是网速要好o(≧口≦)o,不然requests就会报错,校网实在不能打,所以我选择去网吧(≧∇≦)。

算是一个上学期的一个小总结,也算是这学期的开始吧。

因为代码是上学期最后写的,这篇总结一直拖到了这学期开学才写,再不写就要上课了Orz。

希望大三一切都顺利吧,至少能够顺心意。