数据库读取数据或复制系统文件 ——》 发送至指定微信群或微信名
细节开发:
1、使用ini配置文件进行配置获取,修改内部设置通过ini文件进行修改
2、建立exe互斥设置,当一个exe进程启动后,第二exe进程将通知提示已启动进程
3、数据库读取数据,将读取的数据发送至微信中,并将已发送微信数据修改为已读数据
4、微信进程启动与自动登陆设置
5、微信中:复制,粘贴,发送,回车,鼠标移动
6、获取微信中微信搜索框,进行微信群或微信名搜索
7、监测微信进程,如果微信掉线则重新自动登陆
8、定时自动化发送信息或文件
9、进行log日志登记,将程序运行信息进行登记
10、故障异常处理
调用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():获取当前文件夹目录
调用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:关闭数据库
互斥设计,当启动第一个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)
自动发送微信信息需要模拟键盘鼠标进行操作,因此需要设计键盘与鼠标的操作
键盘操作:模拟 ctrl+v;alt+s;enter
鼠标操作:模拟 单击;移动鼠标位置
调用python安装包 —— win32api
pip install pywin32
设计程序
# 模拟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即可
程序设计:
# 模拟单击
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坐标
程序设计
# 启动微信程序
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:窗口句柄
程序设计
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 报错无效语句
调用python包 —— win32clipboard;PyQt5
import win32clipboard as w
from PyQt5 import QtCore, QtWidgets
程序设计
# 文本
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.关闭剪切板
# 复制文件
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):操作剪切板
调用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:该函数或方法要用到的位置参数或关键字参数
调用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文件中
程序设计
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+"发送失败")
程序设计
# 微信信息发送处理
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+"发送微信结束")
此方法通过前面各方法进行整合处理
调用python包 —— pyinstaller
pip install pyinstaller
打包为exe文件是因为如果需要远程执行或者需要发送给同事执行时,无需再次下载python与python的各类包,只需要执行该exe文件即可
首先进入编辑该程序的程序文件夹
在终端使用:cd 文件目录
进入后使用
Pyinstaller -F 程序文件名.py
就此打包成功,打包成功后会出现dist文件夹,exe在dist文件夹中
exe文件
命令总结:
Pyinstaller -F setup.py 打包exe
Pyinstaller -F -w setup.py 不带控制台的打包
Pyinstaller -F -i xx.ico setup.py 打包指定exe图标打包
就此,PC端微信自动化机器人已完成
特此申明,此机器人只供学习,商业转载请联系作者获得授权
此创作中,参考了多位程序创作者的文档,在此表示感谢,如有侵权,可联系本作者。