编写本聊天机器人纯属乐趣,它并不实用!!!
文章有点长,代码分析只把完整代码拆分解释,没有新的东西,大佬可忽略
目录
- 实现思路
- 完整代码
- 代码分析
- 导入要使用的模块
- 获取窗口句柄
- 获取窗口左上角和右下角坐标
- 对窗口进行截图并保存
- 对图片进行处理(裁剪)
- 调用百度ocr识别文字
- 用于访问智能回答网站获取答案
- 发送消息
- 主函数
- 附加代码(对比图片)
- 总结
实现思路
以前的很多微信机器人很多都是依靠python中的itchat来写的,因为当时微信网页端对大部分的用户还是开发的,而itchat模块正是对微信网页端进行操作的(感觉像爬虫)。但现在很多人的微信已经登陆不上微信的网页端了,这就使依靠itchat的微信机器人无法使用。我想做微信机器人后寻找了好久不依赖itchat的方法,我看到两个比较合适的方法,一个是对PC微信客户端进行Hook逆向分析(github上有源码),另外一个就是本文使用的方法(👉方法来源)
实现思路如下:
完整代码
import pyautogui
pyautogui.FAILSAFE = False #防止failSafeCheck()报错
from ctypes import windll
import win32api,win32con,pyperclip
import win32gui
import win32ui
import ocr
from PIL import Image
import time'''获得窗口句柄'''
def gethandle(Class,Caption):return win32gui.FindWindow(Class,Caption)'''获取窗口左上角和右下角坐标'''
def getposition(handle):if handle==0:return Noneelse:return win32gui.GetWindowRect(handle)'''对窗口进行截图并保存'''
def Screenshot(hWnd,file_path):#获取句柄窗口的大小信息left, top, right, bot = win32gui.GetWindowRect(hWnd)width = right - leftheight = bot - top#返回句柄窗口的设备环境,覆盖整个窗口,包括非客户区,标题栏,菜单,边框hWndDC = win32gui.GetWindowDC(hWnd)#创建设备描述表mfcDC = win32ui.CreateDCFromHandle(hWndDC)#创建内存设备描述表saveDC = mfcDC.CreateCompatibleDC()#创建位图对象准备保存图片saveBitMap = win32ui.CreateBitmap()#为bitmap开辟存储空间saveBitMap.CreateCompatibleBitmap(mfcDC,width,height)#将截图保存到saveBitMap中saveDC.SelectObject(saveBitMap)#保存bitmap到内存设备描述表saveDC.BitBlt((0,0), (width,height), mfcDC, (0, 0), win32con.SRCCOPY)#如果要截图到打印设备:###最后一个int参数:0-保存整个窗口,1-只保存客户区。如果PrintWindow成功函数返回值为1result = windll.user32.PrintWindow(hWnd,saveDC.GetSafeHdc(),0)#保存图像##方法一:windows api保存###保存bitmap到文件saveBitMap.SaveBitmapFile(saveDC,file_path)#print(result) #PrintWindow成功则输出1return result'''##方法二(第一部分):PIL保存###获取位图信息bmpinfo = saveBitMap.GetInfo()bmpstr = saveBitMap.GetBitmapBits(True)###生成图像im_PIL = Image.frombuffer('RGB',(bmpinfo['bmWidth'],bmpinfo['bmHeight']),bmpstr,'raw','BGRX',0,1)##方法二(后续转第二部分)##方法三(第一部分):opencv+numpy保存###获取位图信息signedIntsArray = saveBitMap.GetBitmapBits(True)##方法三(后续转第二部分)#内存释放win32gui.DeleteObject(saveBitMap.GetHandle())saveDC.DeleteDC()mfcDC.DeleteDC()win32gui.ReleaseDC(hWnd,hWndDC)##方法二(第二部分):PIL保存###PrintWindow成功,保存到文件,显示到屏幕im_PIL.save("im_PIL.png") #保存im_PIL.show() #显示##方法三(第二部分):opencv+numpy保存###PrintWindow成功,保存到文件,显示到屏幕im_opencv = numpy.frombuffer(signedIntsArray, dtype = 'uint8')im_opencv.shape = (height, width, 4)cv2.cvtColor(im_opencv, cv2.COLOR_BGRA2RGB)cv2.imwrite("im_opencv.jpg",im_opencv,[int(cv2.IMWRITE_JPEG_QUALITY), 100]) #保存cv2.namedWindow('im_opencv') #命名窗口cv2.imshow("im_opencv",im_opencv) #显示cv2.waitKey(0)cv2.destroyAllWindows()''''''对图片进行处理(裁剪)'''
def cropimg(inimg,outimg):#打开图片im = Image.open(inimg)# 图片的宽度和高度img_size = im.sizeprint("图片宽度和高度分别是{}".format(img_size))'''裁剪:传入一个元组作为参数元组里的元素分别是:(距离图片左边界距离x, 距离图片上边界距离y,距离图片左边界距离+裁剪框宽度x+w,距离图片上边界距离+裁剪框高度y+h)'''x = 465y = 535w = 270h = 70region = im.crop((x, y, x + w, y + h))#保存图片region.save(outimg)'''调用百度ocr识别文字'''
def Getmsg(file_path):# 获取access tokentoken = ocr.fetch_token()# 拼接通用文字识别高精度urlimage_url = ocr.OCR_URL + "?access_token=" + tokentext = ""# 读取测试图片file_content = ocr.read_file(file_path)# 调用文字识别服务result = ocr.request(image_url, ocr.urlencode({'image': ocr.base64.b64encode(file_content)}))#print(result)# 解析返回结果result_json = ocr.json.loads(result)for words_result in result_json["words_result"]:text = text + words_result["words"]# 打印文字print('文字识别结果:')print(text)return text'''对比图片,相似返回True,否则返回False'''
def Compare(img1,img2):#打开两张图片image1=Image.open(img1)image3=Image.open(img2)#把图像对象转换为直方图数据,存在list h1、h2 中h1=image1.histogram()h2=image3.histogram()L1=len(h1)L2=len(h2)if L1>=L2:n=L2else:n=L1sum=0for i in range(n):sum=sum+abs(h1[i]-h2[i])print(sum)result=sum/(len(h1)+len(h2)/2)print(result)if result<=1:return Trueelse:return False'''用于访问智能回答网站获取答案'''
import requests #导入requests包,使用爬虫def get_remsg(text):#伪装成浏览器User_Agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36 Edg/93.0.961.47'#线上机器人Furl="https://api.ownthink.com/bot?appid=9ffcb5785ad9617bf4e64178ac64f7b1&spoken="url=Furl+text req=requests.get(url,User_Agent) #向线上机器人提问req.encoding='utf-8' #定义编码格式为utf-8re=eval(req.text) #将返回的内容转成字典类型return re['data']['info']['text'] #返回回答'''发送消息'''
def send(msg_to_sent,handle,rightx,topy):#将鼠标定位到输入框的位置win32api.SetCursorPos([rightx-400,topy+720])# 强行显示界面win32gui.ShowWindow(handle, win32con.SW_RESTORE)# 将窗口提到最前win32gui.SetForegroundWindow(handle)time.sleep(0.5)pyperclip.copy(msg_to_sent) # 需要发送的内容time.sleep(0.5)pyautogui.hotkey('ctrl', 'v') # 按下 ctrl + v 粘贴内容time.sleep(0.5)pyautogui.hotkey('enter')return 1if __name__=='__main__':#获取窗口句柄handle=gethandle('WeChatMainWndForPC','微信')#获取窗口大小信息left, top, right, bot=getposition(handle)#对屏幕进行截图 Screenshot(handle,'my.bmp')#对图片进行处理cropimg('my.bmp','my.bmp')#对图片进行文字识别text=Getmsg('my.bmp')#根据识别内容用爬虫的方法从线上聊天机器人获得回复内容r=get_remsg(text)print('线上机器人的回答:')print(r)#发送消息send(r,handle,right,top)
代码分析
导入要使用的模块
import pyautogui
pyautogui.FAILSAFE = False #防止failSafeCheck()报错
from ctypes import windll
import win32api,win32con,pyperclip
import win32gui
import win32ui
import ocr
from PIL import Image
import time
如果没有上面的库的话可以自行下载,下载方法:
打开cmd或者PowerShell,输入pip install 库名
例如导入win32gui
pip install win32gui
导入win32gui、win32con、win32ui极有可能失败(高版本导入极易失败,别问,问就是试过),建议自行寻找解决方法。
另外,ocr是在我本地的文件,不能通过pip的方法导入,下面调用百度ocr识别文字部分讲解。
获取窗口句柄
'''获得窗口句柄'''
def gethandle(Class,Caption):return win32gui.FindWindow(Class,Caption)
利用win32gui中的函数FindWindow()获取窗口句柄,Class是窗口的类名,Caption是窗口的标题,本人还不会用代码获取窗口类名和标题(一般是窗口左上角的字)的方法,我是通过Visual Studio中的spy++工具查看的(微信的类名是WeChatMainWndForPC,标题是微信)。
获取窗口左上角和右下角坐标
'''获取窗口左上角和右下角坐标'''
def getposition(handle):if handle==0:return Noneelse:return win32gui.GetWindowRect(handle)
简单调用win32gui中的GetWindowRect(),这个函数返回的是窗口左上角和右下角的坐标(以元组的形式返回)
对窗口进行截图并保存
'''对窗口进行截图并保存'''
def Screenshot(hWnd,file_path):#获取句柄窗口的大小信息left, top, right, bot = win32gui.GetWindowRect(hWnd)width = right - leftheight = bot - top#返回句柄窗口的设备环境,覆盖整个窗口,包括非客户区,标题栏,菜单,边框hWndDC = win32gui.GetWindowDC(hWnd)#创建设备描述表mfcDC = win32ui.CreateDCFromHandle(hWndDC)#创建内存设备描述表saveDC = mfcDC.CreateCompatibleDC()#创建位图对象准备保存图片saveBitMap = win32ui.CreateBitmap()#为bitmap开辟存储空间saveBitMap.CreateCompatibleBitmap(mfcDC,width,height)#将截图保存到saveBitMap中saveDC.SelectObject(saveBitMap)#保存bitmap到内存设备描述表saveDC.BitBlt((0,0), (width,height), mfcDC, (0, 0), win32con.SRCCOPY)#如果要截图到打印设备:###最后一个int参数:0-保存整个窗口,1-只保存客户区。如果PrintWindow成功函数返回值为1result = windll.user32.PrintWindow(hWnd,saveDC.GetSafeHdc(),0)#保存图像##方法一:windows api保存###保存bitmap到文件saveBitMap.SaveBitmapFile(saveDC,file_path)#print(result) #PrintWindow成功则输出1return result'''##方法二(第一部分):PIL保存###获取位图信息bmpinfo = saveBitMap.GetInfo()bmpstr = saveBitMap.GetBitmapBits(True)###生成图像im_PIL = Image.frombuffer('RGB',(bmpinfo['bmWidth'],bmpinfo['bmHeight']),bmpstr,'raw','BGRX',0,1)##方法二(后续转第二部分)##方法三(第一部分):opencv+numpy保存###获取位图信息signedIntsArray = saveBitMap.GetBitmapBits(True)##方法三(后续转第二部分)#内存释放win32gui.DeleteObject(saveBitMap.GetHandle())saveDC.DeleteDC()mfcDC.DeleteDC()win32gui.ReleaseDC(hWnd,hWndDC)##方法二(第二部分):PIL保存###PrintWindow成功,保存到文件,显示到屏幕im_PIL.save("im_PIL.png") #保存im_PIL.show() #显示##方法三(第二部分):opencv+numpy保存###PrintWindow成功,保存到文件,显示到屏幕im_opencv = numpy.frombuffer(signedIntsArray, dtype = 'uint8')im_opencv.shape = (height, width, 4)cv2.cvtColor(im_opencv, cv2.COLOR_BGRA2RGB)cv2.imwrite("im_opencv.jpg",im_opencv,[int(cv2.IMWRITE_JPEG_QUALITY), 100]) #保存cv2.namedWindow('im_opencv') #命名窗口cv2.imshow("im_opencv",im_opencv) #显示cv2.waitKey(0)cv2.destroyAllWindows()'''
我是代码搬运工,我也没具体看内容,一句话:能用(实在是懒得写了,网上有现成的)
对图片进行处理(裁剪)
'''对图片进行处理(裁剪)'''
def cropimg(inimg,outimg):#打开图片im = Image.open(inimg)# 图片的宽度和高度img_size = im.sizeprint("图片宽度和高度分别是{}".format(img_size))'''裁剪:传入一个元组作为参数元组里的元素分别是:(距离图片左边界距离x, 距离图片上边界距离y,距离图片左边界距离+裁剪框宽度x+w,距离图片上边界距离+裁剪框高度y+h)'''x = 465y = 535w = 270h = 70region = im.crop((x, y, x + w, y + h))#保存图片region.save(outimg)
这个函数要从完整的聊天界面截图中截出最新的截图消息,效果类似:
函数中的x、y、w、h参数要根据自己的微信窗口调整,可以通过微信的截图工具量尺寸。这种方法很不智能,但没办法,我本来的想法是以微信窗口句柄为父句柄获取其中的子窗口和控件的子句柄,但我使用win32gui中的相关函数寻找时并没有找到,我在网上找了好久有没有其他方法,直到看到:
我也不知道对不对,但我当时就转变思路了,如果可以取子句柄的话,希望有大佬指点一下
调用百度ocr识别文字
def Getmsg(file_path):# 获取access tokentoken = ocr.fetch_token()# 拼接通用文字识别高精度urlimage_url = ocr.OCR_URL + "?access_token=" + tokentext = ""# 读取测试图片file_content = ocr.read_file(file_path)# 调用文字识别服务result = ocr.request(image_url, ocr.urlencode({'image': ocr.base64.b64encode(file_content)}))#print(result)# 解析返回结果result_json = ocr.json.loads(result)for words_result in result_json["words_result"]:text = text + words_result["words"]# 打印文字print('文字识别结果:')print(text)return text
这是调用百度线上的ocr识别文字,要到百度AI开放平台自行开通服务(可以白嫖,B站有教程),技术文档有标准接口代码:
可以直接复制,再在其基础上进行修改。
用于访问智能回答网站获取答案
'''用于访问智能回答网站获取答案'''
import requests #导入requests包,使用爬虫def get_remsg(text):#伪装成浏览器User_Agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36 Edg/93.0.961.47'#线上机器人Furl="https://api.ownthink.com/bot?appid=9ffcb5785ad9617bf4e64178ac64f7b1&spoken="url=Furl+text req=requests.get(url,User_Agent) #向线上机器人提问req.encoding='utf-8' #定义编码格式为utf-8re=eval(req.text) #将返回的内容转成字典类型return re['data']['info']['text'] #返回回答
通过爬虫的方式访问一个线上的聊天机器人,并获得合理的回答😏
发送消息
'''发送消息'''
def send(msg_to_sent,handle,rightx,topy):#将鼠标定位到输入框的位置win32api.SetCursorPos([rightx-400,topy+720])# 强行显示界面win32gui.ShowWindow(handle, win32con.SW_RESTORE)# 将窗口提到最前win32gui.SetForegroundWindow(handle)time.sleep(0.5)pyperclip.copy(msg_to_sent) # 需要发送的内容time.sleep(0.5)pyautogui.hotkey('ctrl', 'v') # 按下 ctrl + v 粘贴内容time.sleep(0.5)pyautogui.hotkey('enter')return 1
这段代码首先利用窗口的右边缘坐标rightx和上边缘坐标topy将鼠标定位到输入框的位置,在不同电脑偏移量可能不一样,自行测量,测量方法参考图片处理(无法获得子句柄,只能用这种方法了)。然后代码会唤醒最小化的窗口,通过pyperclip和pyautogui中的函数模拟键鼠操作。(好像也可以用win32gui.SendMessage(),但我操作时没有达到想要的效果,就换成这种方法了)
主函数
if __name__=='__main__':#获取窗口句柄handle=gethandle('WeChatMainWndForPC','微信')#获取窗口大小信息print(getposition(handle))left, top, right, bot=getposition(handle)#对屏幕进行截图 Screenshot(handle,'my.bmp')#对图片进行处理cropimg('my.bmp','my.bmp')#对图片进行文字识别text=Getmsg('my.bmp')#根据识别内容用爬虫的方法从线上聊天机器人获得回复内容r=get_remsg(text)print('线上机器人的回答:')print(r)#发送消息send(r,handle,right,top)
附加代码(对比图片)
'''对比图片,相似返回True,否则返回False'''
def Compare(img1,img2):#打开两张图片image1=Image.open(img1)image3=Image.open(img2)#把图像对象转换为直方图数据,存在list h1、h2 中h1=image1.histogram()h2=image3.histogram()L1=len(h1)L2=len(h2)if L1>=L2:n=L2else:n=L1sum=0for i in range(n):sum=sum+abs(h1[i]-h2[i])print(sum)result=sum/(len(h1)+len(h2)/2)print(result)if result<=1:return Trueelse:return False
我的完整代码中还有上面这个判断两者图片是否相同的函数,但我的主函数中并没有调用,因为实际自动回复消息肯定时让程序不断运行的,当检测到有新消息才回复。我检测新消息的思路时:对聊天界面截图时,保留上一次的截图,利用上面这个函数对比上次和这次的聊天截图,有差异则判断有消息。
总结
初学win32编程,课余用了三天才写了出来,期间学到了很多,从百度OCR接口到win32自动化操作,再到python爬虫和图片处理,看了好多文章和视频,期间也bug不断。
我这个实现思路肯定是比较初级的,以后争取做出更好的!