Python防微信撤回

要說微信最讓人噁心的發明,消息撤回絕對能上榜。

比如你現在正和女朋友用微信聊着天,或者跟自己喜歡的女孩子聊着天,一個不留神,你沒注意到對方發的消息就被她及時撤回了,這時你很好奇,好奇她到底發了什麼?於是你打算問問她發了什麼,結果她回一句"沒什麼"。這一回復,讓你的好奇心更加強烈了,頓時就感覺消息撤回這一功能就是用來折磨人的。

那麼有沒有什麼辦法能夠知道你心愛的她(他)到底撤回了什麼呢?不要着急,Python幫你搞定。

模塊介紹
本篇文章將用Python實現微信的防撤回功能,針對微信操作,Python有一個十分強大的庫:itchat。相信沒有使用過也有所耳聞吧。官方是這樣描述它的:

Project description
itchat is a open souce wechat api project for personal account.

It enables you to access your personal wechat account through command line.

翻譯過來就是:itchat是一個針對個人帳戶的開放式微信api項目,它使您可以通過命令行訪問您的個人微信帳戶。

既然是針對微信的開發,我們就離不開這個模塊的協助,所以,首先下載該模塊:

pip install itchat
1
也可以在開發工具Pycharm中直接導入該模塊,Pycharm會提示你下載。

模塊初體驗
考慮到應該有些人從來沒有使用過該模塊,這裏對該模塊進行一個簡單的入門。

1、如何登陸微信

既然要操作微信,那麼擺在我們面前的問題就是如何登錄微信,登錄微信非常簡單,直接看代碼:

import itchat

itchat.login()
1
2
3
沒錯,一句代碼即可完成登錄,運行之後就會彈出一個二維碼,掃描之後在手機上授權登錄,控制檯就會提示是否登錄成功。

Login successfully as Y
1
這樣就說明登錄成功了。

這裏需要注意一個問題,就是你會發現每次運行程序都要掃描二維碼登錄,這樣未免太麻煩,有沒有辦法只掃描一次,以後就自動登錄了呢?這當然是可以的。

import itchat

itchat.auto_login(hotReload=True)
1
2
3
通過函數名也能知道該方法可以實現自動登錄,運行程序,掃碼登錄之後會在項目路徑下創建一個itchat.pkl文件,該文件用於存儲登錄的狀態,所以千萬不要動它,如果你想換一個微信賬號登錄,就要先把這個文件刪除,因爲該文件記錄的是上一個微信的狀態,刪除之後即可登錄。

需要注意:這種方式只能保證你在短時間內無需重複登錄,時間長了,還是需要重新掃碼登錄的。

進行到這裏,有些人可能會發現自己的微信登錄不上的情況,據我所知,有些新註冊的微信和長期不使用的微信是無法登錄網頁版微信的,所以這裏也會導致登錄不上。如果登錄不上,那也是沒有辦法的,下面的內容也就沒有意義了。

2、獲取好友列表

登錄上微信之後,我們來用一用itchat模塊提供的一些api,比如獲取好友列表。

import itchat

itchat.auto_login(hotReload=True)
friends = itchat.get_friends()  # 好友列表
print(friends)
1
2
3
4
5
使用get_friends()函數即可獲取到好友列表的所有好友信息,包括暱稱、備註名、地址、個性簽名、性別等等。

這裏我隨意地複製了一個好友的個人信息,當然由於隱私問題,這裏的部分信息我用"*"號代替了,我們重點是分析一下這些信息的內容。比如最開始的UserName,這是用戶的唯一標識,相當於身份證號碼,你的每個好友都會有這樣一個標識,每個好友之間肯定都是不一樣的;然後是NickName,這是好友的暱稱;HeadImgUrl是好友的頭像地址;RemarkName是你對好友的備註名;Province是省份等等,這裏就不一一介紹了,感興趣的話可以自己去了解一下。

3、如何發送消息給好友

如何發送一條消息給指定的好友呢?也非常簡單:

import itchat

itchat.auto_login(hotReload=True)
itchat.send('Hello World', toUserName='@f9e42aafa1175b38b60a0be4d651a34c77f2528d9b7784e7aaf415090eca8fa6')
1
2
3
4
此時的UserName就派上用場了,也就是好友的唯一標識,這樣,我們就給該標識對應的好友發送了一條消息,所以,我們可以這樣改進程序:

import itchat

itchat.auto_login(hotReload=True)
friends = itchat.get_friends()
nickName = '誠信通授權渠道商-老曾'
for i in friends:
    if '誠信通授權渠道商-老曾' == i['NickName']:
        itchat.send('Hello World', toUserName=i['UserName'])
        break

1
2
3
4
5
6
7
8
9
10
這樣,就可以指定發送給任意好友,通過好友的暱稱在好友列表中進行檢索,找到的話,就獲取該好友的UserName,然後發送消息,也可以通過對好友的備註名(RemarkName)查找,大家可以自己嘗試。

4、裝飾器

關於itchat模塊還有很多功能,這裏就不作過多講解了,我們只講關於這次程序的知識點,這裏是最後一個內容,裝飾器。

關於裝飾器,一時半會還講不清楚,這裏只是簡單介紹一下,裝飾器的作用就是用於拓展原來函數功能的一種函數,目的是在不改變原函數名(或類名)的情況下,給函數增加新的功能。

例如現在有一個函數fun(),你並不知曉函數的實現原理,你肯定也不能去修改這個函數的代碼,而你需要給該函數添加一個輸出開始運行時間和結束運行時間的功能,該如何實現呢?這個時候就可以使用裝飾器。

import time
 
def show_time(fun):
    def inner():
        print(time.time())
        fun()
        print(time.time())
    return inner   
 
@show_time
def fun():
    pass
 
fun()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
該如何理解這段程序呢?首先@show_time即是使用一個裝飾器show_time,此時會將裝飾的函數,也就是fun()作爲參數傳遞給裝飾器show_time(),我們知道函數作爲返回值的話,執行的其實是該函數,所以程序會執行內部函數inner(),此時輸出開始運行時間,然後調用fun()函數(原有的功能不能丟),最後輸出結束運行時間。這樣就通過裝飾器實現了一個函數的功能擴展,這也是典型的面向切面編程思想。

如何獲取好友發送的消息
準備工作做完了,接下來就進入正題了,對於上面的知識點,大家一定要掌握,如果不懂的話,接下來的代碼你可能會很懵。

首先,我們看看該如何獲取到好友發送的消息。

import itchat

itchat.auto_login(hotReload=True)


@itchat.msg_register(itchat.content.TEXT)
def resever_info(msg):
    print(msg)


itchat.run() #保持運行

1
2
3
4
5
6
7
8
9
10
11
12
itchat模塊提供了@itchat.msg_register裝飾器來監聽消息,比如這裏我們自定義了一個resever_info()函數,並用裝飾器對消息進行監聽,裝飾器中傳入了itchat.content.TEXT類型,這樣監聽的就是文本消息,監聽到輸入之後,裝飾器就會將文本消息傳入resever_info()的參數中。所以,msg就是監聽到的消息內容。

對於@itchat.msg_register裝飾器,它不僅可以監聽文本,還可以監聽語音、圖片、地圖、名片、視頻等等,爲了方便,這裏我們導入itchat模塊下的content模塊中的全部內容,因爲這些消息類型都是在該模塊下聲明的。

TEXT       = 'Text'
MAP        = 'Map'
CARD       = 'Card'
NOTE       = 'Note'
SHARING    = 'Sharing'
PICTURE    = 'Picture'
RECORDING  = VOICE = 'Recording'
ATTACHMENT = 'Attachment'
VIDEO      = 'Video'
FRIENDS    = 'Friends'
SYSTEM     = 'System'

INCOME_MSG = [TEXT, MAP, CARD, NOTE, SHARING, PICTURE,
    RECORDING, VOICE, ATTACHMENT, VIDEO, FRIENDS, SYSTEM]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
還有要注意的地方,最後記得調用itchat的run()函數,保持程序運行,否則程序就直接結束了。

接下來我們就可以測試一下了,我讓我的好友發了一條消息給我,控制檯就輸出瞭如下內容:


內容很多,我們只挑重要的看。例如FromUserName,這是發送者的標識;ToUserName,這是接收者的標識;Content,這當然就是文本內容了;CreateTime,這是發送時間;注意最後的兩個值:Type,這是消息類型,這裏是文本類型Text,然後Text也是文本內容,所以如果想取出好友發送的消息內容的話,用Content和Text都可以。分析過後,取出內容就很簡單了:

import itchat
import time
from itchat.content import *  # 導入itchat下的content模塊

itchat.auto_login(hotReload=True)


@itchat.msg_register(TEXT)
def resever_info(msg):
    info = msg['Text']  # 取出文本消息
    info_type = msg['Type']  # 取出消息類型
    fromUser = itchat.search_friends(userName=msg['FromUserName'])['NickName']
    ticks = msg['CreateTime']  # 獲取信息發送的時間
    time_local = time.localtime(ticks)
    dt = time.strftime("%Y-%m-%d %H:%M:%S", time_local)  # 格式化日期
    print("發送人:" + fromUser + '\n消息類型:' + info_type + '\n發送時間:' + dt + '\n消息內容:' + info)


itchat.run()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
這裏用到了time模塊,用於格式化日期。

爲了測試方便,我就自己發了一條消息給別人,自己發的消息也是會被監聽的,看運行結果:

發送人:Y
消息類型:Text
發送時間:2019-11-28 16:19:13
消息內容:土鱉

1
2
3
4
5
再來試試語音和圖片能獲取到嗎?我們回到剛纔的代碼:

import itchat
from itchat.content import *  # 導入itchat下的content模塊

itchat.auto_login(hotReload=True)


@itchat.msg_register(TEXT)
def resever_info(msg):
    print(msg)


itchat.run()

1
2
3
4
5
6
7
8
9
10
11
12
13
運行之後,發送語音和圖片試試,不管怎麼發,控制檯就是沒反應,這是當然的了,我們還沒對語音和圖片進行監聽呢,修改代碼:

import itchat
from itchat.content import *  # 導入itchat下的content模塊

itchat.auto_login(hotReload=True)


@itchat.msg_register([TEXT, PICTURE, RECORDING])    #添加了對圖片和語音的監聽
def resever_info(msg):
    print(msg)


itchat.run()

1
2
3
4
5
6
7
8
9
10
11
12
13
再運行試試,先發送一張圖片,再發送一段語音,控制檯輸出了兩段內容,由於篇幅過長,就不貼出來了,無非還是那些信息,發送者,接收者,日期,消息內容等等,這裏只需注意圖片和語音的內容:

'Type': 'Picture', 'Text': <function get_download_fn.<locals>.download_fn at 0x0000000003574158>
'Type': 'Recording', 'Text': <function get_download_fn.<locals>.download_fn at 0x0000000002CFED08>

1
2
3
這是一段地址,通過它我們就能夠將圖片和語音保存起來。

如何保存好友發送的圖片和語音
下面我們對好友發送的圖片和語音進行保存。

import itchat
import os
from itchat.content import *  # 導入itchat下的content模塊

itchat.auto_login(hotReload=True)
temp = 'C:/Users/Administrator/Desktop/CrawlerDemo' + '/' + '撤回的消息'
# 如果不存在該文件夾,就創建
if not os.path.exists(temp):
    os.mkdir(temp)


@itchat.msg_register([TEXT, PICTURE, RECORDING])
def resever_info(msg):
    info = msg['Text']  # 取出文本消息
    info_type = msg['Type']  # 取出消息類型
    name = msg['FileName']  # 取出語音(圖片)文件名

    if info_type == 'Recording':
        # 保存語音
        info(temp + '/' + name)
    elif info_type == 'Picture':
        # 保存圖片
        info(temp + '/' + name)


itchat.run()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
運行起來,然後發送一張圖片和一條語音,就會在指定目錄下生成兩個文件:


如何監聽好友撤回了消息
到這裏,我們其實已經完成了消息監聽,只需要稍加修改即可,但是這個程序是有缺陷的,因爲不是所有消息我們都需要去保存的,好友正常發送過來的消息我們直接就能看到,保存下來不是多此一舉嗎?我們的目的是想知道好友撤回了什麼內容,這就涉及到如何監聽好友是否撤回了消息這一問題了。其實也非常簡單,Content模塊爲我們提供了NOTE類型,該類型指的是系統消息。

所以我們可以自定義一個函數用來監聽系統消息:

import itchat
from itchat.content import *  # 導入itchat下的content模塊


itchat.auto_login(hotReload=True)

@itchat.msg_register(NOTE)
def note_info(msg): # 監聽系統消息
    print(msg)


itchat.run()

1
2
3
4
5
6
7
8
9
10
11
12
13
運行程序,我們撤回一條消息測試一下,輸出結果如下:

......
'DisplayName': '', 'ChatRoomId': 0, 'KeyWord': '', 'EncryChatRoomId': '', 'IsOwner': 0}>, 'Type': 'Note', 'Text': '你撤回了一條消息'}
......

1
2
3
4
這裏截取了部分內容,會發現,撤回消息的文本內容爲"你撤回了一條消息",所以要想知道好友是否撤回了消息就非常簡單了,判斷msg['Text'] == '你撤回了一條消息'即可。

實現微信防撤回程序
關於程序每個步驟的代碼到這裏就分析完了,接下來是對所有代碼的彙總,也是整個程序的完整代碼:

import itchat
from itchat.content import *
import os
import time
import xml.dom.minidom    # 解析xml模塊

# 這是保存撤回消息的文件目錄(如:圖片、語音等),這裏已經寫死了,大家可以自行修改
temp = 'C:/Users/Administrator/Desktop/CrawlerDemo' + '/' + '撤回的消息'
if not os.path.exists(temp):
    os.mkdir(temp)

itchat.auto_login(True)    # 自動登錄

dict = {}    # 定義一個字典


# 這是一個裝飾器,給下面的函數添加新功能
# 能夠捕獲好友發送的消息,並傳遞給函數參數msg
@itchat.msg_register([TEXT, PICTURE, FRIENDS, CARD, MAP, SHARING, RECORDING, ATTACHMENT, VIDEO])  # 文本,語音,圖片
def resever_info(msg):
    global dict    # 聲明全局變量

    info = msg['Text']  # 取出消息內容
    msgId = msg['MsgId']  # 取出消息標識
    info_type = msg['Type']  # 取出消息類型
    name = msg['FileName']  # 取出消息文件名
    # 取出消息發送者標識並從好友列表中檢索
    fromUser = itchat.search_friends(userName=msg['FromUserName'])['NickName']
    ticks = msg['CreateTime']  # 獲取信息發送的時間
    time_local = time.localtime(ticks)
    dt = time.strftime("%Y-%m-%d %H:%M:%S", time_local)  # 格式化日期
    # 將消息標識和消息內容添加到字典
    # 每一條消息的唯一標識作爲鍵,消息的具體信息作爲值,也是一個字典
    dict[msgId] = {"info": info, "info_type": info_type, "name": name, "fromUser": fromUser, "dt": dt}
    

@itchat.msg_register(NOTE)  # 監聽系統提示
def note_info(msg):
    # 監聽到好友撤回了一條消息
    if '撤回了一條消息' in msg['Text']:
        # 獲取系統消息中的Content結點值
        content = msg['Content']
        # Content值爲xml,解析xml
        doc = xml.dom.minidom.parseString(content)
        # 取出msgid標籤的值
        result = doc.getElementsByTagName("msgid")
        # 該msgId就是撤回的消息標識,通過它可以在字典中找到撤回的消息信息
        msgId = result[0].childNodes[0].nodeValue
        # 從字典中取出對應消息標識的消息類型
        msg_type = dict[msgId]['info_type']
        if msg_type == 'Recording':    # 撤回的消息爲語音
            recording_info = dict[msgId]['info']  # 取出消息標識對應的消息內容
            info_name = dict[msgId]['name'] # 取出消息文件名
            fromUser = dict[msgId]['fromUser'] # 取出發送者
            dt = dict[msgId]['dt'] # 取出發送時間
            recording_info(temp + '/' + info_name) # 保存語音
            # 拼接提示消息
            send_msg = '【發送人:】' + fromUser + '\n' + '發送時間:' + dt + '\n' + '撤回了一條語音'
            itchat.send(send_msg, 'filehelper') # 將提示消息發送給文件助手
            # 發送保存的語音
            itchat.send_file(temp + '/' + info_name, 'filehelper')
            del dict[msgId] # 刪除字典中對應的消息
            print("保存語音")
        elif msg_type == 'Text':
            text_info = dict[msgId]['info'] # 取出消息標識對應的消息內容
            fromUser = dict[msgId]['fromUser'] # 取出發送者
            dt = dict[msgId]['dt'] # 取出發送時間
            # 拼接提示消息
            send_msg = '【發送人:】' + fromUser + '\n' + '發送時間:' + dt + '\n' + '撤回內容:' + text_info
            # 將提示消息發送給文件助手
            itchat.send(send_msg, 'filehelper')
            del dict[msgId] # 刪除字典中對應的消息
            print("保存文本")
        elif msg_type == 'Picture':
            picture_info = dict[msgId]['info'] # 取出消息標識對應的消息內容
            fromUser = dict[msgId]['fromUser'] # 取出發送者
            dt = dict[msgId]['dt'] # 取出發送時間
            info_name = dict[msgId]['name'] # 取出文件名
            picture_info(temp + '/' + info_name) # 保存圖片
            # 拼接提示消息
            send_msg = '【發送人:】' + fromUser + '\n' + '發送時間:' + dt + '\n' + '撤回了一張圖片'
            itchat.send(send_msg, 'filehelper') # 將圖片發送給文件助手
            # 發送保存的語音
            itchat.send_file(temp + '/' + info_name, 'filehelper')
            del dict[msgId] # 刪除字典中對應的消息 
            print("保存圖片")


itchat.run()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
這樣,一個完整的防撤回程序就完成了,如果你對於前面的鋪墊能夠掌握得很好的話,這個程序對你來說就是小菜一碟,每一句代碼的註釋我都有寫,應該很容易看懂。

測試程序
到了激動人心的測試環節,我們來測試一下這個程序是否編寫成功了。

我向我的好友發送了三條消息,分別是文本、圖片和語音,接着我一一撤回,然後,微信程序就自動向文件傳輸助手發送了三條消息:


到這裏,這個程序就基本完成了。你們在測試的時候也可以叫自己的好友、同學發給你幾條消息,然後撤回看看是否能夠成功獲取到撤回的消息。

撤回的消息發給別人肯定不行,這樣不僅泄露了隱私,也會騷擾到別人,所以這裏我選擇將撤回的消息發送給文件傳輸助手,如何將消息發送給文件傳輸助手也很簡單:

itchat.send(send_msg, toUserName='filehelper')

1
2
toUserName傳入filehelper即可,這樣,如果對方撤回了消息,你就可以前往文件傳輸助手查看對方究竟撤回了什麼。

說說我遇到的一些坑
這個程序說它難,其實並不難,但我也在編寫的過程中遇到了一些坑,一開始我是一條消息一條消息地進行測試,發現程序是正常的,但我連續撤回幾條消息,卻發現程序出現了Bug。比如我一開始發送了一張圖片和一段文字,結果我撤回這兩條消息後,得到的卻是兩段文字。後面我才醒悟過來,是後面的消息覆蓋了前面的消息,導致了這個結果,所以在程序中,我定義了一個字典,用於存放好友輸入的消息,當監聽到消息被撤回時,就通過撤回消息產生的內容中的msgId去和字典中的匹配,匹配到的就是被撤回的消息,然後進行操作即可。

使用教程
想使用該程序非常簡單,實現微信防撤回程序節點下有程序的完整代碼,直接複製粘貼到你自己的python文件,然後運行該文件即可,運行後會產生一個二維碼,用手機驗證登錄即可。
當然,你也可以選擇將該程序打包成可執行的exe文件,這樣運行更加方便,打包方式:
首先打開cmd窗口,下載pyinstaller模塊,有的話就不用下載了,下載指令:pip insall pyinstaller,此時我們通過cmd窗口進入到python文件目錄,比如我這裏

那就進入到該目錄下:

然後執行下面這條指令:

pyinstaller -F wechat.py
1
後面是需要打包的文件名,執行命令後,就會在文件同級目錄下生成一個dist文件夾。

進入該文件夾,就看到我們的.exe文件了,然後雙擊執行即可。

最後
這個程序目前只實現了監聽好友的文本、圖片、語音類型的消息,對於其它類型的消息,還有羣聊的消息都是無法監聽到的,感興趣的話大家可以自己試着實現一下。

因爲自己也是剛剛接觸這個模塊,文中的程序可能會出現一些意想不到的Bug,但目前我測試來看是沒有問題的,如有問題,歡迎評論區留言。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章