通过Python爬取QQ空间说说并通过Pyechart进行可视化分析

有一天我突然发现自己空间的说说竟然已经达到1833条,于是萌生了爬一下看看的想法(其实就是想学下python爬虫)。我找了一些博客,方法不少,但是有些并不适用。所以我把真正能用的方法记录下来,并且爬取了我自己的全部说说,亲测可用。下面我介绍下爬虫的写法。

  • 用到的库——

selenium,requests,json,sqlite3,re,time,random

其中,selenium是用于模拟QQ空间登录的库,即一些动态页面的操作;requests爬虫常用库,不赘述;sqlite3用来存爬取的数据,python3之后的版本自带;re正则表达式,用来提取一些匹配字段。

  • QQ空间说说板块分析——

我们先打开自己的QQ空间,点击说说,会跳转到https://user.qzone.qq.com/your qq number/infocenter这样一个页面,查看网页源码,搜索说说的具体内容,我们是无法找到的。这说明说说的内容、点赞数、评论等都不是静态页面的一部分,而是通过Ajax等手段访问后台加载得到的,因此,我们需要模拟浏览页面的过程,即需要用selenium。

仔细查看浏览器控制台中,筛选出XHR部分的响应正文。可以得到两个重要的url,如下:

  1. https://h5.qzone.qq.com/proxy/domain/taotao.qq.com/cgi-bin/emotion_cgi_msglist_v6
  2. https://user.qzone.qq.com/proxy/domain/r.qzone.qq.com/cgi-bin/user/qz_opcnt2

第1条链接的响应正文包含了丰富的信息,格式是json。分析json格式,可以发现具体的内容都在msglist数组中,正常情况下一般是返回20条说说,所以数组长度是20。其中,有非常多可用的信息,如conlist属性下包括说说的具体内容(con),说说发表的时间戳(created_time),以及位置信息(lbs),都可以用来做一些相关的分析和可视化。这里提取content,cmtnum,tid和created_time,分别存储了说说具体内容,评论数,tid后面会用到,是一条说说的标识,以及说说发表时的时间戳。先将这四部分存入sqlite数据库中。

第2条链接是获取一些数值指标的接口,例如点赞数、评论数等等,这里为了获取点赞数,用第1个接口就无法获取,因此需要从数据库获取tid,根据它作为参数之一从第2条说说获取点赞数,再更新数据库,完成爬取任务。

以上就是爬虫的整体思路。

  • 代码部分——

先创建一个sqlite数据库,用于存储爬去的数据,会在.py文件目录中生成一个.db文件数据库,代码如下:

#coding = 'utf-8'

import sqlite3

def create(dbname,sql):
	conn = sqlite3.connect(dbname)
	c = conn.cursor()
	c.execute(sql)
	conn.commit()
	conn.close()

def main():
	dbname = 'mineqqzone.db'
	tablename = 'qqzoneinfo'
	createsql = '''
	create table qqzoneinfo
	(id integer primary key autoincrement,#id
	comment text,#说说内容
	cmtnum int,#评论数
	likenum int,#点赞数
	tid text,#参数
	createtime long)#时间戳
	'''
	create(dbname,createsql)

main()

首先要模拟登陆,具体代码在start_login()函数中,需要注意的是,获取g_tk这个参数的方法是对cookie中的p_skey进行加密得到,具体代码在get_g_tk()函数中,这段代码网上找找都找得到。

用selenium模拟浏览器需要下载例如phantomJS、Chromedriver这类的驱动,网上大部分是Chromedriver,但是我试了下配置似乎十分麻烦,所以选择phantomJS(无界面的浏览器),下载的链接在这http://phantomjs.org/download.html,可以下载对应版本。使用时只需在webdriver.PhantomJS()中把phantomjs.exe的路径赋给executable_path即可。虽然selenium现在的版本好像已经把phantomjs剔除了,但是似乎不影响使用。

扫描二维码关注公众号,回复: 8832629 查看本文章

qzonetoken参数可以从登陆后的html页面中提取,用正则表达式。

#coding = 'utf-8'

from selenium import webdriver
import requests
import json
from lxml import etree
from bs4 import BeautifulSoup
import sqlite3
import re
import time
import random

def get_g_tk(cookie):#获取g_tk参数,由cookie中的p_skey加密得到
    hashes = 5381
    for letter in cookie['p_skey']:
        hashes += (hashes << 5) + ord(letter)  # ord()是用来返回字符的ascii码
    return hashes & 0x7fffffff

def start_login():#模拟登录QQ空间
	driver = webdriver.PhantomJS(executable_path = "E:\\phantomjs-2.1.1-windows\\bin\\phantomjs.exe")
	url = "https://qzone.qq.com/"
	driver.get(url)

	driver.switch_to.frame('login_frame')
	driver.find_element_by_id('switcher_plogin').click()

	driver.find_element_by_id('u').clear()
	driver.find_element_by_id('u').send_keys('你的QQ号')  
	driver.find_element_by_id('p').clear()
	driver.find_element_by_id('p').send_keys('你的QQ密码')  

	driver.find_element_by_id('login_button').click()

	time.sleep(3)#等待加载完毕

	html = driver.page_source
	g_qzonetoken = re.search('window\.g_qzonetoken = \(function\(\)\{ try\{return (.*?);\} catch\(e\)',html)
	qzonetoken = str(g_qzonetoken[0]).split('\"')[1]#获取参数之一

	cookie_list = driver.get_cookies()#获取cookie
	cookie_dict = {}

	for cookie in cookie_list:
		cookie_dict[cookie['name']]=cookie['value']

	g_tk = get_g_tk(cookie_dict)

	return cookie_dict,g_tk,qzonetoken

我们开始爬取评论、说说、评论数、时间戳,代码如下:

def sqlopr(conn,cur,item):#存入数据库
	cur.execute("insert into qqzoneinfo (comment,cmtnum,tid,createtime) values (?,?,?,?)",(item['content'],int(item['cmtnum']),item['tid'],item['created_time']))
	conn.commit()

def spidercmt():#爬取评论
	cookie,g_tk,qzonetoken = start_login()

        headers={
	'User-Agent':'打开浏览器控制台查看,复制过来即可'
        }

	conn = sqlite3.connect('mineqqzone.db')
	cur = conn.cursor()

	sessions = requests.session()
	count = int(你的说说总数/20) + 1

	for i in range(count):
	    pos = 当前爬取的说说数量减1 + i*20#因为有可能中途会爬取出错,你可以改变pos继续爬,哈哈哈
	    param = {
	    'uin': '你的QQ号',
            'ftype': '0',
            'sort': '0',
            'pos': pos,
            'num': '20',#爬取的说说条数,网页上默认是20条
            'replynum': '100',
            'g_tk': [g_tk, g_tk],#用在这
            'callback': '_preloadCallback',
            'code_version': '1',
            'format': 'jsonp',
            'need_private_comment': '1',
            'qzonetoken': qzonetoken#用在这
	    }
	    respond = sessions.get('https://h5.qzone.qq.com/proxy/domain/taotao.qq.com/cgi-bin/emotion_cgi_msglist_v6',params=param,headers=headers,cookies=cookie)

	    r = re.sub("_preloadCallback","",respond.text)
	    test = r[1:-2]
	    Data = json.loads(test)

	    if not re.search('lbs',test):
	    	print('说说下载完成')
	    else:
                for j in range(len(Data['msglist'])):
		    sqlopr(conn,cur,Data['msglist'][j])
	    time.sleep(2)#可以改时间
	    print("suc"+str(i))

以下是爬取点赞数的代码:

def spiderlikenum():
	cookie,g_tk,qzonetoken = start_login()

	conn = sqlite3.connect('mineqqzone.db')
	cur = conn.cursor()

	sessions = requests.session()
	count = len(cur.execute('select * from qqzoneinfo where likenum is Null').fetchall())

	url = 'http://user.qzone.qq.com/你的QQ号/mood/'

	while count != 0:
	    ress = cur.execute('select * from qqzoneinfo where likenum is Null').fetchall()
	    count = len(ress)-1
	    idc = ress[0][0]
	    tid = ress[0][4]

	    _stp = int(round(time.time() * 1000)) #时间戳
	    param = {
	    '_stp': str(_stp),#时间戳是一个参数
	    'unikey': url + tid + '.1<.>' + url + '.1',
	    'g_tk': [g_tk, g_tk],
	    'face': '0',
	    'fupdate': '1',
	    'qzonetoken': qzonetoken
	    }

	    respond = sessions.get('https://user.qzone.qq.com/proxy/domain/r.qzone.qq.com/cgi-bin/user/qz_opcnt2',params=param,headers=headers,cookies=cookie)
	    
            r = re.sub("_Callback","",respond.text)

            test = r[1:-2]
	    if test == None or test == "" or len(test) == 0:
	    	print("lose data")
	    	continue
	    else:
	    	try:
	            Data = json.loads(test)
		    likenum = Data['data'][0]['current']['likedata']['cnt']
		    cur.execute("update qqzoneinfo set likenum = "+str(likenum)+" where id = "+str(idc))
		    conn.commit()
		    print('like:'+str(likenum))
		    print(idc)
		    ran = random.random()
		    time.sleep(1+ran)#增加随机性,1-2秒访问一次
		except:
		    print("error")

第2条链接的unikey参数一般是按照多条(例如20条)说说的tid进行组装,再访问链接,但是为了方便访问和解析,这里我每次只访问一条说说,这样unikey的组装相对简单明了,返回的响应正问也结构清楚,这样做的唯一缺点就是效率降低很多,只能一条条爬,按照每条1-2秒计算,如果中途不出问题,1800条至少要30min~1h,因此这部分可以根据情况改进,比如多线程、并行等都可以。

爬取点赞数代码的原理是不断访问数据库剩余未爬取点赞数的说说,while循环获取点赞数,直到全部获取为止。

以上就是全部的爬取代码。

  • 可视化分析部分——

爬取了所有的说说数据,就可以做任何想做的可视化咯。可视化可以用echarts的python版本,非常方便,链接在http://pyecharts.org

以下有一些分析的效果图,用了Scatter3D,Scatter,Bar,Pie,HeatMap这些基本表。

分析之后可以发现,大学之前的点赞和评论都极少,说明人际交友等都很少,大学之后(2014-2018)这段期间,虽然说说发表的数量每年都在减少,话越来越少,但是获得高赞和高评论的次数似乎不减反增,说明交际的质量提高了。查看一下点赞数和评论数最高的十条说说,不尽相同,也非常有意思,如下图。

女票霸榜了!有种重翻日记本的感觉~

最后也可以做成日历热力图,可以把每年的说说都展示出来,频数一目了然。这里就展示出两年的图。

以上就是最近这个idea的实现,完整的数据爬取和数据可视化的流程,feeling good。这篇文章核心是分享下爬虫的写法,因此只展示了爬虫部分的代码。可视化代码相对简单,有空再把可视化的代码展示一下~

发布了40 篇原创文章 · 获赞 28 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/lyandgh/article/details/82945942