1. 程式人生 > >愛情大資料 | 你的專屬微信聊天記錄統計

愛情大資料 | 你的專屬微信聊天記錄統計

微信聊天記錄資料分析

或許每個人的微信列表裡都有幾個不捨刪除的聊天記錄,經年累月,這些聊天記錄越積越多,終將成為你和這個人之間的美好回憶。這些回憶中有許多資訊值得挖掘,尤其是情侶之間,將這些資訊統計出來做一份“愛情報表”,一定會給自己的另一半帶去一份驚喜。

工作內容

  • 獲取微信聊天資料庫
  • 解密資料庫獲取聊天資料
  • 使用Python分析資料
  • ECharts(或者pyecharts)做資料視覺化

獲取聊天資料庫

這個教程主要是針對安卓手機使用者的,對於IOS系統,不太清楚如何獲取聊天資料庫,畢竟IOS系統生態非常封閉。安卓使用者獲取微信聊天資料庫的方法很簡單:

  • 首先需要獲取手機root許可權
  • 安裝RE檔案管理器(Root Explorer)
  • 開啟RE檔案管理器,在路徑/data/data/com.tencent.mm/MicroMsg下找到一個由32位字串命名的資料夾,比如我的資料夾如下,這個32位字串是mm+uin碼進行md5加密後得到的(uin碼後面會說)
    這裡寫圖片描述

  • 進入該資料夾,找到EnMicroMsg.db,即為你的微信聊天資料庫,將其複製出來並傳到電腦上

解密資料庫獲取聊天資料

解密微信資料庫,在網上有很多的教程,歸結起來就是,資料庫密碼=md5(IMEI+uin)[0:7],意思就是,將你手機的IMEI碼和你微信的uin碼加起來用md5加密後得到一串32位的字串,取其前7位即為資料庫的密碼。但是在操作的過程中會有一些坑。

  • 獲取IMEI碼,即開啟手機的撥號介面,輸入* #06#,這時就會自動彈出你的IMEI碼,但是對於雙卡使用者,操作的時候會有些懵,比如我用的小米6,輸入* #06#後會出來3個碼……其實也不用懵,MEID碼直接無視,下面兩個IMEI碼的話,一般都是取第一個,當然不放心的話可以兩個都試試。
    這裡寫圖片描述
  • 獲取uin碼,再次開啟RE檔案管理器,在路徑/data/data/com.tencent.mm/shared_prefs下找到auth_infokey_prefs.xml檔案並開啟,找到如下文字,其中value後面跟著的就是你的uin碼了,我的是8位,每個人uin碼的位數可能不一樣。
    這裡寫圖片描述
  • 在獲取到IMEI碼和uin碼之後就可以相加後使用md5加密得到密碼了。加密可以使用線上md5工具,密碼是小寫32位md5值的前7位。
    這裡寫圖片描述
  • 獲取到密碼後就是解密資料庫了。工具是sqlcipher.exe,我在網上找了很多版本,這個是最好用的:https://pan.baidu.com/s/1i7nGgS1(密碼:sz4u)。開啟後找到message表,這裡就是全部的聊天記錄了,關鍵的資訊就是createTime、talker以及content這三列了,值得注意的是,資料庫中儲存的時間資料是時間戳,並且每一個數據後面都多出了三位0,在進行資料分析時需要把這三個0剔除。然後我們依次點選File–>Export–>Table as CSV file,將message表匯出,我們就可以開始進行資料分析了。匯出的檔案預設有.csv和.txt兩種,字尾需要手動寫上去。此外,如果直接讀取這個檔案的話可能會存在編碼錯誤的問題,建議在儲存csv檔案以後,右鍵使用記事本開啟,然後再另存為一個新的檔案,編碼使用utf-8。
    這裡寫圖片描述
    這裡寫圖片描述

使用Python分析資料

呼……終於進入正題了。聊天記錄包含了非常豐富的資料,這裡我只做了兩個比較簡單的例子,一個是針對時間做聊天時間段分佈的統計;一個是針對內容做字元匹配,統計一些高頻詞彙出現的次數,比如“早安”、“晚安”、“想你”、“愛”等等(你懂的)。

讀取資料

使用pandas讀取csv檔案裡的資料,我們需要的資料在6、7、8三列:

chat = pd.read_csv('chat.csv', sep=',', usecols=[6,7,8])

然後定義兩個list,分別用來儲存聊天記錄的時間和內容,定義變數myGirl,賦值為想要提取出來的聯絡人的微訊號:

myGirl = 'wxid_xxxxxxxx'
chat_time = []
chat_content = []
for i in range(len(chat)-1):
    content = chat[i:i+1]
    if content['talker'].values[0] == myGirl:
        t = content['createTime'].values[0]//1000#除以1000用以剔除後三位0
        c = content['content'].values[0]
        chat_time.append(t)
        chat_content.append(c)

這時chat_time和chat_content裡就儲存著你和女(男)朋友所有的聊天資料了。接下來需要定義一個函式to_hour()將時間戳轉換為24h進位制,然後用列表推導式批量進行轉換:

def to_hour(t):
    struct_time = time.localtime(t)#將時間戳轉換為struct_time元組
    hour = round((struct_time[3] + struct_time[4] / 60), 2)
    return hour

hour_set = [to_hour(i) for i in chat_time]
繪圖

聊天時段的分佈可以使用seaborn繪製核密度圖,非常簡潔,一句話搞定,當然如果想讓圖片好看些的話還需要做額外的配置:

import seaborn as sns 
from matplotlib.font_manager import *#如果想在圖上顯示中文,需匯入這個包
myfont = FontProperties(fname=r'C:\Windows\Fonts\MSYH.TTC',size=22)#標題字型樣式
myfont2 = FontProperties(fname=r'C:\Windows\Fonts\MSYH.TTC',size=18)#橫縱座標字型樣式
sns.set_style('darkgrid')#設定圖片為深色背景且有網格線
sns.distplot(hour_set, 24, color='lightcoral')
plt.xticks(np.arange(0, 25, 1.0), fontsize=15)
plt.yticks(fontsize=15)
plt.title('聊天時間分佈', fontproperties=myfont)
plt.xlabel('時間段', fontproperties=myfont2)
plt.ylabel('聊天時間分佈', fontproperties=myfont2)
fig = plt.gcf()
fig.set_size_inches(15,8)
fig.savefig('chat_time.png',dpi=100)
plt.show() 

上面是我經常使用的一些配置方案。通過核密度圖可以看出,我和我女朋友屬於夜貓子型別的,經常在半夜聊天,23點至24點的峰值特別高。
這裡寫圖片描述
不過雖然“lightcoral”的配色非常少女,但是如果你的女(男)朋友並不瞭解資料分析和統計學的話,上面的圖片還是顯得有些過於“專業”了,可能只會得到一個“哦(冷漠臉)”字作為迴應。因此,一個合適的資料展示方式就顯得尤為重要了。

使用ECharts

ECharts(http://echarts.baidu.com/)是一個開源的js元件,繪製的圖表種類繁多而且很好看,使用起來也很方便,當然如果你並沒有接觸過js甚至對其有些抵觸,那麼也可以安裝pyecharts(https://github.com/pyecharts/pyecharts),可以看做是ECharts的Python版,操作更為簡潔。這裡我使用的是ECharts。首先我們需要在Python中統計hour_set裡不同時間段的聊天記錄條數,時間段可以任意劃分,然後開啟ECharts官網,找到官方例項中的雷達圖,將統計出的各時間段聊天資料直接替換進去就可以了:

option = {
    title: {
        text: '聊天時間段',
        textStyle: {
            color: '#000',
            fontSize: 20
        }
    },
    //toolbox配置項可以在網頁上直接生成下載圖片的按鈕而不用截圖
    toolbox: {
      show: true,
      feature: {
        saveAsImage: {
            show:true,
            excludeComponents :['toolbox'],
            pixelRatio: 2
        }
      }
    },
    tooltip: {},
    radar: {
        // shape: 'circle',
        name: {
            textStyle: {
                color: '#fff',
                backgroundColor: '#999',
                borderRadius: 3,
                padding: [3, 5]
           }
        },
        indicator: [
           { name: '凌晨2點至6點', max: 400},
           { name: '6點至10點次', max: 400},
           { name: '10點至14點', max: 400},
           { name: '14點至18點', max: 400},
           { name: '18點至22點', max: 400},
           { name: '22點至次日凌晨2點', max: 400}
        ]
    },
    series: [{
        name: '聊天時間段',
        type: 'radar',
        // areaStyle: {normal: {}},
        data : [
            {
                value : [63, 141, 250, 213, 263, 390],
                name : '聊天時段'
            }
        ]
    }]
};

這裡寫圖片描述
雷達圖的顯示方式比上面的核密度圖要友善的多,如果是在網頁上瀏覽的話效果更好,因為是可以動的。

統計特殊資料繪製字元雲

對於所有夜貓子型別的人來說,可能都會有深夜聊天的經歷,比如上面的雷達圖中,凌晨2點至清晨6點之間還存在少量的聊天記錄,那些不眠的夜晚你可能已經遺忘,但資料會幫你回憶起一切。我們定義一個deep_night列表,在統計hour_set中的時間時,將位於2-6之間的時間資料和對應的聊天資料存進deep_night中,然後將資料寫進Excel表中:

import xlwt
wbk = xlwt.Workbook()
sheet = wbk.add_sheet('late')
for i in range(len(deep_night)):
    sheet.write(i,0,time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(deep_night[i][0])))
    sheet.write(i,1,deep_night[i][1])
wbk.save('聊得很晚.xls')

然後再次祭出ECharts,進入官網,找到字元雲實例,將Excel中的深夜聊天內容直接替換上去就可以了。

option = {
    tooltip: {},
    series: [{
        type: 'wordCloud',
        gridSize: 20,
        sizeRange: [12, 50],
        rotationRange: [0, 45],
        shape: 'circle',
        textStyle: {
            normal: {
                color: function() {
                    return 'rgb(' + [
                        Math.round(Math.random() * 200),
                        Math.round(Math.random() * 200),
                        Math.round(Math.random() * 200)
                    ].join(',') + ')';
                }
            },
            emphasis: {
                shadowBlur: 10,
                shadowColor: '#333'
            }
        },
        data: [{
            name: '呵呵',
            value: 800,
            textStyle: {
                normal: {
                    color: 'black'
                },
                emphasis: {
                    color: 'red'
                }
            }
        }, {
            name: '別傻了,這是假資料',
            value: 618
        }, {
            name: 'ECharts好用',
            value: 438
        }, {
            name: '哈哈。。',
            value: 405
        }, {
            name: '點個贊',
            value: 246
        }, {
            name: '世界人民大團結萬歲',
            value: 224
        }, {
            name: 'naive',
            value: 189
        }, {
            name: '還得學習一個',
            value: 148
        }, {
            name: '悶聲發大財!',
            value: 111
        }, {
            name: '學習使我快樂',
            value: 965
        },  {
            name: '葡萄牙1:0!!!',
            value: 582
        }, {
            name: '+1s',
            value: 555
        }, {
            name: '兩點半的夜晚  哈哈哈哈',
            value: 550
        }]
    }]
};

這裡寫圖片描述
這個官方例項很簡單,字元雲的形狀是雜亂、隨機的,不過我們可以設定maskImage,即整個字元雲可以擺成maskImage的形狀,大家可以去社群裡找找別人做的方案(想想把你和女(男)朋友的聊天記錄用字元雲的形式擺成一個心形。。。還要啥自行車啊)。

匹配高頻字元

這一部分也不難,說白了就是對所有聊天記錄做一個字元的正則匹配。正則表示式在爬蟲中經常使用,雖然語法很多很繁雜,但是對於匹配字元來說就很簡單了,寫法很固定。比如我想匹配一句話中“愛”字出現的次數,定義一個pattern=’. * ?(愛). * ?’,這裡.*?代表匹配任意字元,“愛”字一定要打括號,然後呼叫re模組的findall()方法進行匹配,得到的結果是全由“愛”字組成的list,取其長度就是“愛”字出現的次數了:

import re
pattern = '.*?(愛).*?'
string = '我愛吃西瓜,你愛不愛吃。'
result = re.findall(pattern, string)
result
Out[5]: ['愛', '愛', '愛']
len(result)
Out[6]: 3

接下來我們要寫若干個pattern,即你想要匹配哪幾個詞,然後將這幾個pattern放進一個list中:

pattern_love = '.*?(愛).*?'
pattern_morning= '.*?(早安).*?'
pattern_night = '.*?(晚安).*?'
pattern_miss = '.*?(想你).*?'
pattern_set = [pattern_love, pattern_morning, pattern_night, pattern_miss]

最後寫一個迴圈巢狀,外層遍歷聊天內容,內層遍歷pattern_set:

start = datetime.datetime.now()
statistic = [0,0,0,0]
for i in range(len(chat_content)):
    for j in range(len(pattern_set)):
        length = len(re.findall(pattern_set[j], str(chat_content[i])))
        static[j] += length
result = {
        '愛': static[0],
        '早安': static[1],
        '晚安': static[2],
        '想你': static[3]
        }
print(result)
end = datetime.datetime.now()
print('\n..........\n字元統計結束,用時: {}\n............\n'.format(end-start))

用同樣的方法還可以匹配各種字元,字元越多時間越久,我在12萬多條記錄中匹配15個字元,大概耗時4分鐘,CPU為i7-6700K,在真正的做大資料分析的在時候,這一步是最耗時的,基本都需要進行分散式運算了。資料展示方面可以使用字元雲,不同字元的value值就是其出現的次數,出現次數較多的字元自然就會大一些。當然也可以學習前段時間網易雲音樂展示資料的方式:

這一年
你一共說了999次愛

你熱衷聊天
喜歡在午夜傾訴衷腸

11月11日大概是很特別的一天

這一天裡

你把“早安”

反覆說了11次(早安怪???)
……

呃呃,總之大家可以發揮自己的想象。如果想要寫的像網易雲音樂那樣詳細的話,就必須把聊天內容和聊天時間一一對應起來,而不能像上面的例子一樣將內容和時間分開討論。當然操作也很簡單,只需要定義一個二維list就可以了,第一維是時間,第二維是內容,不過資料庫中儲存的資料時間是混亂的,並不是按時間遞增的,所以我們還需要自己處理一下,也即以list第一維為物件進行排序帶動第二維跟著排序:

from operator import itemgetter
chat_all = []
myGirl = 'wxid_xxxxxxxx'
for i in range(len(chat)-1):
    content = chat[i:i+1]
    if content['talker'].values[0] == myGirl:
        t = content['createTime'].values[0]//1000
        c = content['content'].values[0]
        chat_all.append([t,c])
chat_all = sorted(chat_all, key=itemgetter(0))#以第一維為索引排序

這個時候chat_all裡就是按照時間順序排序好的資料了,剩下的就是按照上面的方法進行字元匹配等等操作,也可以挖掘其他好玩的資訊,比如結合機器學習演算法進行某些資訊的預測,對於時間序列資料使用LSTM網路進行預測再合適不過了,當然聊天行為屬於社會活動,想要做出更加準確的預測還需要涉及博弈……總之,對上面的一些資料分析來說,程式設計的難度並不大,主要是發揮自己的想象力,能夠在同樣的資料中挖掘出獨特且有意義的資訊是資料分析很重要的一點,同樣重要的還有資料的展示方式,畢竟多數人都是外貌協會,好看的展示方式才更得人心。完整程式碼如下github連結(https://github.com/SunGinous/WeChat_Analysis):

import pandas as pd
import time
import seaborn as sns 
import matplotlib.pyplot as plt
import datetime
import numpy as np
import re
from operator import itemgetter
from matplotlib.font_manager import *#匯入這個包,可以新增中文字型
import xlwt

chat_file = '自己的csv檔案'
myGirl = '目標聯絡人的微訊號'

''' 讀取原資料 '''
chat = pd.read_csv(chat_file, sep=',', usecols=[6,7,8])
chat_time = []
chat_content = []
chat_all = []
for i in range(len(chat)-1):
    content = chat[i:i+1]
    if content['talker'].values[0] == myGirl:
        t = content['createTime'].values[0]//1000
        c = content['content'].values[0]
        chat_time.append(t)
        chat_content.append(c)
        chat_all.append([t,c])
chat_all = sorted(chat_all, key=itemgetter(0))#以第一維為索引排序

''' 轉換時間格式 '''        
def to_hour(t):
    struct_time = time.localtime(t)
    hour = round((struct_time[3] + struct_time[4] / 60), 2)
    return hour
hour_set = [to_hour(i) for i in chat_time]

print('\n.......................\n開始畫圖\n.......................')
from matplotlib.font_manager import *#如果想在圖上顯示中文,需匯入這個包
myfont = FontProperties(fname=r'C:\Windows\Fonts\MSYH.TTC',size=22)#標題字型樣式
myfont2 = FontProperties(fname=r'C:\Windows\Fonts\MSYH.TTC',size=18)#橫縱座標字型樣式
sns.set_style('darkgrid')#設定圖片為深色背景且有網格線
sns.distplot(hour_set, 24, color='lightcoral')
plt.xticks(np.arange(0, 25, 1.0), fontsize=15)
plt.yticks(fontsize=15)
plt.title('聊天時間分佈', fontproperties=myfont)
plt.xlabel('時間段', fontproperties=myfont2)
plt.ylabel('聊天時間分佈', fontproperties=myfont2)
fig = plt.gcf()
fig.set_size_inches(15,8)
fig.savefig('chat_time.png',dpi=100)
plt.show() 
print('\n.......................\n畫圖結束\n.......................')

''' 聊天時段分佈 '''
print('\n.......................\n開始聊天時段統計\n.......................')
time_slice = [0,0,0,0,0,0]
deep_night = []
for i in range(len(hour_set)):
    if hour_set[i]>=2 and hour_set[i]<6:
        time_slice[0] += 1
        deep_night.append([chat_time[i], chat_content[i]])
    elif hour_set[i]>=6 and hour_set[i]<10:
        time_slice[1] += 1
    elif hour_set[i]>=10 and hour_set[i]<14:
        time_slice[2] += 1
    elif hour_set[i]>=14 and hour_set[i]<18:
        time_slice[3] += 1
    elif hour_set[i]>=18 and hour_set[i]<22:
        time_slice[4] += 1
    else:
        time_slice[5] += 1
labels = ['凌晨2點至6點','6點至10點','10點至14點',
          '14點至18點','18點至22點','22點至次日凌晨2點']
time_distribution = {
        labels[0]: time_slice[0],
        labels[1]: time_slice[1],
        labels[2]: time_slice[2],
        labels[3]: time_slice[3],
        labels[4]: time_slice[4],
        labels[5]: time_slice[5]
        }
print(time_distribution)

''' 深夜聊天記錄 '''
wbk = xlwt.Workbook()
sheet = wbk.add_sheet('late')
for i in range(len(deep_night)):
    sheet.write(i,0,time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(deep_night[i][0])))
    sheet.write(i,1,deep_night[i][1])
wbk.save('聊得很晚.xls')
print('\n.......................\n聊天時段統計結束\n.......................')

''' 字元統計 '''
print('\n..........\n開始字元統計\n............\n')
start = datetime.datetime.now()
pattern_love = '.*?(愛).*?'
pattern_morning = '.*?(早安).*?'
pattern_night = '.*?(晚安).*?'
pattern_miss = '.*?(想你).*?'
pattern_set = [pattern_love, pattern_morning, pattern_night, pattern_miss]
statistic = [0,0,0,0]
for i in range(len(chat_content)):
    for j in range(len(pattern_set)):
        length = len(re.findall(pattern_set[j], str(chat_content[i])))
        statistic[j] += length
result = {
        '愛': statistic[0],
        '早安': statistic[1],
        '晚安': statistic[2],
        '想你': statistic[3]
        }
print(result)
end = datetime.datetime.now()
print('\n..........\n字元統計結束,用時: {}\n............\n'.format(end-start))