Pywinauto:Windows 桌面应用的 Python 自动化神器

Pywinauto:Windows 桌面应用的 Python 自动化神器

文章目录

文章目录

前言

一、Pywinauto 介绍

1. Pywinauto 是什么?

2. Pywinauto 的主要特点

二、使用步骤

1. 安装 Pywinauto

2. Pywinauto 的常见操作

1. 打开应用程序

2. 连接已打开的应用程序

3. 定位窗口

4. 窗口操作

5. 定位控件

6. 等待

7. 控件的操作

8. 鼠标操作

9. 键盘操作

10. 菜单控件的操作

11. 列表控件的操作

三、自动化场景示例 - 微信发消息

1. 实现思路

2. 注意事项

2. 代码实现


前言

Pywinauto是一个用于自动化Windows图形用户界面(GUI)的Python库。文章介绍了Pywinauto的核心功能,包括跨GUI框架支持、简单易用的API和录制回放功能。详细讲解了安装步骤和常见操作,如启动/连接应用程序、定位窗口和控件、窗口操作、等待机制、控件交互、鼠标键盘操作以及菜单和列表控件处理。最后以微信发消息为例,演示了完整的自动化测试场景实现。Pywinauto通过模拟用户操作实现Windows应用程序自动化测试,大幅提升GUI测试效率。


一、Pywinauto 介绍

1. Pywinauto 是什么?

Pywinauto 是一个用于自动化 Windows 图形用户界面(GUI)的 Python 库。它允许你通过编程方式控制 Windows 应用程序,模拟用户操作(如点击按钮、输入文本、选择菜单等)。

想象你有一个机器人助手,可以代替你操作电脑上的任何软件——点击按钮、输入文字、选择菜单,就像真人操作一样。Pywinauto 就是这样一个能让 Python 控制 Windows 应用程序的"机器人控制器"。它的核心思想就是用代码代替鼠标和键盘。

2. Pywinauto 的主要特点

1. 跨 GUI 框架支持:

  • Win32 API (backend='win32'):适用于 MFC、VB6、WinForms 等。
  • MS UI Automation (backend='uia'):适用于 WPF、Qt、Modern UI(UWP)等。

2. 简单易用的 API

  • 支持通过窗口标题、类名、控件类型等属性定位元素。
  • 提供类似自然语言的链式调用(如 window.Edit.type_keys('hello, world'))。

3. 录制和回放功能

可以使用 inspect.exe 或者 UISpy.exe 辅助识别控件。

二、使用步骤

1. 安装 Pywinauto

使用如下指令在 pycharm 的命令行窗口进行 pywinauto 的安装和检查:

# 安装

pip install pywinauto



# 检查是否安装成功

pip list

安装完成后,使用 pip list 指令,打印如下信息,表示安装成功:

2. Pywinauto 的常见操作

1. 打开应用程序

语法:

  • app = Application(backend='uia').start(self, cmd_line...)

参数解释:

  • cmd_line:启动应用程序的命令行字符串,必须包含应用程序的路径和名称;路径可以是相对路径,也可以是绝对路径;
  • 例如:启动记事本,可以写 "notepad.exe",也可以写 "C:\Windows\notepad.exe";

示例:

# 启动应用程序 app = Application(backend='uia').start('notepad.exe')

2. 连接已打开的应用程序

语法:

  • app = Application(backend='uia').connect(process=12345)
  • app = Appllication(backend='uia').connect(handle=66666)

参数解释:

  • process:目标的进程 ID,可通过进程 ID 连接应用程序;
  • handle:目标的窗口句柄;

示例:

# 连接打开的应用程序 app = Application(backend='uia').connect(process=27076) app = Application(backend='uia').connect(handle=66666)

3. 定位窗口

可以结合 print_control_identifires() 方法,根据打印的控件信息,协助定位窗口;

语法:

  • win = app.window(title='...')
  • win = app.best_match名称

window() 方法参数解释:

  • title:文本为指定的元素;
  • title_re:文本匹配指定正则表达式的元素;
  • best_match:标题与指定值相似的元素;
  • class_name:窗口类为指定值的元素;
  • class_name_re:类名匹配正则表达式的元素;

示例:

# 精确匹配 notepad1 = app.window(title='Hello,Pywinauto! - Notepad') # 正则匹配 notepad2 = app.window(title_re='.*Notepad') # bestmatch匹配 notepad3 = app.window(best_match='Hello,Pywinauto! - Notepad') # 精确匹配 notepad4 = app.window(class_name='Notepad') # 正则匹配 notepad5 = app.window(class_name_re='.*Notepad') 

注意:通过 best_match 的方法定位窗口是不推荐的,因为控件的名称中间有可能会有空格,逗号或者其它的特殊符号,使用上述语法会出现报错,因此不推荐。

4. 窗口操作

常用的窗口操作的方法如下:

  • close():关闭窗口;
  • maximize():将窗口最大化;
  • minimize():将窗口最小化;
  • restore():窗口恢复正常大小;
  • get_show_state():获取窗口的显示状态,返回 0 表示正常,1 表示最大化,2 表示最小化;
  • is_dialog():检查控件是否是顶级窗口;
  • is_maximized():检查窗口是否处于最大化状态;
  • is_minimized():检查窗口是否处于最小化状态;
  • is_normal():检查窗口是否处于正常状态;

示例:

# 连接打开的应用程序 app = Application(backend='uia').connect(process=22860) # 定位窗口 win = app.window(title_re='.*Notepad') # 等待窗口可见 win.wait('visible') # 窗口最大化 time.sleep(3) win.maximize() assert win.is_maximized() # 窗口最小化 time.sleep(3) win.minimize() assert win.is_minimized() # 窗口恢复正常 time.sleep(3) win.restore() assert win.is_normal() # 关闭窗口 win.close()

5. 定位控件

控件的定位方法:

  1. 可以使用 print_control_identifiers() 方法,打印窗口及其子空间的标识符信息;
  2. 使用 UISpy 工具,定位控件;
  3. 结合 UISpy 的控件的标识符信息,在打印的信息中搜索,完成对控件的准确定位;

常见的控件类型如下:

分类控件名称说明
窗口与对话框对话框(Dialog)
用于与用户交互,如显示警告、确认操作或者输入信息。
窗格(Pane)通常作为窗口的一部分,用于显示特定内容或功能。
输入控件按钮(Button)用于触发操作,如点击按钮执行某个功能。
编辑栏(Edit)用于输入或编辑文本,支持多行或单行输入。
组合框(ComboBox)结合文本框和列表框,用户可以选择预定义选项或输入自定义值。
列表框(ListBox)用于显示可选择的列表项目,支持单选或多选。
菜单控件菜单(Menu)用于提供功能选项,通常位于窗口顶部。
菜单项(MenuItem)菜单中的具体选项,点击后执行对应的功能。
弹出菜单(PopupMenu)右键单击时弹出的菜单,用于快速访问常用功能。
列表显示控件列表显示控件(ListView)用于以表格形式显示数据,支持多列显示和排序。
容器控件组框(GroupBox)用于对控件进行分组,提高界面的可读性和组织性。
选择控件复选框(CheckBox)用于多选操作,用户可以勾选多个选项。
单选框(RadioButton)用于互斥选择,用户只能选择一个选项。
显示控件状态栏(StatusBar)通常位于窗口底部,用于显示应用状态或者提示信息。
静态内容(Static)用于显示静态文本或者图像,通常不可编辑。
导航控件树状视图(TreeView)用于展示分层数据,如文件夹结构或组织结构。
选项卡控件(TabControl)用于在有限的空间内组织多个页面或选项卡,每个选项卡可以包含不同的内容。
工具控件工具栏(ToolBar)用于放置常用按钮或工具,方便用户快速操作。
工具提示(ToolTips)当鼠标悬停在控件上时显示提示信息,帮助用户理解控件功能。
头部内容头部内容(Header)通常用于显示标题或者表头信息,如表格的列标题。

打印信息示例:

Dialog - '无标题 - Notepad'    (L0, T0, R0, B0)
['无标题 - Notepad', 'Dialog', '无标题 - NotepadDialog', 'Dialog0', 'Dialog1']
child_window(title="无标题 - Notepad", control_type="Window")
   | 
   | Pane - ''    (L-31988, T-31852, R-30897, B-30874)
   | ['无标题Pane', 'Pane', '无标题Pane0', '无标题Pane1', 'Pane0', 'Pane1']
   |    | 
   |    | Document - ''    (L-31988, T-31852, R-30897, B-30874)
   |    | ['无标题Document', 'Document']
   | 
   | Pane - ''    (L-31908, T-31982, R-31334, B-31918)
   | ['无标题Pane2', 'Pane2']
   |    | 
   |    | Pane - ''    (L-31908, T-31982, R-31334, B-31918)
   |    | ['无标题Pane3', 'Pane3']
   |    |    | 
   |    |    | TabControl - ''    (L-31908, T-31982, R-31334, B-31918)
   |    |    | ['无标题TabControl', 'TabControl添加新标签页', 'TabControl']
   |    |    | child_window(auto_id="Tabs", control_type="Tab")
   |    |    |    | 
   |    |    |    | ListBox - ''    (L-31904, T-31982, R-31408, B-31918)
   |    |    |    | ['ListBox', '无标题ListBox']
   |    |    |    | child_window(auto_id="TabListView", control_type="List")
   |    |    |    |    | 
   |    |    |    |    | TabItem - '无标题. 未修改。'    (L-31900, T-31982, R-31408, B-31918)
   |    |    |    |    | ['TabItem', '无标题. 未修改。', '无标题. 未修改。TabItem']
   |    |    |    |    | child_window(title="无标题. 未修改。", control_type="TabItem")
   |    |    |    |    |    | 
   |    |    |    |    |    | Static - '无标题'    (L-31874, T-31966, R-31801, B-31935)
   |    |    |    |    |    | ['Static', '无标题Static', '无标题', 'Static0', 'Static1']
   |    |    |    |    |    | child_window(title="无标题", control_type="Text")
   |    |    |    |    |    | 
......

打印的控件信息建议保存下来,避免多次打印;

定位控件的语法(以按钮为例):

  • save_btn = win['保存']
  • save_btn = child_window(auto_id='...', control_type='Button', ...)

定位控件常用的属性:

  • title:控件名称;
  • auto_id:自动化 id;
  • control_type:控件类型;
  • found_index:元素索引;

定位记事本的最小化按钮 示例:

# 连接应用程序 app = Application(backend='uia').connect(title_re='.*Notepad') # 获取窗口 notepad = app.window(title_re='.*Notepad') # 使用 best_match 定位最小化按钮 minimize_btn_1 = notepad['最小化'] # 使用 child_window() 定位最小化按钮 minimize_btn_2 = notepad.child_window( auto_id='Minimize', control_type='Button' )

注意:

  • 定位控件可能会定位到多个控件;
  • 如果定位到多个控件,可以使用参数 found_index,指本次定位的是第几个控件;
  • found_index 从 0 开始计数;

6. 等待

语法:

  • win.wait(wait_for, timeout=None, retry_interval=None)
  • win.wait_not(wait_for, timeout=None, retry_interval=None)
  • wait_until(timeout, retry_interval, func, value=True)

参数解释:

  •  wait_for:表示窗口的状态:
  • timeout:表示超时
  • retry_interval:表示重试时间间隔,单位为 s;
  • func:执行的函数;
  • value:比较的值; 

wait_for 状态:

  • exists:表示窗口是一个有效的句柄;
  • visible:表示窗口不隐藏,可以看到;
  • enable:表示窗口未被禁用,可以操作;
  • ready:表示窗口可见且已启用;
  • active:表示窗口处于活动状态;

示例1:

# 计算器 # 1. 测试 enable # 连接打开的窗口 app = Application(backend='uia').connect(process=15652) # 定位窗口 win = app.window(title='计算器') win.wait('visible') win.print_control_identifiers() # 定位控件 mem_btn = win.child_window(title="清除所有记忆", auto_id="ClearMemoryButton", control_type="Button") mem_btn.wait_not('enabled') mem_btn.wait('enabled') # 2. 测试 ready (visible + enabled) # 定位控件 clr_btn = win.child_window(title="清除条目", auto_id="clearEntryButton", control_type="Button") clr_btn.wait('ready') assert clr_btn.is_visible() assert clr_btn.is_enabled() # 定位控件 mem_btn = win.child_window(title="清除所有记忆", auto_id="ClearMemoryButton", control_type="Button") assert mem_btn.is_visible() assert not mem_btn.is_enabled() # 3. 测试 active # 先激活窗口 - 通过找到数字控件, 并点击的方式 num_btn = win.child_window(title="一", auto_id="num1Button", control_type="Button") num_btn.wait('ready') num_btn.click_input() # 激活窗口 - 通过使用 set_focus() win.set_focus() win.wait('active')

示例2:

# 测试 wait_until() def get_window(): # 连接打开的窗口 app = Application(backend='uia').connect(process=15652) # 定位窗口 win = app.window(title='计算器') return win.is_visible() wait_until(3, 0.5, get_window, True)

7. 控件的操作

点击操作语法(以按钮为例):

  • btn.click_input():单击控件
  • btn.right_click_input():右击控件
  • btn.double_click_input():双击控件

示例:

# 双击 OneNote 标题栏 title_bar = win.child_window(title="‪控件信息‬ - OneNote", control_type="TitleBar") title_bar.double_click_input()

文本操作语法:

  • texts():获取窗口或者控件所有文本内容,返回列表,每个元素是一个字符串,表示一个文本片段;
  • window_text():表示窗口或控件的主要显示的文本内容;

示例:

# 1. 连接 OneNote app = Application(backend='uia').connect(process=27944) # 2. 定位窗口 win = app.window(title_re='.*OneNote.*') win.wait('visible') # 打印控件文本 funcs = win.child_window(title="功能区选项卡", control_type="Tab") print(funcs.texts()) print(funcs.window_text())

8. 鼠标操作

当需要灵活的鼠标交互时,控件的点击操作就无法满足需求,因此 Pywinauto 提供了 mouse 模块,用于模拟用户真实的鼠标事件。

语法:

  • mouse.click(coords=(x, y)):单击指定的坐标;
  • mouse.scroll(coords=(x, y), wheel_dist=1):滚动鼠标滚轮;
  • mouse.double_click(coords=(x, y)):双击指定的坐标;
  • mouse.right_click(coords=(x, y)):右键点击指定的坐标;
  • mouse.move(coords=(x, y)):移动鼠标到指定坐标;
  • mouse.wheel_click(coords=(x, y)):鼠标中键点击指定的坐标;
  • mouse.press(coords=(x, y)):按下鼠标按钮;
  • mouse.release(coords=(x, y)):释放鼠标按钮;

参数解释:

  • coords:表示鼠标要操作的坐标;
  • wheel_dist:表示滚动的距离, 大于 0  是向上滚动, 小于 0  是向下滚动;

示例:

# 使用灵活的鼠标交互方式, 实现点击计算器的数字键 # 1. 连接计算器 calc = Application(backend='uia').connect(process=15652) # 2. 定位顶层窗口 win = calc.window(title='计算器') # win.print_control_identifiers() win.wait('visible') # 3. 定位数字区 nums_pad = win.child_window(title='数字键盘', auto_id='NumberPad') # 4. 循环遍历数字键, 依此点击 for key in nums_pad.children(): mid_point = key.rectangle().mid_point() mouse.click(coords=(mid_point.x, mid_point.y)) time.sleep(1)

9. 键盘操作

Pywinauto 提供了强大的键盘操作功能,keyboard 模块是核心组件之一;

输入文本语法:

  • send_keys('...'):在焦点窗口输入文本;
  • type_keys(keys, pause=None, with_spaces=False, with_newlines=False):在控件的编辑区输入文本;

参数解释:

  • keys:要输入的键序列;
  • pause:每次按键后的延迟时间;
  • with_spaces:True 表示为在输入的字符串中保留空格;
  • with_newlines:True 表示为在输入的字符串中保留换行符;

send_keys() 可以将案件序列发送到具有焦点的窗口,但是实际应用中往往需要在控件中输入,而不依赖窗口的焦点状态,因此 type_keys() 应用更广泛;

示例:

from pywinauto.keyboard import send_keys # 在焦点窗口输入文本 time.sleep(1) send_keys('1234567') # type_keys() 输入文本 # 1. 连接应用程序 app = Application(backend='uia').connect(process=14356) # 2. 定位窗口 win = app.window(title_re='.*OneNote.*') win.wait('visible') # 3. 输入 win.type_keys(keys='1234567') # 延迟输入 win.type_keys(keys='好好学习, 天天向上', pause=1) # 空格 win.type_keys(keys='好好学习, 天天向上', with_spaces=True) # 换行 win.type_keys(keys='hello\nworld', with_newlines=True)

常用的虚拟键码:

按键代码
Enter{ENTER}
Tab{TAB}
Backspace{BACKSPACE}
Esc{ESC}
方向键{UP},{DOWN},{LEFT},{RIGHT}
F1~F9{F1}~{F9}
Shift+
Ctrl^
Alt

%

指定重复次数:

可为特殊键指定重复次数,如 {ENTER 2} 表示按两次 Enter 键。

转义特殊字符:

使用 {} 包裹特殊字符(如 {+},{%},{^}),以避免被识别为修饰符。

示例:

# 虚拟按键 # Enter - {ENTER} win.type_keys(keys='abcd{ENTER}efg') win.type_keys(keys='abcd{ENTER 10}efg') # Tab - {TAB} win.type_keys(keys='静夜思{TAB}李白') win.type_keys(keys='静夜思{TAB 10}李白') # Backspace - {BACKSPACE} win.type_keys(keys='{BACKSPACE}') win.type_keys(keys='{BACKSPACE 100}') # Shift win.type_keys(keys='+2') # Ctrl win.type_keys(keys='^a') # 1 + 2 = 3 win.type_keys(keys='1 {+} 2 = 3', with_spaces=True)

10. 菜单控件的操作

语法:

  • items():返回对话框的菜单项,如果没有,返回空列表;
  • item_by_index():返回指定的菜单项;
  • item_by_path(path, exact=False):查找路径上指定的菜单项;
  • menu_select():用户查找指定路径的菜单项;

参数解释:

  • path:用户指定要选择的菜单项路径;路径可以是 "MenuItem -> MenuItem -> MenuItem..." 形式的字符串,每个 MenuItem 是菜单该级别的项目文本,例如:File->Save,空格不重要,可以写也可以省略; 
  • exact:设置为 True,表示要求菜单项名称与路径中的名称完全匹配;如果为 False,则允许模糊匹配;

示例1 返回所有菜单项:

# 使用 Sublime 练习 # 1. 连接应用程序 app = Application(backend='uia').connect(process=17424) # 2. 定位窗口 win = app.window(title_re='.*Sublime Text.*') win.wait('visible') # 3. 定位菜单栏 menu_bar = win.child_window(title="应用程序", auto_id="MenuBar", control_type="MenuBar") # items print(menu_bar.items()) # print('-----------------------------------------------------') print(menu_bar.children()) 

示例2 返回指定菜单项:

# item_by_index print(menu_bar.item_by_index(0)) for i in range(0, len(menu_bar.items())): print(menu_bar.item_by_index(i))

示例3 查找路径上的菜单项:

# item_by_path # 1) 先定位 File -> Save save = menu_bar.item_by_path(path="File -> Save", exact=True) # 2) 再获取坐标 point = save.rectangle().mid_point() # 3) 把鼠标指向按钮 mouse.move(coords=(point.x, point.y)) # 1) 定位 File -> Reopen with Encoding reopen = menu_bar.item_by_path(path="File -> Reopen with Encoding", exact=True) # 2) 再获取坐标 point = reopen.rectangle().mid_point() # 3) 把鼠标指向按钮 mouse.move(coords=(point.x, point.y))

示例4 多级菜单的定位,以 Sublime 为例:

# 使用 Sublime 练习 # 1. 连接应用程序 app = Application(backend='uia').connect(process=17424) # 2. 定位窗口 win = app.window(title_re='.*Sublime Text.*') win.wait('visible') # win.print_control_identifiers() # 3. 定位菜单栏 menu_bar = win.child_window(title="应用程序", auto_id="MenuBar", control_type="MenuBar") # 4. 定位多层次的路径 menu_bar.item_by_path(path='File -> Open Recent').click_input() # 定位到 Open Recent 菜单 open_recent_menu = win.child_window(title="Open Recent", control_type="Menu") # 定位到 Clear Items clear_items = open_recent_menu.item_by_path(path='Clear Items') clear_items.click_input()

示例5 查找指定路径上的菜单项:

# menu_select # 找到 Open Recent 并点击 win.menu_select(path='File -> Open Recent') # 找到 Clear Items clear_items = win.child_window(title="Clear Items", auto_id="23", control_type="MenuItem") clear_items.click_input()

menu_select() 和 item_by_path() 的区别:

  • 在使用 menu_select() 的场景下,至少需要两个菜单栏:"系统" 和 "应用程序";
  • 系统菜单栏是一个标准的窗口菜单,包含:还原,移动,最大化,最小化等,通常有一个标题栏作为父级;
  • 应用程序菜单栏:通常是我们要找的,通常父级是对话框本身,可以在对话框直接子集中找到;

11. 列表控件的操作

语法:

  • get_items():获取列表中的所有项目;
  • item_count():获取列表项数;
  • get_item(row=0):获取列表中的指定项目;

示例:

# 1. 连接应用程序 app = Application(backend='uia').connect(process=26880) # 2. 定位窗口 win = app.window(title_re='sublime_text.*', control_type='Window') win.wait('visible') # 3. 定位列表控件 list_ctrl = win.child_window(title='项目视图', control_type='List') # 4. 获取列表项 print(list_ctrl.children()) print('------------------------------------------------------') print(list_ctrl.get_items()) # 5. 获取项数 print(list_ctrl.item_count()) print('----------------------------------------------------------') print(len(list_ctrl.get_items())) # 6. 获取某个列表项并双击 list_ctrl.get_item(row=0).double_click_input() 

三、自动化场景示例 - 微信发消息

1. 实现思路

1. 连接应用程序,定位聊天窗口;

2. 发送消息之前记录当前消息的条数;

3. 记录要发送的消息内容,并发送消息;

4. 发送消息后记录当前消息的条数;

5. 验证最后一条消息的内容是否为之前记录的消息内容;

2. 注意事项

1. 如果发送的消息内容都是一样的,怎么验证发送的消息是否成功?

消息都是相同的,这样不好验证;为了方便验证,因此需要保证发送的消息的唯一性;

保证发送消息的唯一性,采用时间戳的方式,在要发送的消息后面拼接上当前时间的时间戳,保证消息的唯一性;

在聊天窗口读到这条消息,就表示发送成功,没读到这条消息就表示发送失败;

2. 发送消息成功后,聊天窗口有可能会增加1条消息,也有可能会增加两条消息(一条为时间,另一条为消息本身),该怎么校验?

消息数量校验:发送前消息的数量 + 1 == 发送后消息的数量 或 发送前消息的数量 + 2 == 发送后消息的数量;

消息内容校验:判断最新的消息和先前记录的发送内容是否一致;

2. 代码实现

""" 微信发消息 """ from datetime import datetime from pywinauto.application import Application # 1. 连接应用程序 app = Application(backend='uia').connect(process=2832) # 2. 定位窗口 win = app.window(title='文件传输助手', control_type='Window') win.wait('visible') # 3. 发送微信消息 # 发送之前要记录消息条数 message_list_before = win.child_window(title='消息', control_type='List') message_count_before = message_list_before.item_count() # 发送之前要记录消息的内容 message = 'hello, world!' + ' - ' + str(datetime.now()) # 编辑消息 message_edit = win.child_window(title='输入', control_type='Edit') message_edit.click_input() message_edit.type_keys(keys=message, with_spaces=True) # 点击发送按钮 send_btn = win.child_window(title='发送(S)', control_type='Button') send_btn.click_input() # 4. 校验发送的消息 # 发送之后要记录消息的条数 message_list_after = win.child_window(title='消息', control_type='List') message_count_after = message_list_after.item_count() # 校验发送消息的条数 # print(message_count_after) # print(message_count_before) assert message_count_after == message_count_before + 1 or message_count_after == message_count_before + 2 # 发送之后要记录最新消息的内容 message_get = message_list_after.get_item(row=message_count_after - 1).window_text() # 检验最新消息的内容是否正确 # print(message) # print(message_get) assert message_get == message

Read more

【Git】GitHub 连接失败解决方案:Failed to connect to github.com port 443 after 21090 ms: Couldn’t connect to se

【Git】GitHub 连接失败解决方案:Failed to connect to github.com port 443 after 21090 ms: Couldn’t connect to se

文章目录 * 一、使用 VPN 环境下的解决方案 * 1. 检查当前代理设置 * 2. 配置 Git 使用代理 * 3. 验证代理设置是否生效 * 4. 刷新 DNS 缓存 * 5. 重新尝试 Git 操作 * 二、未使用 VPN 环境下的解决方案 * 1. 取消 Git 配置的代理 * 2. 验证代理设置已成功移除 * 3. 重试 Git 操作 * 三、总结 * 使用 VPN 的解决方案: * 未使用 VPN 的解决方案: 在使用 Git 进行代码管理时,可能会遇到“Failed to connect

By Ne0inhk

DeepSeek-OCR-2开源实操:高校实验室低成本构建论文数字化处理平台

DeepSeek-OCR-2开源实操:高校实验室低成本构建论文数字化处理平台 获取更多AI镜像 想探索更多AI镜像和应用场景?访问 ZEEKLOG星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。 1. 项目简介与核心价值 DeepSeek-OCR-2是一个专门为文档数字化设计的智能OCR工具,它和传统OCR工具最大的不同在于:不仅能识别文字,还能理解文档的结构。想象一下,你扫描了一篇学术论文,传统OCR可能给你一堆杂乱无章的文本,而DeepSeek-OCR-2能够准确识别出哪些是标题、哪些是正文、表格结构是什么样的,然后自动转换成规整的Markdown格式。 对于高校实验室来说,这个工具特别实用。很多实验室都有大量纸质论文、实验报告需要数字化处理,手动输入既费时又容易出错。DeepSeek-OCR-2可以在本地部署,完全离线运行,既保护了研究资料的隐私安全,又不需要支付任何云服务费用。 工具还做了深度性能优化,支持Flash Attention 2加速推理,用BF16精度减少显存占用,意味着即使实验室的GPU配置不是

By Ne0inhk

【GitHub项目推荐--Handy:完全离线的开源语音转文字应用】

简介 Handy 是一个免费、开源且可扩展的语音转文字应用程序,能够在完全离线环境下工作。它是一个跨平台桌面应用程序,使用Tauri(Rust + React/TypeScript)构建,提供简单、注重隐私的语音转录功能。 🔗 GitHub地址 : https://github.com/cjpais/Handy 🚀 核心价值 : 语音转文字 · 完全离线 · 开源免费 · 隐私保护 · 跨平台 项目背景 : * 隐私保护 :解决云端语音处理隐私问题 * 离线可用 :完全离线工作能力 * 开源自由 :开源语音识别工具 * 跨平台 :支持多操作系统 * 可扩展 :高度可扩展架构 项目特色 : * 🔒 完全离线 :无需网络连接 * 🆓 开源免费 :代码完全开源免费 * 🌐 跨平台 :Windows、macOS、Linux * 🤖 多模型 :支持多种语音模型 * ⚡ 高性能 :GPU加速支持 技术亮点

By Ne0inhk

TRAE vs Qoder vs Cursor vs GitHub Copilot:谁才是真正的“AI 工程师”?

引言:工具选择 = 成本 + 效率 + 风险 的综合权衡 2026 年,AI 编程工具已从“玩具”走向“生产主力”。但面对 TRAE、Qoder、Cursor、GitHub Copilot 等选项,开发者不仅要问: * 它能写 Rust 吗?支持中文需求吗? * 更要问:一个月多少钱?团队用得起吗?代码安全有保障吗? 本文将从 五大核心维度 深度剖析四大主流 AI IDE: 1. 核心理念与自主性 2. 多语言与跨生态支持能力 3. 工程化与交付闭环能力 4. 中文本地化与业务适配 5. 收费模式、定价策略与企业成本 帮你做出技术可行、经济合理、风险可控的决策。 一、核心理念:

By Ne0inhk