news 2026/7/4 7:35:41

AI赋能UI自动化测试:Selenium智能脚本生成原理与实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI赋能UI自动化测试:Selenium智能脚本生成原理与实践

1. 项目概述:当UI自动化测试遇见AI,一场效率与心智的变革

如果你在测试或者开发岗位上待过几年,听到“UI自动化测试”这个词,心里多半会涌起一种复杂的情绪。一方面,它是解放生产力、实现持续回归的终极梦想;另一方面,它又像是西西弗斯推的那块石头——脚本编写耗时费力,维护成本高得吓人,页面稍有改动,之前精心编写的用例就可能大面积“阵亡”。这种投入产出比的不确定性,让很多团队对UI自动化望而却步,或者仅仅停留在核心流程的浅层覆盖。今天,我想和你深入聊聊一个正在改变这种局面的前沿实践:CoPaw,或者说,基于Selenium的智能测试脚本生成。这不是一个遥不可及的概念,而是我们这些一线从业者正在尝试、踩坑并逐步落地的一种新工作范式。

简单来说,CoPaw所代表的方向,其核心思想是引入AI(人工智能)来辅助甚至主导UI自动化测试脚本的创建过程。它不再要求测试工程师必须像传统开发一样,逐行手写Selenium代码来定位元素、模拟点击、输入文本和添加断言。相反,它试图通过“观察”用户的手动操作,或者理解自然语言描述的需求,自动分析页面结构、理解操作意图,并最终生成结构清晰、可维护、可直接执行的测试代码。这听起来有点像测试领域的“自动驾驶辅助系统”,目标不是完全取代司机(测试工程师),而是接管那些重复、繁琐且容易出错的驾驶操作(编码),让司机能更专注于规划路线(测试设计)和应对复杂路况(探索性测试与业务验证)。

无论你是刚刚接触自动化测试、被Python和Selenium语法困扰的新手,还是疲于应对海量回归用例、苦于脚本维护的资深测试,理解并尝试这种智能生成思路,都可能是你提升个人和团队效能的关键一步。它不仅仅是换了一个工具,更是在重塑我们对于“测试脚本”本身的认知——从“需要编写的代码”转变为“可以被生成和优化的资产”。

2. 核心思路拆解:智能生成的“大脑”是如何工作的

要真正理解CoPaw这类项目的价值,我们不能只停留在“自动生成代码”这个表面概念上。我们需要拆解其背后的核心逻辑,看看它是如何跨越从“用户操作”到“健壮脚本”这道巨大鸿沟的。这绝非一个简单的“录制-回放”工具的升级版,其背后融合了前端技术、软件测试理论和机器学习等多个领域的知识。

2.1 从“坐标记录”到“语义理解”的本质飞跃

让我们先回顾一下最原始的自动化:坐标录制。早期的工具,甚至现在一些简单的录屏插件,记录的是鼠标的绝对坐标((x, y))和键盘事件。生成的脚本脆弱不堪,屏幕分辨率一变、浏览器窗口一动,脚本就失效了。Selenium的出现,通过操作DOM(文档对象模型)元素,将自动化带入了“相对定位”的时代,稳定性大幅提升。但传统的Selenium IDE录制,本质上仍然是“操作记录器”:你点一个按钮,它记录下driver.find_element(By.ID, “submit”).click()。这虽然比坐标好,但问题依然存在:如果这个按钮的ID是动态生成的(比如id=”button-12345”),下次页面刷新就变了,脚本立刻失效。

智能脚本生成的核心突破,在于尝试进行“语义理解”“意图识别”。系统需要解读的不仅仅是“找到了ID为‘submit’的元素并点击”,而是理解“用户在执行‘提交表单’这个业务意图”。为了实现这一步,系统必须像一个经验丰富的测试员一样,综合多维度信息进行推理:

  1. DOM结构深度分析:这是基础。系统需要实时获取并解析页面的完整HTML DOM树。它不只是找元素,更要理解元素的语义。一个<button>标签比一个<div>更可能是可点击的;一个<input type=”password”>意味着这里需要输入密码;aria-label属性可能提供了更准确的元素描述。系统会分析元素的标签名、属性(id, name, class, type, placeholder)、文本内容以及它在DOM树中的层级关系,构建一个丰富的元素特征画像。

  2. 计算机视觉(CV)的辅助:对于现代Web应用,纯DOM分析有时会失灵。比如,一个用Canvas或SVG绘制的图表按钮,一个完全由CSS渲染的自定义开关控件,在DOM里可能只是一个<div>,缺乏有意义的属性。这时,就需要引入计算机视觉技术。通过对屏幕截图进行实时分析,CV模型可以识别出“这是一个按钮”、“那是一个输入框”,甚至识别出上面的文字内容。这种“所见即所得”的能力,是对DOM分析的有力补充,尤其对付那些前端框架生成的“无特征”元素非常有效。

  3. 操作上下文的关联与抽象:孤立地看,一次点击、一次输入没有意义。智能系统需要将一系列连续操作关联起来,抽象成更高层的业务流。例如,识别出“在输入框A输入‘admin’ -> 在输入框B输入‘123456’ -> 点击按钮C”这一系列操作,共同构成了“登录”这个业务场景。这种抽象能力,是生成可读性高、模块化脚本的关键,也为后续的脚本维护(如业务流程变更)提供了可能。

2.2 脚本生成策略:在稳定性、可读性与可维护性间走钢丝

理解了用户的意图,并将其绑定到具体的页面元素后,下一个挑战是如何生成代码。这里没有银弹,而是一系列权衡和策略:

  1. 智能元素定位器生成——稳定性的基石:这是整个流程中最关键、技术含量最高的一环。一个好的定位器应该在页面多次渲染中保持稳定,且尽可能简洁。AI或规则引擎需要像一个老道的测试开发一样,评估各种定位策略。通常的优先级共识是:唯一且静态的ID > 唯一且静态的Name > 具有特定语义的CSS Selector > 相对稳定的XPath。系统需要为每个交互元素计算多个候选定位器,并通过在当前DOM上下文中的唯一性校验,选择最优解。例如,对于一个“搜索”按钮,优先使用<button id=”search-btn”>,而不是//div[contains(@class, ‘toolbar’)]/button[2]这种依赖布局顺序的脆弱XPath。更高级的系统还会评估定位器的“抗变化”能力,比如优先选择那些与业务功能相关(如>组件可选技术说明与考量操作捕获Selenium WebDriver,Playwright, PuppeteerSelenium生态最成熟、社区最广,是事实标准。Playwright由微软推出,原生支持多浏览器,且自带强大的录制功能(playwright codegen),其API设计更现代,可以作为快速构建原型的高级起点。DOM分析与处理lxml, BeautifulSouplxml基于C语言,解析大型HTML文档速度极快,对XPath的支持非常完整和高效,适合程序化、高性能的处理场景。BeautifulSoup的API更人性化,适合快速原型开发和调试。定位器生成算法自定义规则引擎这是系统的核心逻辑,需要自行开发。规则示例:优先取id(非空且全局唯一);若无,则取name(在表单内唯一);若为<button><a>,可尝试结合其text()内容;最后考虑组合tagclass和属性生成CSS Selector。必须对每种方案进行唯一性校验。代码生成Jinja2(模板引擎)Python生态最流行的模板引擎。将操作数据与代码模板分离,可以非常灵活地生成不同风格(线性脚本风格、POM风格、关键字驱动风格)的代码,只需更换模板文件即可。测试框架集成pytest比Python标准库的unittest更简洁、功能更强大。其夹具(fixture)机制(如@pytest.fixture)非常适合管理WebDriver的生命周期(启动、退出)。断言语句更直观,测试报告也更美观。可选AI增强大语言模型 (LLM) API用于处理模糊或复杂场景。例如,让LLM为一段操作序列生成描述性的测试步骤注释;或者将自然语言描述的需求(“测试用户登录失败的情况”)转化为具体的测试操作步骤。注意:初期不建议直接依赖LLM生成核心定位逻辑,应将其作为辅助和增强手段。

    实操心得:在项目启动的初期,最忌讳的就是盲目追求“大而全”的AI模型。一个更务实、更可控的策略是:从规则引擎起步。先用精心设计的手工规则(启发式算法)去解决80%的常见、模式固定的场景,让“操作捕获->分析->生成”这个核心流程先跑通。在这个过程中,你会积累大量高质量的、标注好的数据(即“用户操作-最佳定位器”配对)。有了这些数据,再去考虑用机器学习模型去优化那剩下的20%的疑难杂症(比如处理极其复杂的动态组件),这样技术路线会更清晰,成功率也更高。

    4. 关键实现细节与代码解析:让概念落地

    让我们深入到代码层面,看看几个最核心的模块具体如何实现。这里我会提供简化但可运行的代码示例,来阐明思路。

    4.1 智能元素定位器生成算法详解

    这是脚本稳定性的生命线。下面的Python函数展示了一个简化版的定位器优先级评估逻辑:

    from lxml import html, etree import cssselect def generate_best_locator(dom_html, target_element_xpath): """ 根据DOM和目标的XPath,生成最佳定位器。 :param dom_html: 页面HTML字符串 :param target_element_xpath: 目标元素在本次DOM中的临时XPath(由监听器捕获) :return: 一个字典,如 {'type': 'id', 'value': 'submit-button'} """ # 解析DOM tree = html.fromstring(dom_html) try: target_elem = tree.xpath(target_element_xpath)[0] except IndexError: raise ValueError(f"无法通过XPath找到元素: {target_element_xpath}") locator_candidates = [] # 1. 优先尝试 ID (最稳定,前提是静态且唯一) elem_id = target_elem.get('id') if elem_id and elem_id.strip(): # id非空 # 检查这个id在整个页面中是否唯一 if len(tree.xpath(f'//*[@id="{elem_id}"]')) == 1: locator_candidates.append({'type': 'id', 'value': elem_id, 'score': 100}) # 高分 # 2. 尝试 Name 属性 (通常在表单元素中比较稳定) elem_name = target_elem.get('name') if elem_name and elem_name.strip(): # 检查name在当前页面中的唯一性(简化处理,实际需考虑表单范围) if len(tree.xpath(f'//*[@name="{elem_name}"]')) == 1: locator_candidates.append({'type': 'name', 'value': elem_name, 'score': 90}) # 3. 构建唯一的CSS Selector (平衡可读性和稳定性) tag = target_elem.tag classes = target_elem.get('class') css_selector = tag # 策略:尝试使用有区分度的单个class if classes: class_list = [cls.strip() for cls in classes.split() if cls.strip()] for cls in class_list: # 检查这个class在当前页面是否唯一标识该元素 if len(tree.cssselect(f'{tag}.{cls}')) == 1: css_selector = f'{tag}.{cls}' locator_candidates.append({'type': 'css', 'value': css_selector, 'score': 85}) break # 找到一个可用的class就退出 # 如果上面没找到合适的class,尝试用其他属性组合 if not locator_candidates or (locator_candidates and locator_candidates[-1]['type'] != 'css'): # 例如,使用 type 和 placeholder 组合 (常见于输入框) elem_type = target_elem.get('type') placeholder = target_elem.get('placeholder') if elem_type and placeholder: css_selector = f'{tag}[type="{elem_type}"][placeholder="{placeholder}"]' if len(tree.cssselect(css_selector)) == 1: locator_candidates.append({'type': 'css', 'value': css_selector, 'score': 80}) # 4. 作为保底,生成相对可靠的XPath (例如基于id、name或text) fallback_xpath = None if elem_id: fallback_xpath = f'//*[@id="{elem_id}"]' elif elem_name: fallback_xpath = f'//*[@name="{elem_name}"]' elif target_elem.text and target_elem.text.strip(): text_content = target_elem.text.strip()[:50] # 取前50字符防止过长 # 使用normalize-space处理空格,更健壮 fallback_xpath = f'//{tag}[normalize-space(text())="{text_content}"]' else: # 最终保底:使用传入的原始XPath(通常很长且不稳定) fallback_xpath = target_element_xpath # 验证保底XPath的唯一性 if fallback_xpath and len(tree.xpath(fallback_xpath)) == 1: score = 60 if fallback_xpath == target_element_xpath else 70 # 原始XPath分数最低 locator_candidates.append({'type': 'xpath', 'value': fallback_xpath, 'score': score}) # 5. 返回优先级最高的定位器 if locator_candidates: # 按分数降序排序,返回分数最高的 locator_candidates.sort(key=lambda x: x['score'], reverse=True) best = locator_candidates[0] return {'type': best['type'], 'value': best['value']} else: # 所有尝试都失败,返回最原始的XPath return {'type': 'xpath', 'value': target_element_xpath} # 示例使用 sample_html = """ <html> <body> <form id="loginForm"> <input type="text" id="username" name="user" placeholder="请输入用户名"> <input type="password" id="pwd" name="pass"> <button id="submit-btn" class="btn btn-primary">登录</button> </form> </body> </html> """ # 假设我们捕获到的目标是登录按钮,其临时XPath可能是 /html/body/form/button best_locator = generate_best_locator(sample_html, '/html/body/form/button') print(f"最佳定位器: {best_locator}") # 输出: {'type': 'id', 'value': 'submit-btn'}

    这个函数体现了核心思路:按稳定性降序尝试多种定位方式,并对每种方式进行唯一性校验。在实际工业级系统中,规则要复杂得多,还需要考虑元素是否在iframe内、是否属于Shadow DOM、以及如何处理动态生成的类名(如class=”button-123abc”)等边缘情况。

    4.2. 操作序列化与脚本模板渲染

    捕获到的原始事件需要被转换成结构化的操作对象。我们定义一个简单的类:

    class UserAction: def __init__(self, action_type, locator, value=None, timestamp=None, description=""): self.action_type = action_type # 如:'click', 'input_text', 'select_dropdown', 'assert_text' self.locator = locator # 来自 generate_best_locator 的字典 self.value = value # 输入的文字、下拉选项的值等 self.timestamp = timestamp self.description = description # 可读的描述,如“点击登录按钮” # 等待策略(可由分析层自动推断或手动指定) self.pre_wait_condition = None # 操作前需满足的条件,如元素可点击 self.post_wait_condition = None # 操作后需等待的条件,如新页面加载 def to_dict(self): return { 'action_type': self.action_type, 'locator_type': self.locator['type'], 'locator_value': self.locator['value'], 'value': self.value, 'description': self.description }

    接下来,我们使用Jinja2模板引擎,将一系列UserAction对象渲染成一个可执行的pytest脚本。这是模板文件(test_template.jinja2)的示例:

    import pytest from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException @pytest.fixture(scope="function") def driver(): """初始化WebDriver,测试结束后退出。""" # 可配置化:从环境变量或配置文件读取浏览器类型 options = webdriver.ChromeOptions() options.add_argument('--headless') # 无头模式,适合CI环境 options.add_argument('--no-sandbox') options.add_argument('--disable-dev-shm-usage') d = webdriver.Chrome(options=options) d.implicitly_wait(5) # 设置全局隐式等待 d.maximize_window() yield d d.quit() def test_generated_case_{{ test_case_id }}(driver): """智能生成的测试用例: {{ test_case_name }}""" driver.get("{{ start_url }}") {% for action in actions %} # Step {{ loop.index }}: {{ action.description }} print("Executing: {{ action.description }}") {% if action.pre_wait_condition %} # 等待前置条件: {{ action.pre_wait_condition }} try: WebDriverWait(driver, 10).until( EC.{{ action.pre_wait_condition }}((By.{{ action.locator_type.upper() }}, "{{ action.locator_value }}")) ) except TimeoutException: driver.save_screenshot(f"pre_wait_failed_step_{{ loop.index }}.png") pytest.fail(f"Step {{ loop.index }} 前置等待失败: {{ action.pre_wait_condition }}") {% endif %} # 定位元素并执行操作 locator = (By.{{ action.locator_type.upper() }}, "{{ action.locator_value }}") try: element = WebDriverWait(driver, 10).until( EC.presence_of_element_located(locator) ) except TimeoutException: driver.save_screenshot(f"element_not_found_step_{{ loop.index }}.png") pytest.fail(f"Step {{ loop.index }} 未找到元素: {{ action.locator_type }}={{ action.locator_value }}") # 执行具体操作 {% if action.action_type == 'click' %} element.click() {% elif action.action_type == 'input_text' %} element.clear() element.send_keys("{{ action.value }}") {% elif action.action_type == 'assert_text' %} actual_text = element.text assert actual_text == "{{ action.value }}", f"文本断言失败。预期: '{{ action.value }}', 实际: '{actual_text}'" {% endif %} {% if action.post_wait_condition %} # 等待后置条件: {{ action.post_wait_condition }} try: WebDriverWait(driver, 10).until( EC.{{ action.post_wait_condition }}((By.{{ action.locator_type.upper() }}, "{{ action.locator_value }}")) ) except TimeoutException: driver.save_screenshot(f"post_wait_failed_step_{{ loop.index }}.png") pytest.fail(f"Step {{ loop.index }} 后置等待失败: {{ action.post_wait_condition }}") {% endif %} {% endfor %} print("{{ test_case_name }} 测试执行通过!")

    然后,用Python代码将数据灌入模板:

    from jinja2 import Environment, FileSystemLoader import json # 准备数据 actions_data = [ UserAction('input_text', {'type': 'id', 'value': 'username'}, 'testuser', description='输入用户名').to_dict(), UserAction('input_text', {'type': 'id', 'value': 'pwd'}, 'password123', description='输入密码').to_dict(), UserAction('click', {'type': 'id', 'value': 'submit-btn'}, description='点击登录按钮').to_dict(), UserAction('assert_text', {'type': 'css', 'value': 'h1.welcome'}, '欢迎,testuser!', description='验证登录成功').to_dict(), ] context = { 'test_case_id': 'login_001', 'test_case_name': '用户登录成功流程', 'start_url': 'https://example.com/login', 'actions': actions_data } # 加载模板并渲染 env = Environment(loader=FileSystemLoader('.')) template = env.get_template('test_template.jinja2') generated_code = template.render(context) # 将生成的代码写入文件 with open('test_generated_login.py', 'w', encoding='utf-8') as f: f.write(generated_code) print("测试脚本已生成: test_generated_login.py")

    通过这种方式,我们实现了数据与表现的分离。要改变生成的代码风格(比如从线性脚本改为Page Object模式),我们只需要更换Jinja2模板,而无需修改核心的分析和数据结构代码,这大大提升了系统的灵活性和可维护性。

    5. 从原型到产品:必须跨越的挑战与演进方向

    构建一个能在自己电脑上跑通的Demo固然令人兴奋,但要让一个智能脚本生成系统在真实的团队协作和持续集成环境中稳定运行、产生价值,我们还需要直面一系列严峻的挑战。

    5.1 稳定性与可维护性:智能脚本的“生死劫”

    AI或规则生成的脚本,其最大的挑战往往不在第一次生成,而在第N次执行,尤其是在被测试应用频繁迭代时。

    • 挑战一:定位器失效的常态化应对。这是最高频的问题。今天id=”submit”的按钮,明天可能变成了>问题现象可能原因排查思路与解决方案录制时一切正常,回放时元素找不到1.页面加载/渲染速度差异(录制时手动操作慢,回放时脚本快)。
      2.元素属性动态变化(如ID、Class含随机数)。
      3.页面存在iframe或Shadow DOM,未切换上下文。
      4. 操作触发了新窗口/标签页,未切换句柄。1.增加显式等待:在关键操作后插入等待,等待特定条件(如元素可点击、新窗口打开),而非固定sleep
      2.审查并优化定位器:检查生成的定位器是否依赖了动态属性。改用更稳定的属性组合,如>脚本在本地运行成功,但在CI服务器上失败1.环境差异:浏览器版本、WebDriver版本不匹配。
      2.资源加载问题:CI服务器网络慢或受限,导致JS/CSS加载超时。
      3.无头模式差异:本地在GUI下运行,CI在无头模式下,某些前端行为可能不同。1.环境固化:使用Docker容器统一测试环境,锁定浏览器和WebDriver的精确版本。
      2.调整超时与重试策略:针对CI环境,适当延长全局隐式等待和显式等待的超时时间。对网络请求添加重试机制。
      3.本地模拟CI环境:在本地开发时,就经常在无头模式下运行测试(--headless),提前暴露兼容性问题。生成的脚本可读性差,像“面条代码”,难以维护1. 生成的定位器过于复杂冗长(如超长的绝对XPath)。
      2. 代码是线性的,没有模块化,所有操作堆在一个函数里。
      3.缺乏有意义的注释,无法理解步骤意图。1.优化定位器生成算法:优先产出简洁、语义化的CSS Selector或基于唯一稳定属性的定位。设立规则,拒绝生成超过一定复杂度的XPath。
      2.应用高级代码模板:使用支持**页面对象模型(POM)屏幕播放模式(Screenplay Pattern)**的模板生成代码,强制进行业务逻辑与元素定位的分离。
      3.自动添加语义化注释:在生成代码时,利用操作元素的文本、placeholderaria-label等属性,自动为每个步骤生成描述性注释,如# 在‘用户名’输入框输入‘admin’AI/规则无法理解复杂交互(如拖拽、画布绘图、复杂手势)当前技术局限。底层事件监听和意图推断对于非标准、连续性的交互支持不足。1.采用混合策略:对于标准表单、列表操作,使用智能生成。对于拖拽、绘图等复杂交互,在系统中提供“自定义代码块”插入功能。允许用户手动编写这一小段Selenium代码,系统负责将其整合到生成的脚本框架中。
      2.依赖专业工具库:生成代码时,对于文件上传、日期选择器等特定交互,直接调用成熟的第三方工具函数或封装好的PageObject方法,而不是尝试从零生成底层事件序列。

      6.2 核心避坑经验

      1. 设定合理的期望:不要试图追求100%自动化。这是最重要的心态调整。智能生成的目标是覆盖那80%的重复、模式固定、高价值的回归测试场景,从而将测试人员从繁重的编码工作中解放出来。剩下的20%复杂、探索性、需要人类直觉和创造力的测试,仍然需要人工设计和执行。一开始就追求全自动,项目极易陷入技术深渊,难以产出实际价值。

      2. “可调试性”的价值远高于“全智能”。一个生成的脚本,如果失败了却让人无从下手,那它就是垃圾。必须确保生成的脚本具备极强的可调试性:每一步操作都有清晰的日志输出;失败时能自动截取当前屏幕和DOM快照;生成的定位器旁边最好能注释其来源(如“基于ID定位”)。当脚本失败时,测试员能在一分钟内判断出是“页面变了”、“定位器生成了”、“还是“测试数据错了”,这比一个黑盒的、看似智能但无法调试的脚本有价值一万倍。

      3. 采用“小步快跑,快速验证”的迭代策略。不要一上来就雄心勃勃地要做一个能录制整个电商下单流程的系统。从一个边界清晰、价值明确的小场景开始。比如,就只做“用户登录模块”的脚本生成。把这个场景做深、做透、做稳定,让团队看到它确实能节省时间、减少错误。然后,再逐步扩展到“商品搜索”、“加入购物车”等相邻场景。用一个个成功的小点,连成一条线,再铺成一个面。

      4. 与现有测试资产和流程深度融合。智能生成不是要推翻重来,而是增强现有体系。生成的脚本应该能轻松导入到团队已有的测试用例管理系统(如TestRail, Jira, Zephyr)中,并与对应的手工测试用例关联。执行结果能自动回填到系统,生成统一的测试报告。这样,测试经理仍然可以在他熟悉的工具里查看测试覆盖率和进度,整个团队的流程不会因为引入新工具而断裂。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/4 7:35:31

深度解析PoB2 Lua架构:如何实现高效物品数据处理与构建优化

深度解析PoB2 Lua架构&#xff1a;如何实现高效物品数据处理与构建优化 【免费下载链接】PathOfBuilding-PoE2 项目地址: https://gitcode.com/GitHub_Trending/pa/PathOfBuilding-PoE2 Path of Building PoE2&#xff08;PoB2&#xff09;作为流放之路2社区最强大的构…

作者头像 李华
网站建设 2026/7/4 7:34:16

终极指南:3分钟快速掌握Google图片批量下载神器

终极指南&#xff1a;3分钟快速掌握Google图片批量下载神器 【免费下载链接】google-images-download Python Script to download hundreds of images from Google Images. It is a ready-to-run code! 项目地址: https://gitcode.com/gh_mirrors/go/google-images-download …

作者头像 李华
网站建设 2026/7/4 7:34:07

手写体识别终极指南:PaddleOCR如何让潦草文字“开口说话“?

手写体识别终极指南&#xff1a;PaddleOCR如何让潦草文字"开口说话"&#xff1f; 【免费下载链接】PaddleOCR 飞桨多语言OCR工具包&#xff08;实用超轻量OCR系统&#xff0c;支持80种语言识别&#xff0c;提供数据标注与合成工具&#xff0c;支持服务器、移动端、嵌…

作者头像 李华
网站建设 2026/7/4 7:33:31

Linux数据恢复与备份:从误删文件到系统灾难的完整解决方案

Linux数据恢复与备份&#xff1a;从误删文件到系统灾难的完整解决方案 【免费下载链接】Awesome-Linux-Software &#x1f427; A list of awesome Linux softwares 项目地址: https://gitcode.com/GitHub_Trending/aw/Awesome-Linux-Software 当你在Linux系统中不小心…

作者头像 李华
网站建设 2026/7/4 7:32:06

HPL1Engine物理引擎详解:碰撞检测与关节系统开发实战

HPL1Engine物理引擎详解&#xff1a;碰撞检测与关节系统开发实战 【免费下载链接】HPL1Engine A real time 3D engine. 项目地址: https://gitcode.com/gh_mirrors/hp/HPL1Engine HPL1Engine作为Frictional Games开发的经典3D游戏引擎&#xff0c;其强大的物理引擎系统为…

作者头像 李华
网站建设 2026/7/4 7:30:04

从数组到菜单:spatie/menu的Menu::build方法批量创建导航的实用指南

从数组到菜单&#xff1a;spatie/menu的Menu::build方法批量创建导航的实用指南 【免费下载链接】menu Html menu generator 项目地址: https://gitcode.com/gh_mirrors/menu/menu 你是否曾经为PHP项目中繁琐的导航菜单构建而感到头疼&#xff1f;&#x1f62b; 每次添加…

作者头像 李华

关于博客

这是一个专注于编程技术分享的极简博客,旨在为开发者提供高质量的技术文章和教程。

订阅更新

输入您的邮箱,获取最新文章更新。

© 2025 极简编程博客. 保留所有权利.