用Python写一个自动连点器脚本

drive:baidu name:自动点击.exe link:https://pan.baidu.com/s/1R7JLevNld_kceSH08BCglg password:dthl

写在前面

事情的起因是我在玩游戏时发现有的游戏对话长 ,不太想过剧情的时候,要一直按空格和鼠标左键,希望能有一个脚本实现这个功能,碰巧在B站上看到一篇写Python写自动点击脚本的文章,上面还有源码,于是乎搭建了Python环境准备试试,试了之后发现可行,于是乎准备给源代码进行一些简单的修饰,好让它更好用,然后不知不觉就搞了一天,中间坑也踩了不少,因此写下此文记录一下这段经历。

环境

名称 版本 供应商
Windows 10 专业版 22H2 Microsoft
Python 3.12
PyCharm 2023.2.4 (Community Edition)

一、安装Python

Python官网

windos版本下载

推荐下载windos直装的版本

二、安装PyCharm

下载地址

下载社区版

汉化插件

文件-->设置-->插件-->下载插件

三、脚本

源码

"""经典的Gui类的写法,使用面向对象的方法"""
import os
import sys

import time

import tkinter as tk
from tkinter import *
from PIL import ImageTk, Image
from typing import TypedDict

import pyautogui
from pynput import keyboard
import threading
import pygame


# 变量定义子类
class ApplicationProps(TypedDict):
    app_alive: bool
    auto_click_on: bool
    curpos: StringVar
    state: StringVar
    xpos: StringVar
    ypos: StringVar
    spacer: StringVar


# 空间子类
class ApplicationComponents(TypedDict):
    label_pos: Label
    mouse_pos: Label
    label_xpos: Label
    xpos_entry: Entry
    label_yset: Label
    ypos_entry: Entry
    label_spacer: Label
    time_entry: Entry
    # label_brief: Label


class Application(Frame):
    children: ApplicationComponents

    def __init__(self, master=None):
        print("\napp开始初始化...")

        super().__init__(master)  # super()代表的是父类的定义,而不是父类对象
        # 导入变量类型
        self.props: ApplicationProps = dict()
        # 声明(StringVar这种需要声明,普通的变量不需要)
        self.props['state'] = StringVar()
        self.props['curpos'] = StringVar()
        self.props['xpos'] = StringVar()
        self.props['ypos'] = StringVar()
        self.props['spacer'] = StringVar()
        # 变量初始化
        self.props['auto_click_on'] = False
        self.props['state'].set("自动点击:关闭")
        self.props['curpos'].set("0,0")
        self.props['xpos'].set("1840")
        self.props['ypos'].set("1061")
        self.props['spacer'].set("1.0")

        self.createWideget()
        # 自定义变量(内部使用)
        self.appalive = True

        print("app初始化完成!")

    def destroy(self):
        print("app关闭")
        self.appalive = False
        time.sleep(0.1)
        print("good bye!")

    # 创建+摆放空间
    def createWideget(self):
        # 创建控件
        img = Image.open(curpath+"back.jpg")
        img = img.resize((150, 150))
        photo = ImageTk.PhotoImage(img)

        # 简介信息
        imglabel = Label(self, image=photo)
        imglabel.image = photo
        imglabel.grid(row=1, column=2, rowspan=3, columnspan=3, padx=30)
        Label(self, text="作者:ke").grid(row=4, column=2, padx=60)
        Label(self, text="q:905142251").grid(row=5, column=2, padx=30)

        label_pos = Label(self, text="当前坐标:", name="label_pos")
        mouse_pos = Label(self, textvariable=self.props['curpos'])  # bg="#ffffff"
        label_state = Label(self, textvariable=self.props['state'])
        label_xpos = Label(self, text="x轴设置")
        xpos_entry = Entry(self, width=10, textvariable=self.props['xpos'])
        label_yset = Label(self, text="y轴设置")
        ypos_entry = Entry(self, width=10, textvariable=self.props['ypos'])
        label_spacer = Label(self, text="点击间隔(秒)")
        time_entry = Entry(self, width=10, textvariable=self.props['spacer'])

        Label(self, text="热键").grid(row=5, column=0, sticky="w")
        Label(self, text="自动点击开关:v").grid(row=6, column=0, sticky="w")
        Label(self, text="更新点击位置:g").grid(row=7, column=0, sticky="w")

        # 摆放控件
        label_pos.grid(row=0, column=0, sticky="w", pady=30)
        mouse_pos.grid(row=0, column=1, sticky="w", pady=10)
        label_state.grid(row=1, column=0, sticky="w", pady=10)
        label_xpos.grid(row=2, column=0, sticky="w", pady=10)
        xpos_entry.grid(row=2, column=1, sticky="w", pady=10)
        label_yset.grid(row=3, column=0, sticky="w", pady=10)
        ypos_entry.grid(row=3, column=1, sticky="w", pady=10)
        label_spacer.grid(row=4, column=0, sticky="w", pady=10)
        time_entry.grid(row=4, column=1, sticky="w", pady=10)


def getpath():
    if hasattr(sys, '_MEIPASS'):
        print("在打包环境中运行")
        path = os.path.join(sys._MEIPASS, '')
    else:
        print("在开发环境中运行")
        path = os.path.join(os.path.dirname(__file__), '')
    return path


def task_click(app):
    time.sleep(0.5)
    while app.appalive:
        app.props["curpos"].set(str(pyautogui.position().x) + "," + str(pyautogui.position().y))
        if app.props["auto_click_on"]:
            try:
                # 1.获得对话框坐标
                x = int(app.props["xpos"].get())
                y = int(app.props["ypos"].get())
                # 2.鼠标移动到对话框
                pyautogui.moveTo(x, y)
                # 3.鼠标点击
                print("click")
                pyautogui.leftClick()
                time.sleep(float(app.props["spacer"].get()))
            except ValueError:
                pass
        time.sleep(0.1)


def on_press(key):
    if key == keyboard.KeyCode(char='v'):
        Mainapp.props["auto_click_on"] = not Mainapp.props["auto_click_on"]
        print("auto_click =", Mainapp.props["auto_click_on"])
        if Mainapp.props["auto_click_on"]:
            sound1.play()
            Mainapp.props["state"].set("状态:启动")
        else:
            sound2.play()
            Mainapp.props["state"].set("状态:关闭")
    if key == keyboard.KeyCode(char='g'):
        sound3.play()
        Mainapp.props["xpos"].set(str(pyautogui.position().x))
        Mainapp.props["ypos"].set(str(pyautogui.position().y))


if __name__ == '__main__':
    # 获取工作路径
    curpath = getpath()
    print("资源路径:" + curpath)

    # 开启GUI
    root = Tk()
    root.iconbitmap(curpath+"icon.ico")
    root.title("自动点击")
    root.geometry("500x400")
    Mainapp = Application(root)
    Mainapp.grid(row=0, column=0, sticky=tk.NSEW, padx=50)

    # 导入资源
    pygame.mixer.init()
    sound1_file = curpath + "successful.mp3"
    sound2_file = curpath + "cancel.wav"
    sound3_file = curpath + "hit.mp3"

    sound1 = pygame.mixer.Sound(sound1_file)
    sound2 = pygame.mixer.Sound(sound2_file)
    sound3 = pygame.mixer.Sound(sound3_file)
    # debug

    # app.props["auto_click_on"] = True
    # print(app.props["auto_click_on"])

    # 开启自动点击线程v
    listener = keyboard.Listener(on_press=on_press)
    listener.start()
    p1 = threading.Thread(target=task_click, args=(Mainapp,))
    p1.start()
    root.mainloop()

功能演示

程序框架

Main:程序执行入口

Application:UI的主对象类,其包含很多子类和控件

Task_click:一个定时触发的线程

Listener:键盘和鼠标的监听器

tkinter控件定义和布局

tkinter官方文档:https://docs.python.org/3/library/tkinter.html

tkinter的三大布局方式

  • pack布局:pack布局是一种按照顺序的布局方式,默认是自上而下居中布局(不同行),也可以改为左右布局(同行)。
  • grid布局:grid布局,按照表格的行列进行布局,把元素放在单元格里,所以这种布局方式很灵活,也比较容易排列整齐。
  • place布局:place布局既可以绝对布局也可以相对布局,灵活性非常高,可以直接指定元素的x、y的坐标(原点为窗口或父容器左上角),注意,如果元素位置有重叠,则后布局的元素会盖住前布局的元素。

详情参考文献[3]

鼠标操作

# 获取鼠标位置
pyautogui.position()	# 进一步可以获取.x或者.y
# 设置鼠标位置
pyautogui.moveTo(x, y)

键盘监听函数

def on_press(key):
    if key == keyboard.KeyCode(char='v'):
    	# 执行当键盘按下v时的代码

线程

# 定义线程,执行task_click()函数,传入参数Mainapp
p1 = threading.Thread(target=task_click, args=(Mainapp,))
# 线程开启
p1.start()

线程里一般用While循环,但别写成死循环,不然当你主窗口关了线程也仍然会运行

使用PyCharm打包

在pycharm下工具栏点击文件 -设置,然后进入Python 解释器,点击+号

搜索pyinstaller安装即可

另一种指令安装的办法:

pip intsall pyinstaller

不过这个方法会出一些问题。

如果你是用的PyCharm图形化界面,建议使用PyCharm内置的打包方法。

获取当前工作路径

def getpath():
    if hasattr(sys, '_MEIPASS'):
        print("在打包环境中运行")
        path = os.path.join(sys._MEIPASS, '')
    else:
        print("在开发环境中运行")
        path = os.path.join(os.path.dirname(__file__), '')
    return path

打包命令(以我的为例)

pyinstaller -w -F -i icon.ico --add-data "icon.ico;." --add-data "successful.mp3;." --add-data "cancel.wav;." --add-data "hit.mp3;." --add-data "back.jpg;." --onefile main.py

拆解分析

pyinstaller 
-w 让运行时不出现cmd弹窗
-F 忘了
-i icon.ico 添加icon.ico图标,必须是.ico格式
--add-data "icon.ico;."	额外的数据1
--add-data "successful.mp3;." 
--add-data "cancel.wav;."
--add-data "hit.mp3;." 
--add-data "back.jpg;." 
--onefile 打包成一个exe文件
main.py	主文件

这个icon指的是外部的图标,内部窗口的图标要在程序内部设置

参考文章:

1.[**PC原神自动过对话python]** 栗二犬 - 哔哩哔哩

2.使用pycharm进行Python项目打包 如何用pycharm打包文件 mob6454cc68daf3 - 51cto博客

3.【Python】tkinter的简单使用(Tk对象、三大布局、变量、事件) 冰冷的希望 - CSDN

4.如何使用Python实现多任务的多线程处理? PHPz - php中文网

5.Pyinstaller打包,彻底解决图片、数据路径问题 朝阳区靓仔_James - CSDN

6.Python Tkinter Grid布局管理器详解(转载网络) 芝麻豆 - 知乎 7.Python Gui之tkinter 渗透者:' - CSDN