Python 分析在德的中国程序员,告别 996?(2)

开发需求

  • 获取群聊群成员信息
  • 找出所有群昵称不符合标准的群友
  • 随机抽取5人,在群里发布改昵称提醒消息
  • 同时将这次提醒的5人,存储进数据库
  • 每天早八点晚八点两次定时启动昵称检查脚本
  • 某人在将来被提醒次数超过10次,还不予配合不改昵称时,将自动踢出群
  • 新群友被邀请进入群时,立刻发送群规提示改昵称

开发分解

该任务所需第三方库如下:

pip3 install wxpy
pip3 install apscheduler
pip3 install pymysql
pip3 install DBUtils

1. 建库建表

本文采用的是MySQL,后期可以扩展支持Postgre或者MongoDB。

因为需要存储微信表情字符集,所以表的默认编码采用utf8mb4_unicode_ci。

DROP TABLE IF EXISTS `wx_chat_group`;
CREATE TABLE `wx_chat_group` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` VARCHAR(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
 PRIMARY KEY `id` (`id`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COLLATE utf8mb4_unicode_ci;
INSERT INTO `wx_chat_group` (`id`, `name`) VALUES (1, '德国IT职业信息分享群');
-- 每次抽取的不合规格的昵称将存储如表以供计数
DROP TABLE IF EXISTS `wx_chat_nickname_check`;
CREATE TABLE `wx_chat_nickname_check` (
 `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
 `group_id` int(9) UNSIGNED NOT NULL,
 `wx_puid` VARCHAR(16) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
 `nickname` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
 `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time',
 PRIMARY KEY `id` (`id`),
 INDEX `idx_group_id` (`group_id`),
 INDEX `idx_create_time` (`create_time`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COLLATE utf8mb4_unicode_ci;

2. 用户设置

所有用户自定义变量存入conf文件里,如群名、临时存储路径、数据库接入信息,踢人阈值:

[wechat]
group_name_1=德国IT职业信息分享群
group_id_1=1
path_tmp=/opt/tmp/
notice_random=5
kick_max=10
tuling_api_key=xxxxx
[mysql]
mysql_host=localhost
mysql_port=3306
mysql_user=root
mysql_pwd=xxxx
mysql_database=wechat_group_ibot

3. 监听群消息

初始化群聊对象,并且监听群消息

# 查找群聊,并且设置附加属性,以备后用
def init_group(group_name, group_id):
 group = ensure_one(bot.groups().search(group_name))
 group.ext_attr = lambda: None
 setattr(group.ext_attr, 'group_id', group_id)
 setattr(group.ext_attr, 'group_name', group_name)
 return group
# 初始化微信机器人bot
bot = Bot(cache_path=True, console_qr=True)
# unique chat person's id
bot.enable_puid()
# 读取自定义参数
cf = configparser.ConfigParser()
cf.read('wechat.conf')
group_name_1 = cf.get('wechat', 'group_name_1')
group_id_1 = cf.get('wechat', 'group_id_1')
# 初始化群聊对象
group_1 = init_group(group_name_1, group_id_1)
# 监听类型为NOTE的群消息,如:"aa"邀请"bbb"加入了群聊
@bot.register(group_1, NOTE)
def welcome_for_group(msg):
 try:
 new_member_name = re.search(r'邀请"(.+?)"|"(.+?)"通过', msg.text).group(1)
 except AttributeError:
 return
 group_1.send(welcome_text.format(new_member_name, space_after_chat_at))
# 保持bot持续运行
bot.join()

4. 昵称检查

检查群友昵称,存入数据库并且发送提醒, 具体逻辑代码这里不予累述。

def check_nickname(nickname):
 # 正则检验群昵称是否标准
 if re.match(r'([一-龥]|[ -~]|[sS])+|([一-龥]|[ -~])+|([一-龥]|[ -~])+', nickname):
 return True
 else:
 return False
......
# 检查群友昵称
def process_group_members(group):
 # 每次检查前先刷新群成员信息,避免用户改了昵称后再次被提醒
 # 但刷新会改变成员临时的内部puid,所以检查昵称必须同时结合puid和nickname
 group.update_group(members_details=False)
 ......
 for member in group:
 nickname = member.name
 wx_puid = member.puid
 if not check_nickname(nickname):
 invalid_member = GroupMember(nickname, wx_puid, 0)
 invalid_members.append(invalid_member)
 .....
 # 随机抽取不合格的5人
 random_members = random.sample(invalid_members, k=5)
 ......
# 将本次提醒群友存入数据库,供下次计数
def insert_invalid_name(group_id, wx_puid, nickname):
 bot_db.execute("INSERT INTO wx_chat_nickname_check (`group_id`, `wx_puid`, `nickname`)"
 " VALUES (%s, %s, %s)",
 (group_id, wx_puid, nickname))
# 获取昵称不合规群友被提醒计数
def get_invalid_name_count(group_id, wx_puid, nickname):
 result = bot_db.get_count("SELECT id FROM wx_chat_nickname_check "
 "WHERE group_id = %s and (wx_puid = %s or nickname = %s)", (group_id, wx_puid, nickname))
 return result

5. 数据库连接池

这里的数据库连接使用了数据库连接池:DBUtils.PersistentDB

DBUtils.PooledDB: 适用于多线程频繁开启关闭数据库连接

DBUtils.PersistentDB:适用于单线程多次频繁连接数据库

如果不采用线程池而是采取直连,那么运行一段时间后,脚本将出现该错误

pymysql.err.OperationalError: 2006

这里将DBUtils再次封装了一下,写了一个单例模式BotDatabase, 提供了query(select), execute(update, delete) 以及批处理execute等常用接口。

6. 启动定时器

# 早八点晚八点各执行检查一次
def start_schedule_for_checking_member(group):
 scheduler = BlockingScheduler()
 scheduler.add_job(lambda: process_group_members(group), 'cron', hour=8, minute=1, timezone="Europe/Paris")
 scheduler.add_job(lambda: process_group_members(group), 'cron', hour=20, minute=1, timezone="Europe/Paris")

最终成果

Python 分析在德的中国程序员,告别 996?(2)

Python 分析在德的中国程序员,告别 996?(2)

已知问题

在消息中输入 @群员昵称 并不能真正让该群友收到@提示(显示推送提示),微信App里是在@群员昵称后自动加上了一个特殊的显示空白的字符u’ ′。但是经测试,加上这个符号也不行,推测是微信Web API基于防范垃圾推送,屏蔽了群提示接口。

wxpy的bot在运行一段时间后会停止工作,出现连接服务器错误,必须重新登录,推测是微信Web API的Session安全机制导致的问题。

数据清洗

一段时间后大部分群友修改了昵称,于是有了在德中国程序员职业和专业方向的数据,经清洗后,导出CSV规格如下。

Python 分析在德的中国程序员,告别 996?(2)

数据分析

该任务所需第三方库如下:

pip3 install pandas
pip3 install matplotlib
pip3 install jieba
pip3 install wordcloud
pip3 install seaborn
pip3 install palettable

相关推荐