PC端微信自动发送机器人

PC端微信自动发送机器人

  • 一、设计框架
  • 二、详细设计流程(代码设计)
    • 2.1 ini配置文件读取
    • 2.2 数据库读取与修改
    • 2.3 exe互斥设计
    • 2.4 键盘与鼠标操作
      • 2.4.1 模拟键盘操作
      • 2.4.2 模拟鼠标操作
    • 2.5 启动PC端微信
    • 2.6 判断微信进程是否存在
    • 2.7 文本与文件剪切
      • 2.7.1 文本剪切
      • 2.7.2 文件剪切
    • 2.8 定时任务器
    • 2.9 log日志创建
    • 2.10 微信信息/文件发送
      • 2.10.1 微信文件发送处理
      • 2.10.2 微信信息发送处理
    • 2.11 打包为exe文件
    • 总结

一、设计框架

​​​​​​​​PC端微信自动发送机器人_第1张图片
主要开发流程:

数据库读取数据或复制系统文件 ——》 发送至指定微信群或微信名

细节开发:

1、使用ini配置文件进行配置获取,修改内部设置通过ini文件进行修改

2、建立exe互斥设置,当一个exe进程启动后,第二exe进程将通知提示已启动进程

3、数据库读取数据,将读取的数据发送至微信中,并将已发送微信数据修改为已读数据

4、微信进程启动与自动登陆设置

5、微信中:复制,粘贴,发送,回车,鼠标移动

6、获取微信中微信搜索框,进行微信群或微信名搜索

7、监测微信进程,如果微信掉线则重新自动登陆

8、定时自动化发送信息或文件

9、进行log日志登记,将程序运行信息进行登记

10、故障异常处理

二、详细设计流程(代码设计)

2.1 ini配置文件读取

调用python程序包 —— configparser

pip install configparser

程序代码

# 读取ini
cfgpath = os.getcwd() + r"\peiz.ini"
log.info(cfgpath)
# 创建管理对象
conf = configparser.RawConfigParser()
# 读取ini文件
try:
    conf.read(cfgpath, encoding="utf-8")
except:
    conf.read(cfgpath, encoding='utf-8-sig')
items = conf.items('mysql_db')

附加:ini配置文件编写规则

节点:[节点名称]
注释: ;注释内容

[weixin_name]
;name:发送微信名称
;address:微信存储路径
;file_path:发送文件路径及文件名
name = 小号,留梦阁,文件传输助手
address = D:\WeChat\WeChat.exe
file_path = D:\python.txt

因为读取ini文件的编程会有所变动,因此需要设置异常处理,选择读取 utf-8 或 utf-8-sig 两种编码形式。

cfgpath = os.getcwd() + r"\peiz.ini"

os.getcwd():获取当前文件夹目录

2.2 数据库读取与修改

调用python包 —— pymysql

pip install pymysql

程序代码
2.2.1 数据库读取

# 连接数据库
def connect_sql():
    log.info('开始连接数据库读取数据库')
    items = conf.items('mysql_db')
    print(items)
    try:
        connect = pymysql.connect(
            host=items[0][1],
            port=int(items[1][1]),
            user=items[2][1],
            password=items[3][1],
            db=items[4][1],
            charset=items[5][1]
        )
        mysql_select = conf.items('mysql_select')
        cursor = connect.cursor()
        sql_select = mysql_select[0][1]
        cursor.execute(sql_select)
        lidata = cursor.fetchall()
        connect.commit()
        # sql_update = mysql_select[1][1]
        # cursor.execute(sql_update)
        connect.close()
        log.info('读取数据库成功')
        return lidata
    except Exception as e:
        log.info(e)

2.2.2 数据库修改

def update_sql(gaojid):
    log.info('开始修改数据库数据')
    items = conf.items('mysql_db')
    try:
        connect = pymysql.connect(
            host=items[0][1],
            port=int(items[1][1]),
            user=items[2][1],
            password=items[3][1],
            db=items[4][1],
            charset=items[5][1]
        )
        cursor = connect.cursor()
        sql_update = "update gaojxxb set tixzt=2,gengxsj=now() where gaojid="+'\''+gaojid+'\''
        print(sql_update)
        try:
            cursor.execute(sql_update)
            # sql_update = mysql_select[1][1]
            # cursor.execute(sql_update)
            connect.commit()
        except Exception as t:
            log.info(t)
        connect.close()
        log.info('修改数据成功')
    except Exception as e:
        log.info(e)

通过读取ini中的数据库配置信息进行对数据库连接读取等操作
数据库连接

pymysql.connect(host,port,user,password,db,charset)

host:读取数据库IP
port:读取端口号
user:读取用户名
password:用户名密码
db:读取的数据库
charset:读取编码

游标获取

cursor = connect.cursor()

用cursor方式获取游标,一切操作通过游标进行操作

执行sql代码

sql_update = "update gaojxxb set tixzt=2,gengxsj=now() where gaojid="+'\''+gaojid+'\''
cursor.execute(sql_update)
connect.commit()

cursor.execute:执行sql
connect.commit:提交数据库执行
cursor.fetchall:查询全部数据
connect.close:关闭数据库

2.3 exe互斥设计

互斥设计,当启动第一个exe进程时,再次启动exe进程将会报错
调用python程序包 —— ERROR_ALREADY_EXISTS

from winerror import ERROR_ALREADY_EXISTS
pip install winerror

设计程序

class singleinstance(object):
    """在内核里设置互斥对象的方式实现只启动一个exe程序"""
    def __init__(self):
        self.mutexname = "testmutex_{D0E858DF-985E-4907-B7FB-8D732C3FC3B9}"
        self.mutex = CreateMutex(None, False, self.mutexname)
        self.lasterror = GetLastError()

    def aleradyrunning(self):
        print(self.lasterror)
        print(ERROR_ALREADY_EXISTS)
        return (self.lasterror == ERROR_ALREADY_EXISTS)

    def __del__(self):
        if self.mutex:
            CloseHandle(self.mutex)


# 程序入口
def run():
    function_title = "程序入口"
    try:
        global myapp
        myapp = singleinstance()
        # 判断程序是否存在
        if myapp.aleradyrunning():
            error_info = "已有一个程序正在运行中"
            print(error_info)
            MessageBox(0, error_info, "警告", win32con.MB_ICONWARNING)
            return
        # 注册进程信息
        multiprocessing.freeze_support()
        items = conf.items('job_timing')
        time_job = int(items[0][1])
        weixin_job(time_job)
    except Exception as e:
        error_info = "{}异常:{}".format(function_title, e)
        log.info(error_info)
        MessageBox(0, "报备机器人出现异常,请重启程序!", "警告", win32con.MB_ICONWARNING)

2.4 键盘与鼠标操作

自动发送微信信息需要模拟键盘鼠标进行操作,因此需要设计键盘与鼠标的操作
键盘操作:模拟 ctrl+v;alt+s;enter
鼠标操作:模拟 单击;移动鼠标位置

调用python安装包 —— win32api

pip install pywin32

2.4.1 模拟键盘操作

设计程序

# 模拟ctrl+V
def ctrlV():
    win32api.keybd_event(17, 0, 0, 0)  # ctrl
    win32api.keybd_event(86, 0, 0, 0)  # V
    win32api.keybd_event(86, 0, win32con.KEYEVENTF_KEYUP, 0)  # 释放按键
    win32api.keybd_event(17, 0, win32con.KEYEVENTF_KEYUP, 0)

# 模拟alt+s
def altS():
    win32api.keybd_event(18, 0, 0, 0)
    win32api.keybd_event(83, 0, 0, 0)
    win32api.keybd_event(83, 0, win32con.KEYEVENTF_KEYUP, 0)
    win32api.keybd_event(18, 0, win32con.KEYEVENTF_KEYUP, 0)

# 模拟enter
def enter():
    win32api.keybd_event(13, 0, 0, 0)
    win32api.keybd_event(13, 0, win32con.KEYEVENTF_KEYUP, 0)

win32api.keybd_event(bVk, bScan, dwFlags, dwExtraInfo)

bVk:虚拟键码(键盘键码对照表见附录)
bScan:硬件扫描码,一般设置为0即可
dwFlags:函数操作的一个标志位,如果值为KEYEVENTF_EXTENDEDKEY则该键被按下,也可设置为0即可,如果值为KEYEVENTF_KEYUP则该按键被释放;
dwExtralnfo:定义与击键相关的附加的32位值,一般设置为0即可

对应键盘键码:
PC端微信自动发送机器人_第2张图片
PC端微信自动发送机器人_第3张图片
PC端微信自动发送机器人_第4张图片

2.4.2 模拟鼠标操作

程序设计:

# 模拟单击
def click():
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)

# 移动鼠标的位置
def movePos(x, y):
    win32api.SetCursorPos((x, y))

win32api.mouse_event(dwFlags,dx,dy,dwData,dwExtraInfo)

dwFlags:控制鼠标运动和按钮点击的各个方面

函数 意义
MOUSEEVENTF_ABSOLUTE 在 DX和 DY参数包含归绝对坐标。如果未设置,这些参数包含相关数据:自上次报告位置以来的位置变化。无论哪种鼠标或类似鼠标的设备(如果有)连接到系统,都可以设置或不设置此标志。有关鼠标相对运动的更多信息,请参阅以下备注部分
MOUSEEVENTF_LEFTDOWN 左击鼠标
MOUSEEVENTF_RIGHTDOWN 右击鼠标
MOUSEEVENTF_WHEEL 如果鼠标有滚轮,则滚轮已移动。移动量在dwData 中指定

更多信息:
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mouse_event

dx:鼠标沿 x 轴的绝对位置或其自上次生成鼠标事件以来的运动量,具体取决于MOUSEEVENTF_ABSOLUTE的设置

dy:鼠标沿 y 轴的绝对位置或其自上次生成鼠标事件以来的运动量,具体取决于MOUSEEVENTF_ABSOLUTE的设置

dwData:
如果 dwFlags包含MOUSEEVENTF_WHEEL,则 dwData指定车轮移动量。正值表示滚轮向前旋转,远离用户;负值表示轮子向后旋转,朝向用户。滚轮点击定义为WHEEL_DELTA,即 120。
如果dwFlags包含MOUSEEVENTF_HWHEEL,则 dwData指定车轮移动量。正值表示车轮向右倾斜;负值表示车轮向左倾斜。
如果 dwFlags包含MOUSEEVENTF_XDOWN或MOUSEEVENTF_XUP,则 dwData指定按下或释放哪些 X 按钮。该值可以是以下标志的任意组合。
如果 dwFlags不是MOUSEEVENTF_WHEEL、MOUSEEVENTF_XDOWN或MOUSEEVENTF_XUP,则 dwData应该为零

dwExtraInfo:与鼠标事件关联的附加值。应用程序调用GetMessageExtraInfo来获取此额外信息

鼠标移动

win32api.SetCursorPos((dx,dy))
dx:x坐标
dy:y坐标

2.5 启动PC端微信

程序设计

# 启动微信程序
def weixin():
    log.info('启动微信')
    address = conf.items('weixin_name')
    # app = Application(backend='uia').start(address[1][1]) 对后续有影响,使用os启动
    app = os.startfile(address[1][1]) 
    time.sleep(3) # 休眠等待,避免网络环境导致报错
    pyautogui.press('enter')
    time.sleep(7) # 休眠等待,避免网络环境导致报错
    log.info('微信启动成功')
    hwnd = win32gui.FindWindow("WeChatMainWndForPC", None)
    win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST, 0, 0, 1000, 700, win32con.SWP_SHOWWINDOW)
    win32gui.SetForegroundWindow(hwnd)

os.startfile()

启动进程,括号内写进程路径

pyautogui.press(‘enter’)

模拟回车按键

win32gui.FindWindow(param1,param2)

窗口句柄获取
param1:传入窗口类名
param2:传入窗口标题

win32gui.SetWindowPos(HWN hWnd,HWND hWndlnsertAfter,int X,int Y,int cx,int cy,UNIT.Flags)

该函数改变一个子窗口,弹出式窗口式顶层窗口的尺寸,位置和Z序。子窗口,弹出式窗口,及顶层窗口根据它们在屏幕上出现的顺序排序、顶层窗口设置的级别最高,并且被设置为Z序的第一个窗口
hWnd:窗口句柄
hWndlnsertAfter:在z序中的位于被置位的窗口前的窗口句柄。该参数必须为一个窗口句柄,可以是以下值

函数 意义
HWND_BOTTOM 将窗口置于Z序的底部。如果参数hWnd标识了一个顶层窗口,则窗口失去顶级位置,并且被置在其他窗口的底部
HWND_DOTTOPMOST 将窗口置于所有非顶层窗口之上(即在所有顶层窗口之后)。如果窗口已经是非顶层窗口则该标志不起作用
HWND_TOP 将窗口置于Z序的顶部
HWND_TOPMOST 将窗口置于所有非顶层窗口之上。即使窗口未被激活窗口也将保持顶级位置

int X:以客户坐标指定窗口新位置的左边界
int Y:以客户坐标指定窗口新位置的顶边界
cx:以像素指定窗口的新宽度
cy:以像素指定窗口的新高度

UNIT.Flags:窗口尺寸和定位的标志。该参数可以是下列值的组合

函数 意义
SWP_ASNCWINDOWPOS 如果调用进程不拥有窗口,系统会向拥有窗口的线程发出需求。这就防止调用线程在其他线程处理需求的时候发生死锁。
SWP_DEFERERASE 防止产生WM_SYNCPAINT消息
SWP_DRAWFRAME 在窗口周围画一个边框(定义在窗口类描述中)
SWP_FRAMECHANGED 给窗口发送WM_NCCALCSIZE消息,即使窗口尺寸没有改变也会发送该消息。如果未指定这个标志,只有在改变了窗口尺寸时才发送WM_NCCALCSIZE
SWP_HIDEWINDOW 隐藏窗口
SWP_NOACTIVATE 不激活窗口。如果未设置标志,则窗口被激活,并被设置到其他最高级窗口或非最高级组的顶部(根据参数hWndlnsertAfter设置)
SWP_NOCOPYBITS 清除客户区的所有内容。如果未设置该标志,客户区的有效内容被保存并且在窗口尺寸更新和重定位后拷贝回客户区
SWP_NOMOVE 维持当前位置(忽略X和Y参数)
SWP_NOOWNERZORDER 不改变z序中的所有者窗口的位置
SWP_NOREDRAW 不重画改变的内容。如果设置了这个标志,则不发生任何重画动作。适用于客户区和非客户区(包括标题栏和滚动条)和任何由于窗回移动而露出的父窗口的所有部分。如果设置了这个标志,应用程序必须明确地使窗口无效并区重画窗口的任何部分和父窗口需要重画的部分。
SWP_NOREPOSITION 与SWP_NOOWNERZORDER标志相同
SWP_NOSENDCHANGING 防止窗口接收WM_WINDOWPOSCHANGING消息
SWP_NOSIZE 维持当前尺寸(忽略cx和Cy参数)
SWP_NOZORDER 维持当前Z序(忽略hWndlnsertAfter参数)
SWP_SHOWWINDOW 显示窗口

win32gui.SetForegroundWindow(HWND HWND)

将创建指定的窗口,并激活到前台窗口的线程
HWND:窗口句柄

2.6 判断微信进程是否存在

程序设计

def check_exsit(process_name):
    try:
        pythoncom.CoInitialize() # 加上此函数避免processCodeCov 报错无效语句
        WMI = win32com.client.GetObject('winmgmts:')
        processCodeCov = WMI.ExecQuery('select * from Win32_Process where Name like "%{}%"'.format(process_name))
        # print(processCodeCov)
        # print(len(processCodeCov))
        if len(processCodeCov) > 0:
            pythoncom.CoInitialize()
            # weixdy_xinx()
            weixdy_wenj(u"微信")

        else:
            weixin()
            weixdy_xinx()
            # 置顶微信
            movePos(880, 10)
            click()
            movePos(50,50)

    except Exception as e:
        log.info(e)

判断微信进程是否存在,若不存在则开启微信进程,如存在则执行自动化程序

WMI = win32com.client.GetObject(‘winmgmts:’)
processCodeCov = WMI.ExecQuery(‘select * from Win32_Process where Name like “%{}%”’.format(process_name))

监测进程是否存在,如存在则返回len(processCodeCov)大于零

pythoncom.CoInitialize()

加上此函数避免processCodeCov 报错无效语句

2.7 文本与文件剪切

调用python包 —— win32clipboard;PyQt5

import win32clipboard as w
from PyQt5 import QtCore, QtWidgets

2.7.1 文本剪切

程序设计

# 文本
def setText(aString):
    w.OpenClipboard()
    w.EmptyClipboard()
    w.SetClipboardData(win32con.CF_UNICODETEXT, aString)
    w.CloseClipboard()

w.OpenClipboard()
1.开始剪切板操作
w.EmptyClipboard()
2.清空剪切板
w.SetClipboardData(win32con.CF_UNICODETEXT, aString)
3.尝试将处理完的字符放入剪切板,win32con.CF_UNICODETEXT:使用编码;aString:剪切的txt
w.CloseClipboard()
4.关闭剪切板

2.7.2 文件剪切

# 复制文件
def setFile(url):
    fileName = url
    app = QtWidgets.QApplication([]) # 实例化应用对象 操控对象
    data = QtCore.QMimeData() # 容器MIME类型信息的数据记录 转移,拷贝
    print(data)
    url = QtCore.QUrl.fromLocalFile(fileName) # 打开该文件夹
    data.setUrls([url]) # 将存储在MIME数据对象中的URL设置为参数指定的URL
    app.clipboard().setMimeData(data) #操作剪切板
    return fileName

注:如果导入pywinauto.application包,则此文件剪切将会失效。

QtWidgets.QApplication([]):实例化应用对象 操控对象
QtCore.QMimeData():容器MIME类型信息的数据记录 转移,拷贝
QtCore.QUrl.fromLocalFile(fileName):打开该文件夹
data.setUrls([url]):将存储在MIME数据对象中的URL设置为参数指定的URL
app.clipboard().setMimeData(data):操作剪切板

2.8 定时任务器

调用python包 —— threading

from threading import Timer

# 定时任务器
def weixin_job(inc):
    check_exsit("WeChat.exe")
    t = Timer(inc,weixin_job,(inc,))
    t.start()

Timer(interval,func,args/kwargs)
interval:用于设置等待时间
func:要执行的函数或方法
args/kwargs:该函数或方法要用到的位置参数或关键字参数

2.9 log日志创建

调用python包 —— logging;os

import logging
import os
from logging.handlers import RotatingFileHandler

程序设计

# - * - coding: utf - 8 -*-
import logging
import os
from logging.handlers import RotatingFileHandler


# 获取日志级别
int_loglevel = 10

# 创建日志log对象
save_log_folder = os.getcwd() + os.sep + "rpa_log"
folder = os.path.exists(save_log_folder)
if not folder:
    os.makedirs(save_log_folder)
logging_msg_format = '%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s'
logging_date_format = '%Y-%m-%d %H:%M:%S'
log_file = os.path.join(save_log_folder, "rpa_log")
logHandler = RotatingFileHandler(filename=log_file, maxBytes=2*1024*1024, encoding='utf-8', backupCount=30)
logFormatter = logging.Formatter(logging_msg_format)
logHandler.suffix = "%Y%m%d.log"
logHandler.setFormatter(logFormatter)
log = logging.getLogger('MyLogger')
log.addHandler(logHandler)
log.setLevel(int_loglevel)

此方法需要另起一个文件放于同级main文件中

2.10 微信信息/文件发送

2.10.1 微信文件发送处理

程序设计

def weixdy_wenj(chatroom):
    # 获取鼠标当前位置
    log.info("获取微信窗口界面")
    hwnd = win32gui.FindWindow("WeChatMainWndForPC", chatroom)
    # print(hwnd)
    win32gui.BringWindowToTop(hwnd)
    win32gui.MoveWindow(hwnd, 0, 0, 1000, 700, win32con.SWP_SHOWWINDOW)
    win32gui.SetForegroundWindow(hwnd)
    time.sleep(0.01)
    date_1 = conf.items('weixin_name')
    url_date = date_1[2][1]
    print(url_date)
    name_list = date_1[0][1].split(',')
    for i in name_list:
        log.info("正在向"+i+"发送文件")
        # 2.移动鼠标到通讯录位置,单击打开通讯录
        movePos(28, 147)
        click()
        # 3.移动鼠标到搜索框,单击,输入要搜索的名字
        movePos(148, 35)
        click()
        time.sleep(1)
        setText(i)
        ctrlV()
        time.sleep(1)
        enter()
        time.sleep(1)
        try:
            setFile(url_date)
            ctrlV()
            altS()
            time.sleep(0.5)
        except:
            log.info("向"+i+"发送失败")

2.10.2 微信信息发送处理

程序设计

# 微信信息发送处理
def weixdy_xinx():
    # 获取鼠标当前位置
    log.info("获取微信窗口界面")
    hwnd = win32gui.FindWindow("WeChatMainWndForPC", None)
    # print(hwnd)
    win32gui.BringWindowToTop(hwnd)
    win32gui.MoveWindow(hwnd, 0, 0, 1000, 700, True)
    time.sleep(0.01)
    # 1.获取sql读取数据传入
    data = connect_sql()
    # print(data)

    # 读取ini文件微信名称
    log.info("读取微信名")
    name = conf.items('weixin_name')
    name_list = name[0][1].split(',')
    for i in name_list:
        log.info("正在向"+i+"发送微信信息")
        # 2.移动鼠标到通讯录位置,单击打开通讯录
        movePos(28, 147)
        click()
        # 3.移动鼠标到搜索框,单击,输入要搜索的名字
        movePos(148, 35)
        click()
        time.sleep(1)
        setText(i)
        ctrlV()
        time.sleep(1)
        enter()
        time.sleep(1)
        for row in data:
            # print(row)
            data_text = row[0]
            gaojid = str(row[1])
            try:
                log.info(data_text)
                # 4.复制要发送的消息,发送
                setText(data_text)
                ctrlV()
                altS()
                time.sleep(0.5)
                update_sql(gaojid)
            except:
                update_except(gaojid)
                log.info(data_text+":发送失败")
        log.info(i+"发送微信结束")

此方法通过前面各方法进行整合处理

2.11 打包为exe文件

调用python包 —— pyinstaller

pip install pyinstaller

打包为exe文件是因为如果需要远程执行或者需要发送给同事执行时,无需再次下载python与python的各类包,只需要执行该exe文件即可

首先进入编辑该程序的程序文件夹

在终端使用:cd 文件目录

进入后使用

Pyinstaller -F 程序文件名.py

就此打包成功,打包成功后会出现dist文件夹,exe在dist文件夹中
打包文件夹
exe文件
exe图标
命令总结:
Pyinstaller -F setup.py 打包exe

Pyinstaller -F -w setup.py 不带控制台的打包

Pyinstaller -F -i xx.ico setup.py 打包指定exe图标打包

总结

就此,PC端微信自动化机器人已完成
特此申明,此机器人只供学习,商业转载请联系作者获得授权
此创作中,参考了多位程序创作者的文档,在此表示感谢,如有侵权,可联系本作者。

你可能感兴趣的:(微信,python,聊天机器人)