美文网首页Python学习资料整理Python
玩转Python发微信之itchat

玩转Python发微信之itchat

作者: 惑也 | 来源:发表于2019-06-16 01:11 被阅读80次

    一、说明

    • 适用环境:Python2.7、Python3及以上版本
    • itchat是一个开源的微信个人号接口,使用python调用微信从未如此简单
    • 使用不到三十行的代码,就可以完成一个能够处理所有信息的微信机器人
    • 先附上本文会用到的包,文章中不再做说明
    #-*- coding: utf-8 -*-
    import pandas as pd
    import numpy as np
    import plotly.offline as py                      # 保存图表,同于plotly.plotly as py,同时增加了离线功能
    py.init_notebook_mode(connected=True)            # 离线绘图时,需要额外进行初始化
    import plotly.graph_objs as go                   # 创建各类图表
    import plotly.figure_factory as ff               # 创建table
    from plotly import tools
    import matplotlib.pyplot as plt
    from pyecharts import options as opts            # 配置echarts
    from pyecharts.globals import ThemeType, SymbolType
    from pyecharts.charts import Geo, Map, Page
    from IPython.display import HTML, display
    
    import itchat    
    import jieba                                     # 中文分词   
     
    from googletrans import Translator               # 翻译
    translate = Translator().translate
    from scipy.misc import imread                    # 获取图片,读为np.array的三维数组
    from PIL import Image                            # 操作图片
    from wordcloud import WordCloud                  # 词云
    import os, math
    

    二、安装

    pip install itchat
    

    三、登录

    1. 登录

    itchat.auto_login(hotReload=True, loginCallback=li, exitCallback=lo) 
    
    • 弹出二维码,手机端微信扫描登录;
    • 参数hotReload=True,使程序关闭一定时间内,重新开启不用重新扫码,该方法会生成一个静态文件 itchat.pkl,用于存储登录的状态;
    • 定义登录成功和退出后调用的方法,并分别赋值给参数loginCallback 和 exitCallback。若不设置 loginCallback值,会自动删除二维码图片并清空命令行显示;
    def li(): print("Log In!")    # 定义登录成功的提示语
    def lo(): print("Log Out!")    # 定义退出的提示语
    

    2. 向文件助手发送消息

    send_str_1 = "您好吖,猜猜我是谁?\n其实,我是一个机器人儿"
    send_str_2 = "您好,机器人自动发消息\n收到了请忽略或任意回复"
    for s in [send_str_1, send_str_2]:
        itchat.send(msg=s, toUserName="filehelper")    
    
    • 效果示意图

    3. 退出登录

    if 1 == 1: itchat.logout()    # 使用if条件,可以避免输出ItchatReturnValue提示
    

    四、信息获取

    • 主要信息包括:好友、群聊、公众号、消息等,都是以字典的形式存放的;
    • 常见的操作函数:获取get_xxxx()、更新update_xxxx()、搜索search_xxxx()、添加add_xxxx()、删除delete_xxxx()、修改set_xxxx();
    • 本文仅以获取函数get_xxxx()为例,进行讲解:
    friends = itchat.get_friends(update=True)    # 获取通讯录好友列表
    contacts = itchat.get_contact(update=True)    # 获取保存到通讯录的微信群
    chatrooms = itchat.get_chatrooms(update=True)    # 获取近期活跃的微信群
    mps = itchat.get_mps()    # 获取关注的微信公众号
    
    • 获取的信息,返回的都是列表ContactList类,继承于Python内置的列表类型list;
    • 返回的列表,其中的元素类型是不同的,但最上层父类都是dict类型,根据字典的特点,信息的索引主要靠key。

    五、统计分析

    1. 数据处理

    # 获取微信好友信息
    key_list = ["UserName", "NickName", "RemarkName", "Sex", "Province", "City", "Signature", "HeadImgUrl"] # 好友指标
    df_friend = pd.DataFrame()
    for friend in friends:
        tmp = dict()
        for i in friend:
            if i not in key_list:
                continue        
            tmp[i] = friend[i]
        term = pd.DataFrame(tmp, index=[0])
        df_friend = df_friend.append(term, sort=False)
        
    df_friend = df_friend.reindex(columns=key_list)
    
    # 性别转化为中文
    df_friend['Sex'] = df_friend['Sex'].apply(lambda x: '男' if x == 1 else '女')
    
    # 将英文的省份和城市,翻译成中文,若为空时转化为未知
    df_friend["Province"] = df_friend["Province"].apply(lambda x: (x if not x.encode('UTF-8').isalpha() else translate(x, dest='zh-CN').text) if x != '' else '未知')
    df_friend["City"] = df_friend["City"].apply(lambda x: (x if not x.encode('UTF-8').isalpha() else translate(x, dest='zh-CN').text) if x != '' else '未知')
    

    2. 概括统计

    display(HTML(f"<h3 style='color: green'>微信好友数量:{len(friends)}</h3"))
    
    contacts_group_chat = [ql["NickName"] for ql in contacts]
    display(HTML(f"<h3 style='color: green'>保存到通讯录的群聊数量:{len(contacts)},&nbsp;&nbsp;具体如下:</h3"))
    for gc in contacts_group_chat: print(gc, end='  ')
    
    chatrooms_group_chat = [ql["NickName"] for ql in chatrooms if ql["NickName"] not in contacts_group_chat]
    display(HTML(f"<h3 style='color: green'>近期活跃的群聊数量:{len(chatrooms) - len(contacts)},&nbsp;&nbsp;具体如下:</h3"))
    for gc in chatrooms_group_chat: print(gc, end='  ')    # 排除已保存通讯录的群
    
    display(HTML(f"<h3 style='color: green'>关注的微信公众号数量:{len(mps)},&nbsp;&nbsp;具体如下:</h3"))
    for ql in mps: print(ql["NickName"], end='  ')
    
    • 结果如下

    3. 男女比例

    # 好友性别统计
    friend_sex = df_friend.groupby("Sex")["UserName"].count().to_frame().reset_index()
    trace = go.Pie(labels=friend_sex.Sex, values=friend_sex.UserName, hole=.4)
    layout = dict(width=850, height=450)
    fig = dict(data=[trace], layout=layout)
    py.iplot(fig)
    
    • 使用plotly包画图

    4. 地域分布

    # 好友地域分布
    friend_area = df_friend.groupby("Province")["UserName"].count().to_frame().reset_index().sort_values(by="UserName", ascending=False)
    trace = go.Bar(x=friend_area.Province, y=friend_area.UserName)
    layout = dict(width=900, height=450, xaxis = dict(tickangle = -45))
    fig = dict(data=[trace], layout=layout)
    py.iplot(fig)
    
    • 使用plotly包画图

    5. 地图分布

    # 好友地图分布
    china_province = pd.read_excel('province.xlsx')
    china_province["Province"] = china_province.province.apply(lambda x: x[0:3] if len(x) in (4, 6) else x[0:2])
    friend_area = friend_area[friend_area.Province.isin(china_province.Province)]    # 排除省份为空或国外的地区
    area = (Map(opts.InitOpts(width="900px", height="500px", theme=ThemeType.PURPLE_PASSION))
           .add("", [list(z) for z in zip(list(friend_area.Province), list(friend_area.UserName))], maptype="china")
           .set_global_opts(visualmap_opts=opts.VisualMapOpts(max_=int(np.max(friend_area.UserName)), range_text=["多", "少"], range_color=['#FFFF00', '#D6292B']))
           .set_series_opts(label_opts=opts.LabelOpts(is_show=True)))
    area.render_notebook()
    
    • 使用pyecharts包画图

    6. 签名词云

    # 好友签名词云
    background_image = imread("picture.jpeg")    # 词云背景图,自选,本文选心形
    sign_str = ''.join(df_friend.Signature.tolist())    # 签名转化为list
    
    # 仅选取汉字、大小写英文字母,其它的一概忽略
    jb_str = ''
    for s in sign_str:
        if s.isspace() or (s >= u'\u4e00' and s <= u'\u9fa5') or (s >= u'\u0041' and s <= u'\u005a') or (s >= u'\u0061' and s <= u'\u007a'):
            jb_str += s
    
    # 使用结巴分词,对签名文字进行分词,分词后排除掉单字,因为单字词云无意义     
    cut_str = ' '.join([jb for jb in jieba.cut(jb_str) if len(jb) > 1])
    
    # 使用wordcloud包制作词云,可能需要单独安装汉字字体
    wordcloud = WordCloud(font_path="/System/Library/Fonts/STHeiti Medium.ttc", scale=4, background_color="white", mask=background_image).generate(cut_str)
    fig = plt.gcf()
    fig.set_size_inches(18, 12)
    plt.imshow(wordcloud, interpolation="lanczos")
    plt.axis("off")
    plt.show()
    
    • 制作词云,自选背景图片,因包含汉字需要进行分词;
    • 使用wordcloud包制作词云,可能需要单独安装汉字字体;
    • 使用matplotlib包画图


    7. 头像拼图

    # 创建文件夹user_image,存储所有好友头像照片
    try:
        os.mkdir("user_image")
    except FileExistsError:
        pass
    
    # 下载好友头像,并存储
    image_dir = "./user_image/"
    for k, friend in enumerate(friends):
        image_name = str(k)+'.jpg'
        user_name = friend["UserName"]
        img = itchat.get_head_img(userName=user_name)
        with open(image_dir + image_name, 'wb') as file:
            file.write(img)
    
    # 根据好友数量,设计头像拼图的最佳行列数
    phone_width = 200
    phone_height = 200
    pic_list = [path for path in os.listdir(image_dir) if path.endswith(".jpg")]    # 头像照片的文件名列表
    pic_num = len(pic_list)
    array = int(math.sqrt(pic_num))
    if array != math.sqrt(pic_num):
        if abs(array * array - pic_num) > abs((array + 1) * (array + 1) - pic_num):
            array = array + 1
        tem = dict()
        for temp in zip(range(1, array + 1)[::-1], range(array + 1, array * 2 + 1)):
            term = abs(temp[0] * temp[1] - pic_num)
            if term in tem:
                continue
            else:
                tem[term] = temp
        row_col = tem[min(tem)]
    else:
        row_col = (array, array) 
    row_num = row_col[0]
    col_num = row_col[1]
    
    # 创建底图,宽为:列数*200px,高为:行数*200px
    to_image = Image.new("RGBA", (phone_width * col_num, phone_height * row_num))
    
    # 循环粘贴每一个头像照片
    n = 0
    for i in range(0, row_num):
        for j in range(0, col_num):
            if n == pic_num:
                break
            image_any = Image.open(image_dir + pic_list[n])    # 读取头像照片
            image_any_size = image_any.resize((phone_width, phone_height))    # 设置照片大小(200px*200px)
            loc = (j * phone_width, i * phone_height)    # 计算照片粘贴的位置
            to_image.paste(image_any_size, loc)    # 粘贴照片
            n += 1
            
    # 展示好友头像拼图
    fig = plt.gcf()
    fig.set_size_inches(16, 9)
    plt.imshow(to_image, interpolation="lanczos")
    plt.axis("off")
    plt.show()
    
    • 头像拼图时的行列个数,设计思路:对好友总数开根号,若正好能开根号,则为行列个数,一般都开不尽,处理办法是寻找行列数都最优,保证拼图时空出来的位置最少

    • 具体思路
      1)好友总数M开根号取整后为n
      2)确认n*n(n+1)*(n+1)谁离总数更近(差值小),哪个值近则以该值为准;
      3)计算一些列值:n*(n+1) - M、(n-1)*(n+2)-M、(n-2)*(n+3)-M.......
      4)忽略正负号,求得差值最小时,对应的两个数,即为最优行列数
      5)特别地,当差值最小时,存在多个对应关系时,取最后的一对
      6)具体思路见代码,此处有疑问欢迎交流

    • 使用matplotlib包画图

    六、发送消息

    • 推荐使用send方法,可以实现大多数发送需求
    • 方法: send(msg="Message", toUserName=None)
    • 参数
      文字消息使用字符串
      文件使用关键字@fil@
      图片使用关键字@img@
      视频使用关键字@vid@
    • demo
    if 1==1: itchat.send(msg="Python_data_analysis", toUserName="filehelper")    # 发送文字
    if True: itchat.send(msg="@fil@%s" % "./Python.txt", toUserName="filehelper")    # 发送文件
    if True: itchat.send(msg="@img@%s" % "./picture.jpeg", toUserName="filehelper")    # 发送图片
    if True: itchat.send(msg="@vid@%s" % "./Tableau_introduction.mp4", toUserName="filehelper")    # 发送视频
    
    • 效果图

    七、自动回复

    • 实现原理:当收到微信好友发来的消息时,我们将这个消息传给图灵机器人API,它会根据消息做出答复,然后我们将这个消息返回给微信好友;
    • 图灵官网注册账号,获取机器人API的key值。不过,从2019-06-05开始,图灵平台对个人用户的调用,做了限制。未认证的用户不能调用了,个人认证后,调用次数为100次/天,不太好玩了;
    • 实现步骤
      定义一个从图灵机器人获取返回结果的函数
      调用itchat封装好的装饰器
      定义自动回复函数:可以指定聊天好友、设置默认回复
      运行itchat.run()函数,开启自动回复
    • 代码
    def from_turing_reply(msg):
        """从图灵机器人获取回复内容"""
    
        api_url = 'http://www.tuling123.com/openapi/api'
        data = {
            "key": "64496a7591a3450682188885461fc328",
            "info": msg,
            "user_id": "Robot"
        }
        try:
            r = requests.post(api_url, data=data).json()
            return r.get("text")
        except:
            return ''
    
    @itchat.msg_register(itchat.content.TEXT)
    def text_reply(msg):
        """自动文本回复"""
        
        default_reply = "我陪主人去月球了,明天再陪你聊!"
        if msg["FromUserName"] in ["@07c0724e5bb70a6e99651277c16daa66"]:  # 指定好友
            reply = from_turing_reply(msg["Text"])
            if "当天请求次数已用完" in reply:
                reply = default_reply
            return reply or default_reply
    
    itchat.run()
    

    相关文章

      网友评论

        本文标题:玩转Python发微信之itchat

        本文链接:https://www.haomeiwen.com/subject/nxivgqtx.html