一文教會你用Python實現最有效的剪切板實時監控


前言

  上網瀏覽網頁的時候,看見好的內容免不了要使用復制粘貼,但是我們看到的內容、心里想要的內容和實際粘貼后的內容往往不一致。數據的獲取始於復制,終於粘貼,那么問題來了,在這中間系統做了哪些操作,我們怎么能控制它呢?

  人生苦短,我用python,查閱相關資料之后發現有很多不一樣的實現方式,如利用內置ctypes模塊、tk模塊,第三方模塊如跨平台的pyperclip模塊、clipboard模塊、pywin.win32clipboard模塊等等,大部分都封裝好了簡潔易用的高級接口,方便我們直接使用。

person holding sticky note

  基於強迫症的心理,本文分析比較了幾種主流的方式,對他們逐一進行源碼分析、讀寫性能實測,最后選擇了讀寫速度最快的一種做出一個實時剪切板監控小案例,以供大家參考。

小案例實現的功能如下:

  1. 實時監測ctrl+c剪切板寫入事件,去除剪切板中指定字符或文本,如某些文字的后綴 (¬_¬)瞄。
  2. 使用正則對某些文本進行智能替換,如將python2格式的代碼轉換為python3格式。

方式一:調用第三方pyperclip模塊

In [1]: import pyperclip

In [2]: data = pyperclip.paste()

In [3]: data
Out[3]: "print 'Hello World'\r\n————————————————\r\n版權聲明:本文為CSDN博主「...」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。\r\n原文鏈接:https://blog.csdn.net/.../article/details/..."

In [4]: data = data[7:12]

In [5]: pyperclip.copy(data)

In [6]: pyperclip.paste()
Out[6]: 'Hello'

源碼調用: 內置ctypes模塊中的ctypes.windll.user32接口編寫,和pandas包的代碼一致,代碼位置:pandas.io.clipboard.windows,代碼引用如下

import ctypes

windll = ctypes.windll
safeGetClipboardData = CheckedCall(windll.user32.GetClipboardData)
safeGetClipboardData.argtypes = [UINT]
safeGetClipboardData.restype = HANDLE

safeSetClipboardData = CheckedCall(windll.user32.SetClipboardData)
safeSetClipboardData.argtypes = [UINT, HANDLE]
safeSetClipboardData.restype = HANDLE

優點: 跨平台,接口調用方便簡潔

缺點: 剪切板的數據格式只支持utf-8文本,頻繁讀寫速度較慢

方式二:調用第三方win32clipboard模塊

In [1]: import win32clipboard
 ...:
 ...: def clipboard_get():
 ...:  """獲取剪貼板數據"""
 ...:  win32clipboard.OpenClipboard()
 ...:  data = win32clipboard.GetClipboardData()
 ...:  win32clipboard.CloseClipboard()
 ...:  return data
 ...:
 ...: def clipboard_set(data):
 ...:  """設置剪貼板數據"""
 ...:  win32clipboard.OpenClipboard()
 ...:  win32clipboard.SetClipboardData(13, data)
 ...:  win32clipboard.CloseClipboard()
 ...:  return True
 ...:

In [2]: data = clipboard_get()

In [3]: data
Out[3]: "print 'Hello World'\r\n————————————————\r\n版權聲明:本文為CSDN博主「...」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上 原文出處鏈接及本聲明。\r\n原文鏈接:https://blog.csdn.net/.../article/details/..."

In [4]: clipboard_set(data[7:12])
Out[4]: True

In [5]: clipboard_get()
Out[5]: 'Hello'

源碼調用: C源碼封裝,python接口調用如下

def GetClipboardData(*args, **kwargs): # real signature unknown  
  pass

def SetClipboardData(*args, **kwargs): # real signature unknown
  pass

優點: 原生C封裝讀寫速度最快,支持多種剪切板數據格式

缺點: 只適用於windows平台,高頻率讀寫會報錯需要小心處理,utf-8格式之外的數據格式需要熟悉winuser.h庫自行設計編寫

方法三:調用內置tkinter模塊

In [1]: from tkinter import *
 ...:
 ...: r = Tk()

In [2]: data = r.clipboard_get()

In [3]: data
Out[3]: "print 'Hello World'\n————————————————\n版權聲明:本文為CSDN博主「...」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文 出處鏈接及本聲明。\n原文鏈接:https://blog.csdn.net/.../article/details/..."

In [4]: r.clipboard_append(data[7:12])

In [5]: r.clipboard_get()
Out[6]: 'Hello'

注意: 在win10系統測試后發現,使用tkinter模塊只能獲取剪切板數據,不能將數據寫入剪切板,如果在外部調用clipboard_append方法將字符串寫入剪切板后銷毀Tk對象,系統剪切板進程會被tk接管鎖死,此時在其他的應用按ctrl+v,粘貼的應用會直接處於卡死的狀態,或者粘貼后內容為空。

如果還是通過Tk()對象將數據寫入剪切板,只能采取下面的方法,設置延遲銷毀Tk對象,系統剪切板數據才會被更新,否則內容還是為空(實測如果設置0.2秒以內的頻率讀取,剪切板還是為空,這就很雞肋了):

from tkinter import *

import time

r = Tk()
r.withdraw()
r.clipboard_clear()
r.clipboard_append('some string')

r.update()
time.sleep(.2)
r.update()

r.destroy()

源碼調用: C源碼封裝,python接口調用如下

# 讀取剪切板數據:
_tkinter.tkapp('clipboard', 'get')

# 寫入剪切板數據:
_tkinter.tkapp('clipboard', 'append')

剪切板讀寫速度測試結果

img

實時監控小案例:

import win32clipboard
import re
import time


def clipboard_get():
    """獲取剪貼板數據"""
    win32clipboard.OpenClipboard()
    data = win32clipboard.GetClipboardData(win32clipboard.CF_UNICODETEXT)
    win32clipboard.CloseClipboard()
    return data


def clipboard_set(data):
    """設置剪貼板數據"""
    win32clipboard.OpenClipboard()
    win32clipboard.EmptyClipboard()
    win32clipboard.SetClipboardData(win32clipboard.CF_UNICODETEXT, data)
    win32clipboard.CloseClipboard()


# 初始化替換字符列表,相比於正則使用replace函數進行單字符替換更快
char_list = [('(', '('),
             (')', ')'),
             ('“', '"'),
             ('”', '"'),
             ('‘', '\''),
             ('’', '\''),
             ('print ', 'print '),
             ('版權聲明:本文為CSDN', '版權聲明:本文為CSDN'),
             ]

# 預編譯正則替換匹配表達式
# 匹配python2格式的 print函數文本
sub_print = re.compile(r'\bprint\s+(.+)')
# 匹配csdn復制自帶的版權聲明后綴文本
sub_csdn = re.compile(r'—+\s+版權聲明:本文為CSDN.*\s+原文鏈接.*')


# 指定場景 sub替換函數:python2格式的 print函數 替換為python3格式
def sub_fn(s):
    return 'print(' + s.group(1).strip() + ')\r\n'


# 判斷如果沒有要替換的字符則返回None,有則執行替換操作,先進行字符列表replace,再執行reg.sub(sub_fn, txt)
def char_replace_reg_sub(txt):
    new_txt = txt

    # 對字符列表中字符 逐一判斷,如果字符在文本中 則replace替換,如果都不在 則return None,不用再進行替換操作
    i = 0
    for old_char, new_char in char_list:
        if old_char in new_txt:
            i += 1
            new_txt = new_txt.replace(old_char, new_char)
    if i == 0:
        return None

    print('-' * 150, '\n【After char replace】:', new_txt)
    # 對指定場景替換 使用正則re.sub
    new_txt = sub_print.sub(sub_fn, new_txt)
    new_txt = sub_csdn.sub('', new_txt)
    print('【After sub replace:】', new_txt)
    return new_txt


def main():
    """后台腳本:每隔0.2秒,讀取剪切板文本,檢查有無指定字符或字符串,如果有則執行替換"""
    # recent_txt 存放最近一次剪切板文本,初始化值只多執行一次paste函數讀取和替換
    recent_txt = clipboard_get()
    replaced_txt = char_replace_reg_sub(recent_txt)
    clipboard_set(recent_txt if replaced_txt is None else replaced_txt)

    while True:
        # txt 存放當前剪切板文本
        txt = clipboard_get()

        # 剪切板內容和上一次對比如有變動,再進行內容判斷,判斷后如果發現有指定字符在其中的話,再執行替換
        if txt != recent_txt:
            # print(f'txt:{txt}')
            new_txt = char_replace_reg_sub(txt)  # 沒查到要替換的子串,返回None

            if new_txt is not None:
                clipboard_set(new_txt)
                # 更新 recent_txt 為替換之后的文本,便於下次與 txt 剪切板文本對比,判斷內容有無更新
                recent_txt = new_txt

        # 檢測間隔(延遲0.2秒)
        time.sleep(0.2)


if __name__ == '__main__':
    main()

運行效果:

-----------------------------------------------------------------------------------------
【Copy text】:

print 'Hello World' \r\n————————————————\r\n版權聲明:本文為CSDN博主「...」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上 原文出處鏈接及本聲明。\r\n原文鏈接:https://blog.csdn.net/.../article/details/...)
-----------------------------------------------------------------------------------------
【After replace:】:

print('Hello World')
-----------------------------------------------------------------------------------------

參考鏈接:

微軟開發文檔:https://docs.microsoft.com/zh-cn/windows/win32/dataxchg/using-the-clipboard?redirectedfrom=MSDN#_win32_Copying_Information_to_the_Clipboard

Stack Overflow:https://stackoverflow.com/questions/579687/how-do-i-copy-a-string-to-the-clipboard-on-windows-using-python


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM