使用Wireshark与Python解析USB键盘流量:从数据包到按键的完整实战 1. 项目概述从USB数据流到键盘敲击的秘密如果你对计算机安全、逆向工程或者仅仅是好奇你的键盘敲击在电脑里到底经历了什么感兴趣那么“分析USB键盘流量”绝对是一个能让你豁然开朗的实战项目。这听起来可能有点黑客范儿但本质上它是一次深入理解计算机底层通信协议的绝佳实践。我们每天都在用USB键盘但很少有人去探究当我们按下“A”键时那一串0和1是如何从键盘的微控制器出发穿过USB线缆最终被操作系统识别为一个字符的。这个项目的核心就是使用网络封包分析领域的“瑞士军刀”——Wireshark来捕获并解析这些原始的USB通信数据。Wireshark虽然常被用来分析网络流量但其强大的协议解析能力同样适用于USB接口。我们将捕获键盘与主机之间的原始交互数据然后像侦探一样从一堆看似杂乱无章的十六进制字节中还原出每一次按键和释放的动作。更有趣的是为了将这个过程自动化我们还会编写一个完整的Python脚本它能自动读取Wireshark捕获的数据包文件.pcapng并像翻译密码本一样将数据流实时转换成我们可以读懂的按键序列。这个教程适合谁呢如果你是信息安全爱好者想了解硬件层面的攻击与防御比如BadUSB攻击的原理如果你是嵌入式或物联网开发者需要调试自定义的USB HID设备或者你只是一个充满好奇心的极客想揭开日常技术背后的神秘面纱——那么这个从理论到工具再到自动化脚本的完整流程将为你提供一套可直接上手复现的方法论。整个过程不需要昂贵的硬件只需要一台电脑、一个USB键盘、Wireshark软件和Python环境。接下来我们就从原理开始一步步拆解这个有趣的过程。2. 核心原理与前置知识拆解在动手抓包之前我们必须先搞清楚我们要抓的是什么以及为什么能抓到。这涉及到对USB协议特别是HID人机接口设备协议的基本理解。如果你只关心操作步骤可以快速浏览本节但理解这些背景能让你在分析数据时知其然更知其所以然遇到异常数据包时也能有自己的排查思路。2.1 USB HID协议与键盘报告描述符USB键盘属于USB设备大类中的“HID”类。HID设备的通信核心是“报告”。键盘不会每次按下一个键就发送一个字母“A”的ASCII码而是会定期例如每1毫秒到10毫秒向主机发送一个数据结构这个结构叫做“报告描述符”。报告描述符定义了数据包的格式而具体每次发送的数据包内容就是一个“输入报告”。对于一个标准键盘它的输入报告通常是一个8字节的数据包。这个结构是固定的字节0 修饰键位图。每一位代表一个修饰键如Ctrl、Shift、Alt、GUI/Win键是否被按下。例如左Ctrl键按下对应位0置1。字节1 保留字节通常为0。字节2-7 普通按键码。最多可以同时报告6个普通按键这就是为什么标准键盘有“6键无冲”的说法。每个位置存储一个按键的“Usage ID”这是HID协议定义的一个数值对应物理按键的位置。例如键盘上的“A”键的Usage ID是0x04。这里有一个关键点报告里传递的是按键的“位置ID”而不是字符。按下“A”键发送0x04按下“B”键发送0x05。至于这个0x04最终是显示为小写‘a’、大写‘A’还是其他字符完全由主机操作系统当前的键盘布局和修饰键状态来决定。我们的分析脚本核心工作之一就是建立从Usage ID到常见字符的映射字典。2.2 Wireshark如何捕获USB流量你可能会有疑问Wireshark不是抓网络包的吗怎么能抓到USB总线上的数据这得益于一个叫做usbpcap的底层驱动或过滤器。当我们安装Wireshark时通常会附带安装USBPcap组件。它的作用是在Windows系统内核层面为指定的USB主机控制器安装一个嗅探驱动将所有经过该控制器的USB请求块数据复制一份并传递给Wireshark的用户态程序进行处理。因此捕获过程需要管理员权限因为它涉及内核驱动。在Wireshark的捕获接口列表中你会看到类似“USBPcap1”这样的选项对应着你电脑上的某一个USB根集线器。选择它就能开始捕获所有连接到这个集线器上的USB设备流量。这就像在一条USB总线上安装了一个透明的监听器。注意 开启USB捕获会生成海量数据因为你的鼠标、键盘、USB耳机等所有设备的数据都会被捕获。因此我们必须使用捕获过滤器来精确地只抓取目标键盘的数据否则几秒钟内你的捕获文件就会巨大无比并且充斥着无关噪音。3. 实战环境准备与精准捕获配置工欲善其事必先利其器。这一部分我们完成所有准备工作并实现针对目标键盘的精准流量捕获。3.1 软件安装与检查首先确保你安装了正确版本的软件Wireshark 从官网下载并安装最新稳定版。在安装过程中务必勾选“Install USBPcap”组件。这是捕获USB流量的关键。Python环境 需要Python 3.6及以上版本。我们将使用pyshark和pandas库。pyshark是一个Python封装库允许我们以编程方式解析pcap文件它背后调用的依然是Wireshark的解析引擎tshark。安装命令如下pip install pyshark pandas安装完成后以管理员身份运行Wireshark。这是必须的否则USBPcap驱动无法加载你在接口列表里看不到USB捕获选项。3.2 识别目标键盘与配置捕获过滤器这是最关键的一步目的是在混杂的USB总线中只抓我们关心的键盘数据。连接键盘并识别端点 将你要分析的USB键盘连接到电脑。在Wireshark的捕获接口列表里你会看到多个“USBPcap”开头的接口。通常新插入的设备会产生明显的URBUSB请求块提交事务你可以通过插拔键盘来观察哪个接口上有新的URB_SUBMIT事件出现从而确定键盘所在的接口。记下这个接口编号比如USBPcap1。获取键盘的设备地址 更精确的方法是先进行一次短暂2-3秒的无过滤捕获。在捕获到的数据中寻找GET DESCRIPTOR请求其Device字段会显示一个地址比如2.4.0。这个地址由三部分组成总线号.设备号.端点号。对于键盘的输入报告我们关心的是中断输入端点通常是端点1方向为IN。所以你的键盘的完整输入端点地址可能类似于2.4.1。这个地址是动态分配的每次连接可能不同但一次会话中不变。设置捕获过滤器 在Wireshark捕获界面的捕获过滤器栏输入以下格式的过滤器usb.addr 2.4.1这个过滤器的含义是只捕获源地址或目标地址为2.4.1的USB流量。由于键盘是向主机地址为host发送数据所以数据包的源地址是2.4.1目标地址是host符合过滤条件。这样就完美过滤掉了同一USB总线上其他所有设备如鼠标、U盘的干扰数据。实操心得 如果你无法确定准确的usb.addr一个更宽泛但有效的过滤器是usb.transfer_type 0x01。0x01代表中断传输而键盘数据正是通过中断传输的。这可能会抓到同一总线上其他HID设备如鼠标的数据但比抓全部流量要好得多。你可以在捕获后再用显示过滤器usb.src “2.4.1”进行二次筛选。3.3 执行捕获与触发按键配置好过滤器和接口后点击“开始捕获”。为了获得有分析价值的数据你需要有计划地触发一些按键。基础按键 依次按下并释放a,b,c,1,2,3。这有助于我们确认普通按键的映射。修饰键组合 尝试Shift a(应输出 ‘A’)Ctrl c。这能帮助我们验证修饰键位图字节的解析是否正确。长按与多键 按住一个键不放观察是否持续产生数据包。同时按下多个普通键如a,s,d看报告中的多个按键槽是如何被填充的。特殊功能键 按下CapsLock,Enter,Space观察它们的Usage ID。输入一段有意义的文本比如你的邮箱或“Hello, Wireshark!”然后停止捕获。将捕获到的数据保存为一个.pcapng文件例如keyboard_trace.pcapng。至此我们获得了分析的“原材料”。4. 手动解析Wireshark捕获数据在让脚本自动化之前我们最好亲手在Wireshark界面里解析几个包这能让你对数据结构有最直观的感受也是后续调试脚本的基础。4.1 定位与解读键盘输入报告包在捕获文件中使用显示过滤器usb.src “2.4.1” usb.transfer_type 0x01来精确定位键盘发出的中断输入数据包。点击一个这样的数据包在下方协议详情面板中层层展开展开USB URB层 查看Endpoint确认是INTERRUPT INLeftover Capture Data字段显示了数据净荷的长度键盘报告通常是8字节。展开HID Data层 这是Wireshark解析后的关键层。你会看到类似这样的结构HID Data (8 bytes) [Destination: Host] [Source: 2.4.1] Leftover Capture Data: 0800000000000000注意这里的Leftover Capture Data就是原始的8字节报告以十六进制显示。例如0800000000000000。解读报告字节字节0 (08) 二进制是00001000。查阅HID使用表可知位3从0开始数对应左GUI键即Windows键。这说明这个报告可能是在我按下Win键时捕获的。如果按下Shift该字节会是02左Shift或20右Shift。字节1 (00) 保留字节忽略。字节2 (00) 第一个普通按键槽。如果是00表示该槽位没有按键。如果按下‘a’键这里会是0x04。字节3-7 (00 00 00 00 00) 其余五个按键槽。4.2 分析按键按下与释放序列USB键盘的协议是状态报告而不是事件报告。这意味着按下按键 当按下一个新键时该键的Usage ID会出现在报告的一个空闲槽位字节2-7中第一个为0的位置中同时报告被发送。释放按键 当松开一个键时该键的Usage ID会从报告槽位中移除变回0x00报告再次被发送。空报告 当没有任何普通键和修饰键被按下时报告为0000000000000000。因此要还原输入过程你需要按时间顺序追踪一系列报告。比如报告1:0000000000000000(空闲状态)报告2:0204000000000000(字节00x02左Shift按下字节20x04 ‘a’键按下)报告3:0004000000000000(字节0变回0x00Shift释放但‘a’键仍按下)报告4:0000000000000000(‘a’键释放回归空闲)这个序列就代表了输入一个大写‘A’的过程按下Shift - 按下a - 释放Shift - 释放a。操作系统会综合这些状态变化输出字符‘A’。5. 编写Python自动化解析脚本手动分析几个包还行但面对成百上千个数据包我们需要自动化工具。下面我们来编写一个功能完整的Python脚本。这个脚本将使用pyshark来读取pcapng文件解析每个键盘报告并输出可读的按键序列。5.1 脚本结构与核心逻辑脚本的核心工作流程是加载文件 - 迭代数据包 - 过滤USB中断输入包 - 提取报告字节 - 解析报告 - 将状态变化转换为按键事件 - 输出。首先我们定义关键的映射字典。HID Usage ID到字符的映射不是唯一的它取决于键盘布局。我们这里定义一个适用于最常见US布局的映射。import pyshark from collections import defaultdict import sys # --- 核心映射字典定义 --- # HID Usage ID 到 字符无修饰键的映射 (US Layout) keycode_to_char { 0x04: ‘a’, 0x05: ‘b’, 0x06: ‘c’, 0x07: ‘d’, 0x08: ‘e’, 0x09: ‘f’, 0x0A: ‘g’, 0x0B: ‘h’, 0x0C: ‘i’, 0x0D: ‘j’, 0x0E: ‘k’, 0x0F: ‘l’, 0x10: ‘m’, 0x11: ‘n’, 0x12: ‘o’, 0x13: ‘p’, 0x14: ‘q’, 0x15: ‘r’, 0x16: ‘s’, 0x17: ‘t’, 0x18: ‘u’, 0x19: ‘v’, 0x1A: ‘w’, 0x1B: ‘x’, 0x1C: ‘y’, 0x1D: ‘z’, 0x1E: ‘1’, 0x1F: ‘2’, 0x20: ‘3’, 0x21: ‘4’, 0x22: ‘5’, 0x23: ‘6’, 0x24: ‘7’, 0x25: ‘8’, 0x26: ‘9’, 0x27: ‘0’, 0x28: ‘\n’, # Enter 0x29: ‘\x1b’, # ESC 0x2A: ‘\b’, # Backspace 0x2B: ‘\t’, # Tab 0x2C: ‘ ‘, # Space # 更多键位可以按需添加如标点符号、功能键等 } # 修饰键位掩码定义 (报告字节0) MODIFIER_LEFT_CTRL 0x01 MODIFIER_LEFT_SHIFT 0x02 MODIFIER_LEFT_ALT 0x04 MODIFIER_LEFT_GUI 0x08 # Win键 MODIFIER_RIGHT_CTRL 0x10 MODIFIER_RIGHT_SHIFT 0x20 MODIFIER_RIGHT_ALT 0x40 MODIFIER_RIGHT_GUI 0x80 # 特殊功能键映射不直接输出字符但可打印其名称 special_keys { 0x39: ‘CapsLock‘, 0x3A: ‘F1‘, 0x3B: ‘F2‘, 0x3C: ‘F3‘, 0x3D: ‘F4‘, # ... 其他功能键 }5.2 数据包解析与状态跟踪我们需要一个类或全局状态来跟踪上一次的报告以便检测出按键的“按下”和“释放”事件。class KeyboardState: def __init__(self): self.previous_report [0] * 8 # 存储上一次的8字节报告 self.current_modifiers 0 # 当前修饰键状态 self.caps_lock_on False def parse_report(self, report_bytes): 解析8字节报告返回事件列表 events [] if len(report_bytes) 8: return events # 忽略长度不对的报告 current_modifiers report_bytes[0] key_codes report_bytes[2:8] # 普通按键槽 # 1. 处理修饰键变化 modifier_changes self.current_modifiers ^ current_modifiers if modifier_changes: # 判断每个修饰键是按下还是释放 for mask, key_name in [ (MODIFIER_LEFT_SHIFT, ‘[LShift]‘), (MODIFIER_RIGHT_SHIFT, ‘[RShift]‘), (MODIFIER_LEFT_CTRL, ‘[LCtrl]‘), (MODIFIER_RIGHT_CTRL, ‘[RCtrl]‘), (MODIFIER_LEFT_ALT, ‘[LAlt]‘), (MODIFIER_RIGHT_ALT, ‘[RAlt]‘), (MODIFIER_LEFT_GUI, ‘[LWin]‘), (MODIFIER_RIGHT_GUI, ‘[RWin]‘) ]: if modifier_changes mask: if current_modifiers mask: events.append((‘down‘, key_name)) else: events.append((‘up‘, key_name)) self.current_modifiers current_modifiers # 2. 处理普通按键变化 (简化比较本次和上次的按键槽) previous_keys set(self.previous_report[2:8]) current_keys set(key_codes) # 新按下的键 for key in (current_keys - previous_keys): if key ! 0: # 忽略0值槽位 events.append((‘down‘, key)) # 被释放的键 for key in (previous_keys - current_keys): if key ! 0: events.append((‘up‘, key)) # 3. 处理CapsLock等切换键需要特殊处理因其是触发式 if 0x39 in key_codes and 0x39 not in self.previous_report[2:8]: self.caps_lock_on not self.caps_lock_on events.append((‘toggle‘, ‘[CapsLock]‘)) # 保存当前报告用于下一次比较 self.previous_report list(report_bytes) return events5.3 主循环与输出逻辑主函数负责读取pcap文件应用显示过滤器并将解析出的事件转换为可读输出。def main(pcapng_file): print(f“[*] 开始解析捕获文件: {pcapng_file}“) print(“[*] 只过滤USB中断输入包 (usb.transfer_type 0x01)“) # 使用pyshark读取文件应用BPF过滤器提高效率 cap pyshark.FileCapture( pcapng_file, display_filter‘usb.transfer_type 0x01‘, # 关键只读中断传输包 use_jsonTrue, include_rawTrue ) state KeyboardState() output_text [] try: for pkt in cap: # 确保是USB包且有HID数据 if hasattr(pkt, ‘usb‘) and hasattr(pkt.usb, ‘hid_data‘): # 从HID Data层获取原始载荷 # pyshark的字段名可能是 ‘usbhid.data‘ 或 ‘hid.data‘取决于版本 raw_data_field None for field in [‘usbhid.data‘, ‘hid.data‘, ‘usb.hid_data‘]: if hasattr(pkt.usb, field): raw_data_field getattr(pkt.usb, field) break if raw_data_field: # 字段值可能是十六进制字符串如 ‘0800000000000000‘ hex_str raw_data_field.replace(‘:‘, ‘‘).replace(‘ ‘, ‘‘) if len(hex_str) 16: # 至少8字节 try: report_bytes bytes.fromhex(hex_str[:16]) # 取前8字节 events state.parse_report(report_bytes) for event_type, key in events: if event_type ‘down‘: # 判断输出字符 char_to_output None if isinstance(key, int): # 普通按键 if key in keycode_to_char: base_char keycode_to_char[key] # 应用Shift和CapsLock逻辑 (简化版) if state.current_modifiers (MODIFIER_LEFT_SHIFT | MODIFIER_RIGHT_SHIFT): char_to_output base_char.upper() if base_char.isalpha() else base_char # 简单处理实际需查Shift映射表 elif state.caps_lock_on and base_char.isalpha(): char_to_output base_char.upper() else: char_to_output base_char elif key in special_keys: char_to_output f‘[{special_keys[key]}]‘ else: char_to_output f‘[Key:0x{key:02X}]‘ else: # 修饰键名如 [LShift] char_to_output key if char_to_output: output_text.append(char_to_output) # 实时打印到控制台 print(char_to_output, end‘‘, flushTrue) except ValueError as e: # 忽略十六进制解析错误 pass except KeyboardInterrupt: print(“\n[*] 用户中断解析。“) finally: cap.close() print(f“\n\n[*] 解析完成。最终输出文本”) print(‘’.join(output_text)) if __name__ ‘__main__‘: if len(sys.argv) ! 2: print(“用法: python usb_keyboard_parser.py path_to_pcapng_file“) sys.exit(1) main(sys.argv[1])这个脚本已经具备了核心功能。运行它指定你捕获的pcapng文件路径它就会在控制台模拟输出捕获到的按键序列。6. 脚本优化与高级解析技巧基础的脚本能工作但在实际应用中可能会遇到各种边界情况。下面分享几个优化点和高级技巧让你的解析器更健壮、更实用。6.1 处理复杂按键与布局映射我们之前的映射字典非常基础。现实中需要处理更多情况Shift组合键 数字键上的符号如!#$%^*()和标点符号在按下Shift时输出不同的字符。你需要一个shifted_keymap字典。shifted_keymap { 0x1E: ‘!‘, # 键‘1‘ Shift - ‘!‘ 0x1F: ‘‘, # 键‘2‘ Shift - ‘‘ 0x20: ‘#‘, 0x21: ‘$‘, 0x22: ‘%‘, 0x23: ‘^‘, 0x24: ‘‘, 0x25: ‘*‘, 0x26: ‘(‘, 0x27: ‘)‘, 0x2D: ‘_‘, # - - _ 0x2E: ‘‘, # - # ... 更多映射 }在输出逻辑中如果检测到Shift按下优先查询shifted_keymap。国际键盘布局 上述映射仅适用于US布局。对于AZERTY、QWERTZ等布局你需要替换整个keycode_to_char映射字典。一个更专业的做法是将布局定义放在外部配置文件中。媒体键与系统键 很多键盘有音量调节、播放暂停等键。这些键通常有独立的Usage Page和Usage ID不在我们定义的普通键盘页面0x07里。你需要在解析时检查Usage Page并扩展你的映射字典来处理它们。6.2 提高解析性能与准确性使用tshark直接输出JSONpyshark在解析大文件时可能较慢因为它需要启动子进程并解析XML。一个更高效的方法是直接使用Wireshark的命令行工具tshark并将其输出解析为JSON。import subprocess, json command [ ‘tshark‘, ‘-r‘, pcapng_file, ‘-Y‘, ‘usb.transfer_type 0x01‘, # 显示过滤器 ‘-T‘, ‘json‘, ‘-e‘, ‘frame.time_epoch‘, ‘-e‘, ‘usb.src‘, ‘-e‘, ‘usb.dst‘, ‘-e‘, ‘usbhid.data‘ ] result subprocess.run(command, capture_outputTrue, textTrue) packets json.loads(result.stdout) # 然后遍历packets列表提取‘usbhid.data‘字段这种方法速度更快且对字段的访问更直接。处理数据包乱序与丢包 USB是可靠传输但在捕获层面极少数情况下数据包可能因缓冲等问题顺序错乱。更健壮的状态机应该基于数据包的时间戳frame.time_epoch进行排序而不是依赖捕获文件的顺序。验证报告长度 不是所有HID设备的报告都是8字节。在脚本开头可以检查前几个有效报告的长度动态确定报告长度而不是硬编码为8。6.3 可视化与高级输出除了输出文本你还可以将解析结果进行可视化时间线图 利用数据包的时间戳绘制每个按键按下和释放的时间点可以直观看到输入节奏和间隔这在侧信道分析中很有用。热力图 统计各个按键的使用频率生成一张键盘热力图。会话还原 将输出保存为文件并附上时间戳形成一个可搜索的“输入日志”。7. 常见问题排查与实战心得在实际操作中你几乎一定会遇到下面这些问题。这里我把踩过的坑和解决方案总结出来希望能帮你节省大量调试时间。7.1 捕获不到任何数据包症状 Wireshark的USBPcap接口列表是空的或灰色的。排查权限问题 必须使用管理员身份运行Wireshark。驱动未安装 重新运行Wireshark安装程序确保勾选了USBPcap。安装后可能需要重启电脑。系统安全策略 某些企业环境或安全软件可能阻止安装未经签名的内核驱动。检查系统事件查看器是否有驱动加载错误。7.2 捕获到海量无关数据包症状 开始捕获后数据包列表飞速滚动瞬间产生数万个包其中大部分不是键盘的。解决务必使用捕获过滤器 在开始捕获前在输入框设置usb.addr x.x.x或usb.transfer_type 0x01。这是最有效的过滤手段数据在进入捕获缓冲区前就被丢弃了。确认设备地址 通过插拔设备观察Device地址的变化确保过滤器中的地址准确。7.3 Python脚本无法解析或输出乱码症状 脚本运行不报错但输出的字符全是乱码或根本不是按下的键。排查步骤检查原始数据 在Wireshark中手动查看几个你知道对应按键的数据包。记下你按下‘a’键时HID Data层的8字节十六进制值。确保你的脚本读取到的字节序列与之完全一致。验证映射字典 确认你按下的键的Usage ID是否在你的keycode_to_char字典中。标准‘a’键是0x04。如果字典里是0x1E那输出肯定是‘1’。检查修饰键逻辑 单独按下‘a’输出‘a’但按下‘Shifta’却输出‘A’了吗如果没有检查current_modifiers的更新逻辑和shifted_keymap的应用逻辑。调试状态机 在parse_report函数中加入打印语句输出每次的report_bytes、current_modifiers和计算出的events与Wireshark中的包顺序对比看状态跟踪是否准确。7.4 如何处理同时按下的多个键我们的基础脚本通过比较集合差集来处理按键按下和释放这能正确处理大多数情况。但对于非常快速的滚按或特定的游戏按键序列如“左右左”快速交替可能会因为报告采样率的问题丢失中间状态。一个更精确但复杂的方法是维护一个“当前按下键的集合”完全根据每个报告来更新这个集合报告里有的键就是被按下的报告里没有的就是被释放的。这要求主机每次报告都准确反映了所有键的状态而标准USB键盘协议正是这样设计的。7.5 安全与隐私考量最后必须强调一点这个技术是一把双刃剑。防御视角 理解它是为了防范“键盘记录器”这种恶意硬件或软件。一些高级的BadUSB设备可以伪装成键盘直接向系统注入按键序列。通过监控异常的USB HID设备连接和流量可以起到一定的检测作用。攻击视角 它本身就是一个软件键盘记录器的原理演示。因此请仅在你拥有完全所有权和控制权的设备上进行此实验切勿用于监控他人的输入这在绝大多数地区都是非法的。实操建议 在虚拟机中进行捕获和分析是更安全、更干净的选择。你可以将USB键盘直接传递给虚拟机然后在虚拟机内进行捕获这样宿主机系统的其他USB流量不会造成干扰。

相关新闻

最新新闻

基于大数据的共享单车调度优化与热力分析

基于大数据的共享单车调度优化与热力分析

1. 项目概述:当共享单车遇上大数据去年夏天,我在杭州街头连续三天看到同一辆共享单车停在小区门口,车篮里积满了雨水。这个画面让我意识到,共享单车的调度问题远比想象中复杂。这正是我们团队选择"基于大数据的共享单车数据分…

2026/7/30 10:15:38
2026年大数据分析软件推荐:性能与安全对比

2026年大数据分析软件推荐:性能与安全对比

大数据分析早已不是大厂的专属需求。随着企业数据量普遍增长,越来越多中型企业也面临同样的挑战:数据量上了亿级、数据源从三五个变成了十几个、分析场景从几张固定报表变成了多部门并发自助查询。此时选一款能扛得住数据量和并发压力、同时满足安全合规…

2026/7/30 10:15:38
vscode 可视化 git history commit 对比两个特定 commit

vscode 可视化 git history commit 对比两个特定 commit

vscode 的可视化 git history commit 只能看单个 commit 的修改。 我想看到从 commit1 -> commit 100 的整合修改,但又不想用 git rebase 去 squash 这些 commits,有办法吗? 回答,首先在可视化 git history 那里,选…

2026/7/30 10:15:38
告别手动保存:Python微博图片批量下载工具,让你的收藏效率提升10倍

告别手动保存:Python微博图片批量下载工具,让你的收藏效率提升10倍

告别手动保存:Python微博图片批量下载工具,让你的收藏效率提升10倍 【免费下载链接】weibo-image-spider 微博图片爬虫,极速下载、高清原图、多种命令、简单实用。 项目地址: https://gitcode.com/gh_mirrors/we/weibo-image-spider 还…

2026/7/30 10:15:38
ChatGPT镜像站安全吗?域名、请求链路与隐私风险检查指南

ChatGPT镜像站安全吗?域名、请求链路与隐私风险检查指南

ChatGPT镜像站安全吗?域名、请求链路与隐私风险检查指南 搜索“ChatGPT镜像”时,经常能看到界面相似、功能描述相近的第三方网页。这些页面是否安全,不能靠页面长相、回复速度或浏览器地址栏里的小锁判断。比较可靠的做法是检查域名归属、TL…

2026/7/30 10:15:38
储能涨价的另一面:当硬件红利退场,运营红利才刚刚开始

储能涨价的另一面:当硬件红利退场,运营红利才刚刚开始

2026年7月,一条消息在储能行业里传得很快。 314Ah电芯成交价从6月的0.30元/Wh,跳到了0.33-0.36元/Wh——一个多月涨了超过20%。如果从5月初0.25-0.28元/Wh的低点算起,涨幅已经超过30%。 涨价的原因并不复杂。价格战打到今年二季度&#xff0c…

2026/7/30 10:10:38

月新闻