天天看點

【實戰】用Python進行10w+QQ說說資料分析

Doctor | 作者

知乎 | 來源

https://zhuanlan.zhihu.com/p/27604277

對程式設計沒有興趣的朋友可以直接看後面的資料分析結果。

  • 開發環境:win7下的python3.5、MySQL5.7
  • 編輯器:pycharm2017.1、ipython,Navicat for mysql
  • 需要的python第三方庫:selenium、PIL、Requests、MySQLdb、csv、pandas、numpy、matplotlib、jieba、wordcloud另外還用到了無頭浏覽器PhantomJS。

主要思路:

  • 通過selenium+phantomjs模拟登入qq空間取到cookies和g_qzonetoken,并算出gtk
  • 通過Requests庫利用前面得到的url參數,構造http請求
  • 分析請求得到的響應,是一個json,利用正規表達式提取字段
  • 設計資料表,并将提取到的字段插入到資料庫中 
  • 通過qq郵箱中的導出聯系人功能,把好友的qq号導出到一個csv檔案,周遊所有的qq号爬取所有的說說
  • 通過sql查詢和ipython分析資料,并将資料可視化
  • 通過python的第三方庫jieba、wordcloud基于說說的内容做一個詞雲 

閑話不多說,直接上代碼。

通過selenium+phantomjs模拟登入qq空間取到cookies和g_qzonetoken,并算出gtk

import re
from selenium import webdriver
from time import sleep
from PIL import Image

#定義登入函數
def QR_login():
    def getGTK(cookie):
        """ 根據cookie得到GTK """
        hashes = 5381
        for letter in cookie['p_skey']:
            hashes += (hashes << 5) + ord(letter)

return hashes & 0x7fffffff
    browser=webdriver.PhantomJS(executable_path="D:\phantomjs.exe")#這裡要輸入你的phantomjs所在的路徑
    url="https://qzone.qq.com/"#QQ登入網址
    browser.get(url)
    browser.maximize_window()#全屏
    sleep(3)#等三秒
    browser.get_screenshot_as_file('QR.png')#截屏并儲存圖檔
    im = Image.open('QR.png')#打開圖檔
    im.show()#用手機掃二維碼登入qq空間
    sleep(20)#等二十秒,可根據自己的網速和性能修改
    print(browser.title)#列印網頁标題
    cookie = {}#初始化cookie字典
    for elem in browser.get_cookies():#取cookies
        cookie[elem['name']] = elem['value']
print('Get the cookie of QQlogin successfully!(共%d個鍵值對)' % (len(cookie)))
    html = browser.page_source#儲存網頁源碼
    g_qzonetoken=re.search(r'window\.g_qzonetoken = \(function\(\)\{ try\{return (.*?);\} catch\(e\)',html)#從網頁源碼中提取g_qzonetoken
    gtk=getGTK(cookie)#通過getGTK函數計算gtk
    browser.quit()
return (cookie,gtk,g_qzonetoken.group(1))
if __name__=="__main__":
    QR_login()
           

通過Requests庫利用前面得到的url參數,構造http請求:

【實戰】用Python進行10w+QQ說說資料分析
【實戰】用Python進行10w+QQ說說資料分析

通過抓包分析可以找到上圖這個請求,這個請求響應的是說說資訊 。

【實戰】用Python進行10w+QQ說說資料分析

通過火狐浏覽器的一個叫json-dataview的插件可以看到這個響應是一個json格式的,開心!

然後就是用正規表達式提取字段了,這個沒什麼意思,直接看我的代碼吧:

def parse_mood(i):
    '''從傳回的json中,提取我們想要的字段'''
    text = re.sub('"commentlist":.*?"conlist":', '', i)
if text:
        myMood = {}
        myMood["isTransfered"] = False
        tid = re.findall('"t1_termtype":.*?"tid":"(.*?)"', text)[0] # 擷取說說ID
        tid = qq + '_' + tid
        myMood['id'] = tid
        myMood['pos_y'] = 0
        myMood['pos_x'] = 0
        mood_cont = re.findall('\],"content":"(.*?)"', text)
if re.findall('},"name":"(.*?)",', text):
            name = re.findall('},"name":"(.*?)",', text)[0]
            myMood['name'] = name
if len(mood_cont) == 2: # 如果長度為2則判斷為屬于轉載
            myMood["Mood_cont"] = "評語:" + mood_cont[0] + "--------->轉載内容:" + mood_cont[1] # 說說内容
            myMood["isTransfered"] = True
        elif len(mood_cont) == 1:
            myMood["Mood_cont"] = mood_cont[0]
else:
            myMood["Mood_cont"] = ""
        if re.findall('"created_time":(\d+)', text):
            created_time = re.findall('"created_time":(\d+)', text)[0]
            temp_pubTime = datetime.datetime.fromtimestamp(int(created_time))
            temp_pubTime = temp_pubTime.strftime("%Y-%m-%d %H:%M:%S")
            dt = temp_pubTime.split(' ')
            time = dt[1]
            myMood['time'] = time
            date = dt[0]
            myMood['date'] = date
if re.findall('"source_name":"(.*?)"', text):
            source_name = re.findall('"source_name":"(.*?)"', text)[0] # 擷取發表的工具(如某手機)
            myMood['tool'] = source_name
if re.findall('"pos_x":"(.*?)"', text):#擷取經緯度坐标
            pos_x = re.findall('"pos_x":"(.*?)"', text)[0]
            pos_y = re.findall('"pos_y":"(.*?)"', text)[0]
if pos_x:
                myMood['pos_x'] = pos_x
if pos_y:
                myMood['pos_y'] = pos_y
            idname = re.findall('"idname":"(.*?)"', text)[0]
            myMood['idneme'] = idname
            cmtnum = re.findall('"cmtnum":(.*?),', text)[0]
            myMood['cmtnum'] = cmtnum
return myMood#傳回一個字典
           

我們想要的東西已經提取出來了,接下來需要設計資料表,通過navicat可以很友善的建表,然後通過python連接配接mysql資料庫,寫入資料。這是建立資料表的sql代碼:

CREATE TABLE `mood` (
`name` varchar(80) DEFAULT NULL,
`date` date DEFAULT NULL,
`content` text,
`comments_num` int(11) DEFAULT NULL,
`time` time DEFAULT NULL,
`tool` varchar(255) DEFAULT NULL,
`id` varchar(255) NOT NULL,
`sitename` varchar(255) DEFAULT NULL,
`pox_x` varchar(30) DEFAULT NULL,
`pox_y` varchar(30) DEFAULT NULL,
`isTransfered` double DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
           

其實到這裡爬蟲的主要的代碼就算完了,之後主要是通過QQ郵箱的聯系人導出功能,建構url清單,最後等着它運作完成就可以了。

這裡我單線程爬200多個好友用了大約三個小時,拿到了十萬條說說。下面是爬蟲的主體代碼:

#從csv檔案中取qq号,并儲存在一個清單中
csv_reader = csv.reader(open('qq.csv'))
friend=[]
for row in csv_reader:
    friend.append(row[3])
friend.pop(0)
friends=[]
for f in friend:
    f=f[:-7]
    friends.append(f)
headers={
'Host': 'h5.qzone.qq.com',
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0',
    'Accept': '*/*',
    'Accept-Language':'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
    'Accept-Encoding': 'gzip, deflate, br',
    'Referer': 'https://user.qzone.qq.com/790178228?_t_=0.22746974226377736',
    'Connection':'keep-alive'
}#僞造浏覽器頭
conn = MySQLdb.connect('localhost', 'root', '123456', 'qq_mood', charset="utf8", use_unicode=True)#連接配接mysql資料庫
cursor = conn.cursor()#定義遊标
cookie,gtk,qzonetoken=QRlogin#通過登入函數取得cookies,gtk,qzonetoken
s=requests.session()#用requests初始化會話
for qq in friends:#周遊qq号清單
    for p in range(0,1000):
        pos=p*20
        params={
'uin':qq,
        'ftype':'0',
        'sort':'0',
        'pos':pos,
        'num':'20',
        'replynum':'100',
        'g_tk':gtk,
        'callback':'_preloadCallback',
        'code_version':'1',
        'format':'jsonp',
        'need_private_comment':'1',
        'qzonetoken':qzonetoken
        }

        response=s.request('GET','https://h5.qzone.qq.com/proxy/domain/taotao.qq.com/cgi-bin/emotion_cgi_msglist_v6',params=params,headers=headers,cookies=cookie)
print(response.status_code)#通過列印狀态碼判斷是否請求成功
        text=response.text#讀取響應内容
        if not re.search('lbs', text):#通過lbs判斷此qq的說說是否爬取完畢
            print('%s說說下載下傳完成'% qq)
break
        textlist = re.split('\{"certified"', text)[1:]
for i in textlist:
            myMood=parse_mood(i)
'''将提取的字段值插入mysql資料庫,通過用異常處理防止個别的小bug中斷爬蟲,開始的時候可以先不用異常處理判斷是否能正常插入資料庫'''
            try:
                insert_sql = '''
                           insert into mood(id,content,time,sitename,pox_x,pox_y,tool,comments_num,date,isTransfered,name)
                           VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
                        '''
                cursor.execute(insert_sql, (myMood['id'],myMood["Mood_cont"],myMood['time'],myMood['idneme'],myMood['pos_x'],myMood['pos_y'],myMood['tool'],myMood['cmtnum'],myMood['date'],myMood["isTransfered"],myMood['name']))
                conn.commit()
except:
                pass
print('說說全部下載下傳完成!')
           

下面是爬取的資料,有100878條!

【實戰】用Python進行10w+QQ說說資料分析

拿到資料後,我先用sql進行聚合分析,然後通過ipython作圖,将資料可視化。

統計一年之中每天的說說數目,可以發現每年除夕這一天是大家發說說最多的一天(統計了2013到2017年)

【實戰】用Python進行10w+QQ說說資料分析

通過兩個輔助表,可以看到分年,分月,分小時段統計的說說數目,下面是代碼和資料圖:

【實戰】用Python進行10w+QQ說說資料分析

其餘的幾個圖代碼都是類似的,我就不重複發了。(其實主要是cmd裡面複制代碼太不友善了,建議大家用ipython notebook)

【實戰】用Python進行10w+QQ說說資料分析

額,可以看出2014年9月達到了一個高峰,主要是因為我的朋友大都是是2014年九月大學入學的,之後開始下降,這可能是好多人開始玩微信,逐漸放棄了QQ,通過下面這個年變化圖可以更直覺的看出:

【實戰】用Python進行10w+QQ說說資料分析
【實戰】用Python進行10w+QQ說說資料分析

通過這個每小時段說說發表的數目柱形圖,可以發現大家在晚上22點到23點左右是最多的,另外中午十二點到一點也有一個小高峰

tool發表說說用的工具這個字段的資料比較髒,因為發表工具可以由使用者自定義。最後我用Excel的内容篩選功能,做了一個手機類型的餅圖:

【實戰】用Python進行10w+QQ說說資料分析

通過這個餅圖可以看出使用最多的手機是蘋果,小米,魅族,華為這四個手機品牌。(這個結果有很大的因素是因為我的好友大多數學生黨,偏向于成本效益高的手機)

還有一個比較好玩的就是把經緯度資訊導出來,通過智圖位置智能平台可以生成一個地圖,這個地圖的效果還是非常好的(2000條資料免費,本來有位置資訊的說說有3500條,剔除了國外的坐标後我從中随機選了2000條) 

【實戰】用Python進行10w+QQ說說資料分析

因為涉及到個人隐私問題,這個地圖的連結就不分享了。

最後,通過将mood表中的content字段導出為txt文本檔案,利用python的jieba和wordcloud這兩個第三方庫,可以生成基于說說内容的詞雲。下面是代碼:

#coding:utf-8
import matplotlib.pyplot as plt
from wordcloud import WordCloud,ImageColorGenerator,STOPWORDS
import jieba
import numpy as np
from PIL import Image

#讀入背景圖檔
abel_mask = np.array(Image.open("qq.jpg"))

#讀取要生成詞雲的檔案
text_from_file_with_apath = open('mood.txt',encoding='utf-8').read()

#通過jieba分詞進行分詞并通過空格分隔
wordlist_after_jieba = jieba.cut(text_from_file_with_apath, cut_all = True)
stopwords = {'轉載','内容','em','評語','uin','nick'}
seg_list = [i for i in wordlist_after_jieba if i not in stopwords]
wl_space_split = " ".join(seg_list)
#my_wordcloud = WordCloud().generate(wl_space_split) 預設構造函數
my_wordcloud = WordCloud(
background_color='black', # 設定背景顔色
            mask = abel_mask, # 設定背景圖檔
            max_words = 250, # 設定最大現實的字數
            stopwords = STOPWORDS, # 設定停用詞
            font_path = 'C:/Windows/fonts/simkai.ttf',# 設定字型格式,如不設定顯示不了中文
            max_font_size = 42, # 設定字型最大值
            random_state = 40, # 設定有多少種随機生成狀态,即有多少種配色方案
                scale=1.5,
            mode='RGBA',
            relative_scaling=0.6
                ).generate(wl_space_split)

# 根據圖檔生成詞雲顔色
#image_colors = ImageColorGenerator(abel_mask)
#my_wordcloud.recolor(color_func=image_colors)

# 以下代碼顯示圖檔
plt.imshow(my_wordcloud)
plt.axis("off")
plt.show()

my_wordcloud.to_file("cloud.jpg")
           

下面是效果圖:

【實戰】用Python進行10w+QQ說說資料分析

不會ps,做的不是很美觀...

對于這個小demo,我總結了一以下的幾個問題:

  • 爬蟲沒有采用多線程和異步IO導緻效率太低。(主要是twisted這個庫不太懂,後面我可能會結合scapy這個架構,重寫這個爬蟲,利用他的twisted子產品加上異步IO的功能)
  • 對于python中的關于繪圖的,和資料分析的這幾個庫了解的不好,導緻資料可視化這塊做的不好。

- END -

本文為轉載分享&推薦閱讀,若侵權請聯系背景删除