文庆反射一招轻 發表於 2020-5-17 13:34:00

python监听、操作键盘鼠标库pynput详细教程

<h1 id="-000-前言">§ 0.0.0 前言</h1>
<p>监听、操作鼠标、键盘是实现自动化的捷径,比如我实现自动化签到用到了模拟键盘操作。</p>
<p>pynput是监听、操控鼠标和键盘的<strong>跨平台</strong>第三方python库。<br>
你可以通过<code>pip install pynput</code>来安装。安装时会自动下载依赖库。</p>
<p>pypi链接在此。</p>
<p>接下来我会按<br>
“鼠标按键”“监听鼠标”“控制鼠标”,“键盘按键”“监听键盘”“控制键盘”<br>
的顺序介绍它的用法。</p>
<hr>
<hr>
<ul>
<li>以下为正文。</li>
</ul>
<hr>
<hr>
<h1 id="100-鼠标按键">§1.0.0 鼠标按键</h1>
<p>鼠标的按键在<code>pynput.mouse.Button</code>中,<br>
有<code>lift</code>、<code>right</code>、<code>middle</code>还有<code>unknown</code>四种。</p>
<p>每一个按键都有两个有意义的属性:<code>name</code>和<code>value</code>。<br>
name是该按键的名称,比如 Button.left.name == 'left';<br>
value是记录上一次点击位置的元组。</p>
<hr>
<hr>
<h1 id="110-监听鼠标">§1.1.0 监听鼠标</h1>
<p>有两种方法,一种是函数式、非阻塞型,另一种是语句式、阻塞型。</p>
<p>先说第一种,这种是常见的教程所说的方法。</p>
<h2 id="111-pynputmouselistener">§1.1.1 pynput.mouse.Listener</h2>
<p>以下是官网的说明示例:</p>
<pre><code class="language-python">import pynput, time

def on_move(x, y):
    print('Pointer moved to {0}'.format((x, y)))

def on_click(x, y, button, pressed):
    print('{0} at {1}'.format(
      'Pressed' if pressed else 'Released',
      (x, y)))
    if not pressed:
      # Stop listener
      return False

def on_scroll(x, y, dx, dy):
    print('Scrolled {0} at {1}'.format(
      'down' if dy &lt; 0 else 'up',
      (x, y)))

# Collect events until released
with pynput.mouse.Listener(
      on_move=on_move,
      on_click=on_click,
      on_scroll=on_scroll) as listener:
    listener.join()
</code></pre>
<p>运行这段代码时,移动鼠标会显示其坐标,<br>
按下鼠标按键并松开后,程序结束。</p>
<ul>
<li>当三个函数任意一个返回<code>False</code>((还有就是释放<strong>Exception或继承自Exception的异常</strong>)时,就会结束进程。</li>
<li>可以用<code>listener.start()</code>和<code>listener.stop()</code>代替with语句。</li>
</ul>
<h2 id="112-pynputmouseevents">§1.1.2 pynput.mouse.Events</h2>
<p>个人认为,这个方法比上一个更直观。</p>
<pre><code class="language-python">import pynput

with pynput.mouse.Events() as event:

    for i in event:
    #迭代用法。
      if isinstance(i, pynput.mouse.Events.Move):
            #鼠标移动事件。
            print(i.x, i.y)
            #不要直接打印`i`,模块这里有问题,会报错。

      elif isinstance(i, pynput.mouse.Events.Click):
            #鼠标点击事件。
            print(i.x, i.y, i.button, i.pressed)
            #这个i.button就是上文所说的“鼠标按键”中的一个,用is语句判断即可。

      elif isinstance(i, pynput.mouse.Events.Scroll):
            #鼠标滚轮。
            print(i.x, i.y, i.dx, i.dy)


      break
   
    i = event.get(1)
    #另一种用法。
    #默认值是None。
    #这个`1`就是最长等待时间,超过这个时间没有事件,
    #就会报错。错误类型是queue模块的Empty,而非TimeoutError。
</code></pre>
<hr>
<hr>
<h1 id="120-控制鼠标">§1.2.0 控制鼠标</h1>
<ul>
<li>§ 1.2.1<br>
先执行<code>pynput.mouse.Controller()</code>获取控件。<br>
<strong>以下方法都是该控件的所属方法。</strong></li>
</ul>
<p>如下:</p>
<pre><code class="language-python">import pynput

ctr = pynput.mouse.Controller()
</code></pre>
<hr>
<ul>
<li>§ 1.2.2</li>
</ul>
<p>动态属性<code>position</code><strong>返回鼠标位置坐标</strong>的元组(像这样: (x, y) ),<br>
通过定义来<strong>改变鼠标位置</strong>,比如<code>ctr.position = (500, 500)</code>。</p>
<hr>
<ul>
<li>§ 1.2.3</li>
</ul>
<p>当然,也有<code>move</code>方法,用于<strong>移动鼠标</strong>,<br>
用法是<code>ctr.move(dx, dy)</code>。</p>
<hr>
<ul>
<li>§ 1.2.4</li>
</ul>
<p>使用方法<code>click</code>进行<strong>模拟点击</strong>,需提供点击的按钮,<br>
按钮在<code>pynput.mouse.Button</code>里,有<code>left</code>、<code>right</code>和<code>middle</code>。<br>
还有可选参数<code>count</code>,是点击次数,默认为1。</p>
<p>示例:</p>
<pre><code class="language-python">import pynput

ctr = pynput.mouse.Controller()

ctr.click(pynput.mouse.Button.left)
#左键单击。

ctr.click(pynput.mouse.Button.left, 2)
#左键双击。

ctr.click(pynput.mouse.Button.right)
#右键单击。
</code></pre>
<hr>
<ul>
<li>§ 1.2.5</li>
</ul>
<p>使用<code>press(button)</code><strong>按下</strong>button键;<br>
方法<code>release(button)</code><strong>释放</strong>键,如果操作时按键并没有被按下,也不会报错。</p>
<p>示例:</p>
<pre><code class="language-python">import pynput

ctr = pynput.mouse.Controller()

ctr.press(pynput.mouse.Button.left)
#按下左键。

ctr.move(50, 0)
#右移50单位。

ctr.move(0, 50)
#下移50单位。

ctr.release(pynput.mouse.Button.left)
#释放左键。
</code></pre>
<hr>
<ul>
<li>§ 1.2.6</li>
</ul>
<p><strong>模拟滚轮</strong>,使用的方法是<code>scroll</code>,提供参数<code>dx</code>和<code>dy</code>。</p>
<p>例:</p>
<pre><code class="language-python">import pynput

ctr = pynput.mouse.Controller()

ctr.scroll(0, 50)
#向上滚动50单位。

ctr.scroll(0, -50)
#向下滚动50单位。
</code></pre>
<hr>
<hr>
<ul>
<li>以上是鼠标操作,</li>
<li>以下是键盘操作。</li>
</ul>
<hr>
<hr>
<h1 id="-200-键盘按键">§ 2.0.0 键盘按键</h1>
<p>键盘的按键获取比鼠标的麻烦些,但是没有鼠标用得多。<br>
因此我先说给常用的<em>使用方法</em>,再说<em>获取</em>。</p>
<hr>
<h2 id="-201-使用方法">§ 2.0.1 使用方法</h2>
<p>首先,当获取事件后,要判断按键是“<em>特殊按键</em>”还是“<em>普通按键</em>”,<br>
需判断其是否具有<code>name</code>属性,有则是<em>特殊按键</em>。<br>
这个属性记录属性的名称,比如<code>ctrl</code>对应着<code>'ctrl'</code>、<code>'ctrl_l'</code>或是<code>'ctrl_r'</code>。<br>
<em>普通按键</em>中,取而代之的是<code>.char</code>。</p>
<blockquote>
<p>注:大写字母与小写字母有不同的按键。</p>
</blockquote>
<p>还有其他功能。这里就不多说了。</p>
<h2 id="-202-获取">§ 2.0.2 获取</h2>
<p>首先,<strong>特殊按键</strong>在<code>pynput.keyboard.Key</code>“模块”中可以直接找到。<br>
比如<code>ctrl</code>对应<code>pynput.keyboard.Key.ctrl</code>还有<code>.ctrl_l</code>以及<code>.ctrl_r</code>。</p>
<p>然后,<strong>普通按键</strong>可以通过<code>pynput.keyboard.KeyCode.from_char</code>取得(特殊按键不可以,使用时会出现ArgumentError)。<br>
如<code>a</code>可以运行<code>pynput.keyboard.KeyCode.from_char('a')</code>获得。</p>
<p>二者都可以用<code>pynput.keyboard.KeyCode.from_vk</code>通过按键的映射码取得。</p>
<hr>
<hr>
<h1 id="-210-监听键盘">§ 2.1.0 监听键盘</h1>
<p>主要有两种方法,类似于鼠标的,我的讲述顺序同前文。<br>
还有一种是对Listener的封装,用于快捷键,我放在最后一个说。</p>
<h2 id="-211-pynputkeyboardlistener">§ 2.1.1 pynput.keyboard.Listener</h2>
<blockquote>
<p>注:如果你只想关注个别按键而非所有事件,可以使用<code>GlobalHotKeys</code>。我会在后文说。</p>
</blockquote>
<p>官网示例:</p>
<pre><code class="language-python">from pynput import keyboard

def on_press(key):
    '按下按键时执行。'
    try:
      print('alphanumeric key {0} pressed'.format(
            key.char))
    except AttributeError:
      print('special key {0} pressed'.format(
            key))
    #通过属性判断按键类型。

def on_release(key):
    '松开按键时执行。'
    print('{0} released'.format(
      key))
    if key == keyboard.Key.esc:
      # Stop listener
      return False

# Collect events until released
with keyboard.Listener(
      on_press=on_press,
      on_release=on_release) as listener:
    listener.join()
</code></pre>
<ul>
<li>当两个函数中任意一个返回<code>False</code>(还有就是释放<strong>Exception或继承自Exception的异常</strong>)时,就会结束进程。</li>
<li>可以用<code>listener.start()</code>和<code>listener.stop()</code>代替with语句。</li>
</ul>
<h2 id="-212-pynputkeyboardevents">§ 2.1.2 pynput.keyboard.Events</h2>
<pre><code class="language-python">
import pynput

with pynput.keyboard.Events() as event:

    for i in event:
    #迭代用法。
      key_event = i
      break
   
    key_event = event.get()
    #get用法。
    #可以提供一个实数作为最长等待时间(单位秒),超过这个时间没有事件,
    #就会报错。错误类型是queue模块的Empty,而非TimeoutError。

#判断事件情况:

if isinstance(key_event, pynput.keyboard.Events.Press):
    print('按下按键', end = '')
elif isinstance(key_event, pynput.keyboard.Events.Release):
    print('松开按键', end = '')

#判断按键:

#*这个事件的`key`属性*对应才是*Listener方法获得的按键`'key'`*。

try:
    print(key_event.key.name)
except AttributeError:
    #说明这个是普通按键。
    print(key_event.key.char)
else:
    #两种判断方式,第一种是我自创的,第二种是官网上的。
    if (key_event.key.name).startswith('ctrl'):
      #通过名称判断。
      print('发生了ctrl键事件。')
    elif key_event.key is pynput.keyboard.Key.esc:
      print('发生了esc键事件。')
</code></pre>
<h2 id="-213-pynputkeyboardglobalhotkeys">§ 2.1.3 pynput.keyboard.GlobalHotKeys</h2>
<p>(还有'pynput.keyboard.HotKey'可以实现相似功能,但很麻烦)</p>
<p>官网示例,esc那个是我写的。</p>
<pre><code class="language-python">from pynput import keyboard

def on_activate_h():
    print('&lt;ctrl&gt;+&lt;alt&gt;+h pressed')

def on_activate_i():
    print('&lt;ctrl&gt;+&lt;alt&gt;+i pressed')

def esc():
    print('&lt;esc&gt; pressed')
    return False

def esc_shift():
    print('&lt;esc&gt;+&lt;shift&gt; pressed')
    raise Exception

with keyboard.GlobalHotKeys({
      '&lt;ctrl&gt;+&lt;alt&gt;+h': on_activate_h,
      '&lt;ctrl&gt;+&lt;alt&gt;+i': on_activate_i,
      '&lt;esc&gt;':          esc,
      '&lt;esc&gt;+&lt;shift&gt;':esc_shift}) as h:
    h.join()
</code></pre>
<p>当按下esc键时,函数被触发,但运行未停止。<br>
观察源码,发现虽然该类继承自Listener,但会对执行的函数有封装,封装后只返回None。<br>
所以无法通过return False结束进程。</p>
<p>设置单个普通按键(比如“a”)也是可以的。</p>
<p>不能分辨按键顺序如“&lt;ctrl&gt;+a”与“a+&lt;ctrl&gt;”。</p>
<hr>
<hr>
<h1 id="-220-控制键盘">§ 2.2.0 控制键盘</h1>
<ul>
<li>§ 2.2.1</li>
</ul>
<p>先获取控件 :<code>ctr = pynput.keyboard.Controller()</code>。<br>
<strong>以下所说的方法均是指该控件的方法。</strong></p>
<hr>
<ul>
<li>§ 2.2.2</li>
</ul>
<p>通过方法<code>press</code>来<strong>按下按键</strong>,<br>
你需要提供一个“<em>长度为1的字符</em>”或是“<em>前文所说的按键对象</em>”。</p>
<blockquote>
<p>温馨提示,此方法测试有风险,如果发现电脑打字、操作异常,<br>
很可能是因为模拟按下了某个键未松开。<br>
可以重启控制台或电脑。</p>
</blockquote>
<hr>
<ul>
<li>§ 2.2.3</li>
</ul>
<p>通过<code>release</code><strong>释放按键</strong>,<br>
和press方法一样,需要提供一个“<em>长度为1的字符</em>”或是“<em>前文所说的按键对象</em>”。</p>
<p>示例:</p>
<pre><code class="language-python">'''
这段程序会按“下ctrl+shilf+s”快捷键,停顿3秒后按下esc键。
简单模拟了“另存为”操作。
'''

import pynput, time

ctr = pynput.keyboard.Controller()

ctr.press(pynput.keyboard.KeyCode.from_vk(17))
#通过按键的映射码 按下ctrl键。

ctr.press(pynput.keyboard.Key.shift)
#通过按键对象 按下shift键。

ctr.press('s')
#通过长度为1的字符 按下s键。


#扫尾,释放刚才按下的键。后面我会说更简单、优雅的办法。
ctr.release(pynput.keyboard.Key.ctrl)
ctr.release(pynput.keyboard.Key.shift)
ctr.release('s')

time.sleep(0.3)

ctr.press(pynput.keyboard.Key.esc)
ctr.release(pynput.keyboard.Key.esc)
</code></pre>
<hr>
<ul>
<li>§ 2.2.4</li>
</ul>
<p><code>pressed</code>方法就是我说的“更简单、优雅”的方法。<br>
使用时提供要按下的键,再用<code>with</code>语句“封装”上。<br>
效果是<strong>进入语句块时</strong><em>顺序</em>按下提供按键,<strong>退出语句块时</strong><em>逆序</em>释放按键。</p>
<p>如下:</p>
<pre><code class="language-python">import pynput, time

ctr = pynput.keyboard.Controller()

with ctr.pressed(
      pynput.keyboard.Key.ctrl,
      pynput.keyboard.Key.shift,
      's'):
    pass

time.sleep(0.3)

with ctr.pressed(pynput.keyboard.Key.esc):
    pass
</code></pre>
<hr>
<ul>
<li>§ 2.2.5</li>
</ul>
<p><code>type</code>在英语中除了“类型”还有“打字”之意。<br>
该方法接收字符串,然后打出每个字符。<br>
据测试,向它提供一个“按键列表”也可以正常使用。</p>
<p>例:</p>
<pre><code class="language-python">import pynput

ctr = pynput.keyboard.Controller()

ctr.type('Hello world!')

ctr.type()
#按下esc再松开。
</code></pre>
<hr>
<hr>
<ul>
<li>以上是正文。</li>
</ul>
<hr>
<hr>
<h1 id="一些建议">一些建议</h1>
<p>该模块鼠标、键盘的监听操作均由多线程实现。<br>
所以,可以利用多线程共享内存的特征编程;二要注意不要频繁启动监听,这样对系统开支极大。</p>
<p>还有就是对鼠标位置监听要求不是很高时(比如实时向用户显示鼠标位置),可以每次循环sleep一下。<br>
如果向<code>Listener</code>提供的函数会执行很久(比如含有sleep),会导致电脑变卡。</p>
<h1 id="闲谈">闲谈</h1>
<p>之前和别人聊天,他说他在不需要时把电脑的麦拔下来、摄像头挡上。<br>
我说用Fn快捷键禁掉麦克风更方便。<br>
现在想想,黑客可以通过模拟按键解禁麦克风啊。<br>
虽然这个模块没有Fn键,但别的呢?</p>
<h1 id="最后">最后</h1>
<p>这份算是最全的了。原创不易,纯手打。</p>
<p>转载请注明作者和链接哦。</p>
<p>感谢阅读!</p><br><br>
来源:https://www.cnblogs.com/tobe-goodlearner/p/tutorial-pynput.html
頁: [1]
查看完整版本: python监听、操作键盘鼠标库pynput详细教程