美文网首页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