小蛇学python(3)两百行代码实现微信好友数据爬取与可视化

前段时间发现了一个好玩的东西,一个python的第三方库itchat,它的功能很强大。只要你扫一下它所生成的二维码即可模拟登陆你的微信号,然后可以实现自动回复,爬取微信列表好友信息等功能。基于这个第三方库,写了个两百行的代码。

废话不多说,先贴代码。这份代码复制粘贴就可以用,不过要注意改一下代码中所设置的文件路径。

python3+mysql+pycharm开发环境

代码

#!/usr/bin/env python3
#_*_ coding:utf-8 _*_
#__author__='跌跌撞撞小红豆'
#__date__ = '2018-03-18'
import itchat
import pymysql.cursors
from matplotlib import pyplot as plt
import numpy as np
import ch
import jieba.analyse

Nickname1 = []
Remarkname1 = []
Sex1 = []
Province1 = []
Signature1 = []
province_distribution = []

db = 'new_schema'

Province_dict = {'北京': 0, '上海': 0, '天津': 0, '重庆': 0, '黑龙江': 0, '吉林': 0, '辽宁': 0, '内蒙古': 0, '河北': 0,
                 '山东':0, '河南':0, '安徽':0,'江苏':0, '浙江':0, '福建':0, '广东':0, '湖南':0, '湖北':0, '江西':0,
                 '宁夏':0, '甘肃':0, '新疆':0, '西藏':0, '青海':0, '四川':0,'云南':0, '贵州':0, '广西':0, '山西':0,
                 '陕西':0, '海南':0, '台湾':0 }
Province_tuple = ['北京', '上海', '天津', '重庆', '黑龙江', '吉林', '辽宁', '内蒙古', '河北',
                 '山东', '河南', '安徽','江苏', '浙江', '福建', '广东', '湖南', '湖北', '江西',
                 '宁夏', '甘肃', '新疆', '西藏', '青海', '四川','云南', '贵州', '广西', '山西',
                 '陕西', '海南', '台湾']


def login():
    itchat.auto_login()

def crawl():
    male = 0
    female = 0
    unknow = 0


    for friend in itchat.get_friends(update=True)[0:]:
        #可以用此句print查看好友的微信名、备注名、性别、省份、个性签名(1:男 2:女 0:性别不详)
        print(friend['NickName'],friend['RemarkName'],  friend['Sex'],   friend['Province'],   friend['Signature'])

        Nickname1.append(friend['NickName'])
        Remarkname1.append(friend['RemarkName'])
        Sex1.append(friend['Sex'])
        Province1.append(friend['Province'])
        Signature1.append(friend['Signature'])

        img = itchat.get_head_img(userName=friend["UserName"])
        path = "C:/HeadImages_zyh/"+friend['NickName']+"("+friend['RemarkName']+").jpg"
        try:
            with open(path,'wb') as f:
                f.write(img)
        except Exception as e:
            print(repr(e))

        if friend['Sex'] == 1:
            male += 1
        else:
            if friend['Sex'] == 2:
                female += 1
            else:
                unknow += 1

    print('您的微信账号里有男性:' + str(male) + '  女性:' + str(female) + '  性别不知者: ' + str(unknow))

def store_mysql():
    print('~~~~~~~~~~~~~~~~~~~~~~~~~~连接数据库~~~~~~~~~~~~~~~~~~~~~~~~~~~~`')
    connection = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='123456', db=db,
                                 charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor)
    # 通过cursor创建游标
    cursor = connection.cursor()
    print('~~~~~~~~~~~~~~~~~~~~~~~~~连接数据库成功!正在写入数据,请等待~~~~~~~~~~~~~~~~~~~~~~~~~~~~`')

    for i in range(len(Remarkname1)):
        sql = '''INSERT INTO `my_friends` (`Nickname`, `Remarkname`, `Sex`, `Province`, `Signature`) VALUES ("%s", "%s", "%d", "%s", "%s")''' % (Nickname1[i],Remarkname1[i],Sex1[i],Province1[i],Signature1[i])
        cursor.execute(sql)

    # 提交SQL
    connection.commit()
    cursor.close()
    connection.close()

def select_sex():
    print('~~~~~~~~~~~~~~~~~~~~~~~~~~连接数据库~~~~~~~~~~~~~~~~~~~~~~~~~~~~`')
    connection = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='123456', db=db,
                                 charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor)
    # 通过cursor创建游标
    cursor = connection.cursor()
    print('~~~~~~~~~~~~~~~~~~~~~~~~~连接数据库成功!正在提取数据,请等待~~~~~~~~~~~~~~~~~~~~~~~~~~~~`')

    sql = "select (id) from `my_friends`"
    cursor.execute(sql)
    sum = len(cursor.fetchall())
    print("共计数据" + str(sum) + "条")
    global man, woman, unknown
    man = woman = unknown = 0
    for i in range(sum):
        sql = "select Sex from `my_friends` where id =" + str(i+1)
        cursor.execute(sql)
        dict = cursor.fetchone()
        print(dict)

        if dict['Sex'] == 1:
            man  += 1
        if dict['Sex'] == 2:
            woman += 1
        if dict['Sex'] == 0:
            unknown += 1
    print(man,woman,unknown)
    print(1)


    connection.commit()
    cursor.close()
    connection.close()

def figure_sex():
    fig = plt.figure(1)
    ax1 = plt.subplot(111)
    data = np.array([man, woman, unknown])
    width = 0.5
    x_bar = np.arange(3)
    rect = ax1.bar(left=x_bar, height=data, width=width, color="lightblue")
    for rec in rect:
        x = rec.get_x()
        height = rec.get_height()
        ax1.text(x + 0.2, 1.02 * height, str(height))
    ax1.set_xticks(x_bar)
    ax1.set_xticklabels(("man", "woman", "unknown"))
    ax1.set_ylabel("number")
    ax1.set_title("your friend's sex distribution")
    ax1.grid(True)
    ax1.set_ylim(0, 200)
    plt.show()

def select_province():
    andso_on = 0
    overseas = 0
    total = 0
    print('~~~~~~~~~~~~~~~~~~~~~~~~~~连接数据库~~~~~~~~~~~~~~~~~~~~~~~~~~~~`')
    connection = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='123456', db=db,
                                 charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor)

    cursor = connection.cursor()
    print('~~~~~~~~~~~~~~~~~~~~~~~~~连接数据库成功!正在提取数据,请等待~~~~~~~~~~~~~~~~~~~~~~~~~~~~`')

    sql = "select (Province) from `my_friends`"
    cursor.execute(sql)
    sum = len(cursor.fetchall())
    print("共计数据" + str(sum) + "条")
    for i in range(sum):
        sql = "select Province from my_friends where id =" + str(i+1)
        cursor.execute(sql)
        dict = cursor.fetchone()

        province = dict['Province']

        if province in Province_tuple:
            Province_dict[province] += 1
        if province == '':
            overseas += 1
        if province != ''and province not in Province_tuple:
            andso_on += 1
    #print(Province_dict)    真没想到可以如此简单的就成功计数好友在每个省的分布,是自己写程序无意中想到的,之前一直思考都没有这个灵光闪现。可见有时候编代码和做数学题一样,不能只看,要边动手边想!!!
    for i in range(32):
        province_distribution.append(Province_dict[Province_tuple[i]])

    province_distribution.append(overseas)
    province_distribution.append(andso_on)
    for i in range(len(province_distribution)-2):
        total = total + province_distribution[i]

    print(province_distribution)
    connection.commit()
    cursor.close()
    connection.close()

def figure_province():
    Province_tuple.append('unknow')
    Province_tuple.append('abroad')
    fig = plt.figure(1)
    ax1 = plt.subplot(111)
    data = province_distribution
    width = 0.5
    x_bar = np.arange(len(province_distribution))
    rect = ax1.bar(left=x_bar, height=data, width=width, color="lightblue")
    for rec in rect:
        x = rec.get_x()
        height = rec.get_height()
        ax1.text(x + 0.1, 1.02 * height, str(height))
    ax1.set_xticks(x_bar)
    ax1.set_xticklabels(Province_tuple)
    ax1.set_ylabel("number")
    ax1.set_title("my friend's location distribution")
    ax1.grid(True)
    ax1.set_ylim(0, 100)
    plt.show()

def judge_chinese(word):
    cout1 = 0
    for char in word:
        if ord(char) not in (97, 122) and ord(char) not in (65, 90):
            cout1 += 1
    if cout1 == len(word):
        return word

def jiebaSet(strs):

    tags = jieba.analyse.extract_tags(strs, topK=100, withWeight=True)
    for item in tags:
        print(item[0] + '\t' + str(int(item[1]*1000)))


def select_signature():
    cout = 0
    Sig = []
    strs = ''

    print('~~~~~~~~~~~~~~~~~~~~~~~~~~连接数据库~~~~~~~~~~~~~~~~~~~~~~~~~~~~`')
    connection = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='123456', db=db,
                                 charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor)

    cursor = connection.cursor()
    print('~~~~~~~~~~~~~~~~~~~~~~~~~连接数据库成功!正在提取数据,请等待~~~~~~~~~~~~~~~~~~~~~~~~~~~~`')

    sql = "select (id) from `my_friends`"
    cursor.execute(sql)
    sum = len(cursor.fetchall())

    for i in range(sum):
        sql = "select Signature from `my_friends` where id =" + str(i + 1)
        cursor.execute(sql)
        dict = cursor.fetchone()
        if dict['Signature'] == '':
            cout += 1
        else:
            Sig.append(dict['Signature'])

    print('有' + str(cout) + '人没有写个性签名')

    for word in Sig[0:]:
        judge_chinese(word)
        if judge_chinese(word) != None:
            print(judge_chinese(word))
            strs += word

    jiebaSet(strs)


login()
crawl()
itchat.run()
store_mysql()
select_sex()
figure_sex()
ch.set_ch()
select_province()
figure_province()
select_signature()


接下来我们开始一点点分析这些代码,以及分享一下我写代码过程中遇到的困难,坑和感受

登陆

第一个函数login()没有什么好说的,这是itchat封装好的,调用后可以直接生成一个二维码,这个二维码其实就是你平时登陆web微信时候的二维码,你用手机微信扫这个二维码后,程序会模拟成你登陆进微信,进而对微信进行一系列操作。

爬取

crawl()所实现的功能是爬取你微信列表所有好友的信息,这些信息包括头像,昵称,备注,性别,所在地以及个性签名(昵称和个性签名里有微信表情真的是让人头疼,这个后面再说)以及爬取后储存头像在指定路径和可视化微信好友男女比例的功能。


微信好友性别比例.png

唉,没想到我虽然是个汉子,好友列表里竟然女生比男生多,而且竟然还有这么多个不知道性别的。

path = "C:/HeadImages_zyh/"+friend['NickName']+"("+friend['RemarkName']+").jpg"

这行代码所描述的就是指定的存储微信头像的路径。

因为频繁爬取微信会让腾讯把你web端微信给封掉,所以我想把所爬取的数据存储在mysql数据库中,这样以后分析数据可以直接从数据库里提取,而不需要一直现爬现分析。

链接数据库

store_mysql()就是实现这样一个功能。只需要简单学习几句sql语句就可以满足本程序的使用。

在这里给大家提供几个常用的并且在这个程序中使用到了的sql语句,供大家查阅研究使用。

truncate table  表名;#删除表中所有数据

create table  表名
(id int(10) not null primary key auto_increment) #给表增加索引,而且没插入一条数据索引自增1

alter table 表名 add 字段名 字段类型 

数据库操作是一门表面看起来简单,其实很繁杂且容易出错的学问。举个例子吧。

a='', b=' ', c=null

请问将这三个变量插入数据库会有什么不同呢?答案告诉大家,c是不占内存的,意味着什么也没有,好比真空。a呢,它是个空字符串,但是它是有内存地址的,只不过这个字符串是空的,好比我们生存空间里的空气,看不见摸不着,其实它是占用空间的。b也是个字符串,但它不是空的,而是只有一个空格字符的字符串。

是不是听晕了?哈哈,仔细体会,编程中有许多这样小的细节,不注意区分就会产生你很难察觉的bug。

我在将信息插入数据库的时候,却遇到了一个大麻烦。mysql数据库默认编码是utf8,然后我所要插入信息中的微信昵称,微信个性签名里存在的大量表情是由utf8mb4支持的,utf8mb4是utf8的一个超集。所以我需要更改数据库默认编码才行。

给大家科普一下我们在编程过程中经常遇到的编码问题。到底经常报错的utf8,gb2312,unicode都是些什么东西呢?

最先美国人发明计算机时只把26个英文字母大小写,标点符号,数字等进行了编码,这就是大家C语言入门时都知道的ASCII码。后来计算机普及,中国人为了能让汉字可以表示,就改动了ASCII码,并加入了常用汉字数千个,这就是GB2312。

再后来,满文,日文,藏文等等又不停的加入,一些不常用的汉字(后来发现必须加,因为很多名人的名字都是不常用的汉字等等情况)也加入了进去,GB2312就发展成为了GB18030。

到了最后阶段,信息时代来了,每个国家都知道要抓住机遇,大力发展互联网,因此每个国家对自己文字的编码便应运而生。当真时百花齐放百家争鸣。我们在编程中光是英汉转换就已经够烦的了,难道出差去埃萨俄比亚还要转成埃萨俄比亚的编码嘛?于是一个国际组织就把全世界所有已知的字符符号海纳百川,全部包括进一种编码,这就是unicode。

以上这些从通信角度说都是信源编码,而utf8和utf8bm4都是信道编码。他们包含了一些压缩编码的规则以便在传输中最大限度的降低空间占用率。

接下来讲述一下我在更改数据库默认编码时候遇到的问题。从网上搜索,说是要更改一个叫做my.ini的文件,它涉及到数据库刚刚启动时候的初始化。结果我第一步就卡了很久,就是我怎么也找不到这个文件。费了很大劲才发现它原来在隐藏文件夹ProgramData中,至于如何让隐藏文件夹出现,自行百度吧。

我是如何发现它在隐藏文件夹中的呢?

控制面板->管理工具->服务里,找到你正在运行的mysql服务,右键点击属性,然后就会发现一个路径,这个路径就是mysql启动时候初始化配置文件my.ini的路径。之所以说它是因为它很重要,不同版本的mysql或者不同的系统这个文件位置是不一样的,需要到这里来找到它。


mysql57所在位置
my文件的路径.png

找到文件后更改相应位置的内容就可以了。

如果想查看自己的更改是否有效果,可以以管理员权限启动命令行然后进行如下操作。


命令行操作1.png

命令行操作2.png

如上图所示就对了。

然后紧接着就是重新启动mysql,你更改了配置必须要重新启动才可以生效。可是我在重新启动这个地方真的是遇到了一个大坑,可是说出来也好笑。

mysql下载下来后是有两个服务进程的,一个叫MySQL,一个叫MySQL57。而且在控制面板之前那个服务里我们看到,MySQL叫做本地系统,MySQL57是网络服务。我想当然的认为MySQL是我现在存储数据所用的服务进程。

执行命令net stop mysql无效,于是我又从网上查了各种攻略,又把mysql和mysql workbench安装删除了好几遍,真可谓是精通mysql的安装与删除了才发现原来我一开始服务进程都搞错了,直接输入net stop mysql57就可以了。

本以为可以出现奇迹,结果还是存储失败了。又是一顿百度,找到了一个比较完美的答案。

我们还需要继续实验,仔细看修改配置文件里init_connect='SETNAMES utf8mb4',那我可以拿来用到数据库中。果然,发现加上“SET NAMES utf8mb4;”这句以后,数据库变成了utf8mb4.:在python + mysqldb 或者 python +sqlalchemy两种模式都适用。创建连接以后,游标对象首先要执行一遍“SET NAMES utf8mb4;”这样就能保证数据库连接是以utf8mb4编码格式连接,数据库也就变成utf8mb4的啦。前面都是铺垫的内容,如果没有这一步,前面都是白做的。

数据可视化

存储到数据库成功以后,接下来要做的就是如何从数据库中提取消息,并加以利用。我主要对信息进行了两个方面的利用,一个是将所有好友的所在地进行分析,并做成条形图。一个是将所有好友的个性签名进行中文分词,然后做成词云来观察我好友都在说些什么?

程序的逻辑很清晰,select_province所做的是从数据库中提取信息,figure_province是利用画图。这个功能难度不大,不过涉及到一个细节,就是我们拿到了每个好友的所在地信息,难道要每次都判断一遍他们是不是在我国三十多个行政区嘛?这样无疑太浪费时间,无论是开发效率还是运行效率都不高。

于是我巧用字典,然后解决了需要频繁判断的问题。

首先分别初始化自定义一个字典和一个列表。

Province_dict = {'北京': 0, '上海': 0, '天津': 0, '重庆': 0, '黑龙江': 0, '吉林': 0, '辽宁': 0, '内蒙古': 0, '河北': 0,
                 '山东':0, '河南':0, '安徽':0,'江苏':0, '浙江':0, '福建':0, '广东':0, '湖南':0, '湖北':0, '江西':0,
                 '宁夏':0, '甘肃':0, '新疆':0, '西藏':0, '青海':0, '四川':0,'云南':0, '贵州':0, '广西':0, '山西':0,
                 '陕西':0, '海南':0, '台湾':0 }
Province_tuple = ['北京', '上海', '天津', '重庆', '黑龙江', '吉林', '辽宁', '内蒙古', '河北',
                 '山东', '河南', '安徽','江苏', '浙江', '福建', '广东', '湖南', '湖北', '江西',
                 '宁夏', '甘肃', '新疆', '西藏', '青海', '四川','云南', '贵州', '广西', '山西',
                 '陕西', '海南', '台湾']

字典的key是我国三十多个行政区的名字,而值所代表的是我朋友所在这些地方的个数,初始化为0。数据库里是以{province :'所在地'}的形式存储的,所以利用dict['province']来判断该字典值是否在Province_tuple这个列表里,如果在的话Province_dict[province] += 1。画图的时候直接取出字典值画图就可以了。(不知道表达的是否清晰,尽力了啊,也需要大家仔细看,自行体会。)


微信好友所在地分布.png

从图里可以很直观的看出来,我的朋友在北京,山东,安徽分布最多。人对图像是最敏感的,所以想要成为一个很好的数据分析师要有很强大的数据可视化能力,这知识数据可视化的冰山一角啦。

最后还有一个词云的功能,我引入了jieba分词包加上自己处理,将所有个性签名分成一个一个词,包括英文,日文特殊符号等等。


词云1.png

词云2.png

词云3.png

可以看出来,自由,生活,自我这些词是在我朋友个性签名中最频繁使用的,这不正是90后张扬自我的标签嘛?

是不是挺有趣,心动不如行动,自己实验一下吧。

PS

有人反映其中ch.py这个包安装不了。解释一下,这个包是我自己写的,所以当然安装不了,而且我忘了把这个包公布。其实这个包不涉及程序逻辑,这个包的功能就是解决一些图像显示的问题。

以下是代码

#-*-coding:utf-8-*-
#文件名: ch.py
def set_ch():
    from pylab import mpl
    mpl.rcParams['font.sans-serif'] = ['FangSong'] # 指定默认字体
    mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题</span>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,563评论 25 707
  • 梦海蓉阅读 455评论 0 1
  • .gitignore的用法 在日常的开发中,当我们需要将一个项目提交到Git时,并不是所有的文件都需要提交,比如一...
    WeekDiffculty阅读 312评论 0 1
  • 今日有幸跟随领导走访全息VIP会员单位亿佳门业,精明干练是我对吴总的第一印象,吴总非常热情,落座后直接了当,从人员...
    吕明超阅读 123评论 0 0