Python+Selenium实战:PO设计模式构建可维护的UI自动化测试框架
1. 项目概述为什么PO模式是UI自动化测试的“定海神针”做UI自动化测试的朋友估计都经历过这样的痛苦页面元素一改几十上百条测试用例集体报错改起来简直让人怀疑人生。或者一个测试脚本里混杂着元素定位、业务操作、数据断言代码又长又乱过两个月自己都看不懂。我刚开始做自动化时也在这上面栽过不少跟头直到后来系统性地引入了POPage Object设计模式整个测试框架的维护性和可读性才发生了质的变化。今天我们就来深入聊聊如何用Python从零开始把PO模式真正落地到你的Web UI自动化测试项目中并写出高质量的测试用例。简单来说PO模式的核心思想是把测试对象页面和测试脚本用例分离开。每个页面或页面上的一个组件被抽象成一个独立的类这个类里封装了该页面的所有元素定位器和基本操作如输入、点击。而我们的测试用例则变成了一系列清晰、只描述业务逻辑的步骤比如“登录 - 搜索商品 - 加入购物车”。这样做的好处是显而易见的当页面元素发生变化时你只需要去修改对应的那个Page类里的定位器所有引用这个页面的测试用例都自动生效维护成本直线下降。同时业务逻辑和底层实现分离代码结构清晰团队协作也更顺畅。这个系列我们假设你已经掌握了Python和Selenium的基础目标是构建一个结构清晰、易于维护的自动化测试框架。PO模式是其中承上启下的关键一环它连接着底层的元素操作和上层的业务验证。2. POPOM设计模式的核心思想与架构拆解2.1 PO vs POM不仅仅是名字的差异很多人会把POPage Object和POMPage Object Model混为一谈其实它们有细微但重要的区别。PO指的是一种设计模式一个设计思想。而POM是在PO思想指导下结合具体编程语言和测试框架如Python unittest/pytest实现出来的一整套代码组织结构、类与方法的集合。你可以理解为PO是理论POM是实践。一个基础的PO类通常包含两部分内容元素定位器以类属性的形式存在比如search_input (By.ID, ‘kw’)。页面操作方法以类方法的形式存在比如def search(self, keyword): self.find_element(self.search_input).send_keys(keyword)。而POM则在此基础上通常会考虑更多工程化的问题比如页面继承与复用一个BasePage类封装所有页面共用的操作如等待元素、截图、通用弹窗处理。组件化封装将页面上可复用的部分如头部导航栏、侧边菜单也抽象成独立的类。与测试框架的集成如何与pytest的fixture结合管理浏览器驱动如何生成测试报告等。注意在实际项目中我们通常说的“使用PO模式”指的就是实现一套POM。所以下文我会用POM来指代我们具体要搭建的这套代码架构。2.2 分层架构让代码各司其职一个典型的、结构良好的POM框架至少应该分为三层这能让你的代码像乐高积木一样清晰、可组装。第一层基础层Base Layer这是整个框架的基石。核心是一个BasePage类。它不针对任何具体业务页面而是封装所有页面对象都需要用到的“通用能力”。通常包括初始化方法__init__接收一个driverWebDriver实例。元素查找的二次封装例如一个find_element方法内部集成显式等待WebDriverWait避免在测试中到处写time.sleep。通用操作点击、输入、获取文本、截图等。日志记录每个操作自动记录日志便于调试。这个类的存在避免了代码重复也统一了操作行为。所有具体的页面类都将继承自它。第二层页面对象层Page Object Layer这是POM的核心。对应你Web应用中的每一个页面或重要弹窗、组件你都需要创建一个Python类。例如LoginPageHomePageSearchResultPageShoppingCartPage。 每个这样的类需要在__init__中调用父类BasePage的初始化。定义本页面独有的元素定位器推荐使用元组(By.ID, ‘value’)形式清晰且易于管理。定义本页面的业务操作方法。例如LoginPage类会有login(username, password)方法其内部封装了输入用户名、密码和点击登录按钮的细节。第三层测试用例层Test Case Layer这是最上层也是最终体现测试价值的地方。测试用例脚本应该非常“干净”只包含业务逻辑流和断言。它通过调用第二层页面对象的方法来组合成测试场景。理想状态下一个不懂代码的业务人员也能大致看懂测试用例在做什么。 例如一个购物流程的测试用例伪代码def test_add_item_to_cart(): login_page.login(“valid_user”, “valid_pass”) # 调用LoginPage方法 home_page.search(“python book”) # 调用HomePage方法 search_page.select_first_item() # 调用SearchResultPage方法 detail_page.add_to_cart() # 调用ProductDetailPage方法 assert cart_page.get_item_count() 1 # 调用CartPage方法并断言你看这里没有任何find_element或send_keys只有清晰的业务步骤。2.3 为何选择POM从“脚本”到“框架”的跃升在没有POM之前我们的自动化代码往往是线性的“脚本”。它的生命周期很短随着项目迭代维护成本呈指数级上升。POM带来的最大价值是将自动化测试从“一次性脚本”提升为“可维护的资产”。高可维护性这是首要优势。元素定位变更被隔离在单个Page类中。高可读性测试用例即业务文档降低了沟通和理解成本。高复用性页面操作方法可以被多个测试用例复用避免了重复代码。团队协作友好前端开发改页面测试人员改对应的Page类分工明确并行不悖。降低对工具的依赖即使将来从Selenium换到其他UI自动化工具如Playwright也只需要重写BasePage和少量底层封装大量的页面业务方法和测试用例逻辑可以保持不变。3. 从零搭建POM框架的详细步骤理论说再多不如动手搭一遍。下面我们一步步用Python和Selenium构建一个最小化但功能完整的POM框架。我们以一个经典的电商网站登录和搜索场景为例。3.1 环境准备与项目结构规划首先确保你的环境已经就绪# 安装核心库 pip install selenium pytest pytest-html我强烈推荐使用pytest作为测试运行器它比unittest更灵活、插件生态更丰富。pytest-html用于生成漂亮的HTML测试报告。接下来规划你的项目目录结构。一个清晰的结构是成功的一半。我推荐如下结构your_project/ ├── conftest.py # pytest全局配置如driver的fixture ├── requirements.txt # 项目依赖 ├── base/ # 基础层 │ └── base_page.py # BasePage类 ├── pages/ # 页面对象层 │ ├── __init__.py │ ├── login_page.py │ ├── home_page.py │ └── search_page.py ├── test_cases/ # 测试用例层 │ ├── __init__.py │ └── test_login_and_search.py ├── test_data/ # 测试数据可选如JSON, YAML │ └── users.json ├── logs/ # 日志目录运行时生成 ├── reports/ # 测试报告目录运行时生成 └── utils/ # 工具类可选 ├── logger.py # 日志工具 └── config_reader.py # 配置文件读取这个结构将不同职责的代码模块化一目了然。3.2 实现基石编写健壮的BasePage类base_page.py是这个框架的灵魂。它的健壮性直接决定了上层使用的便利性。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException, NoSuchElementException import logging import os from datetime import datetime class BasePage: 所有页面类的基类封装通用操作 def __init__(self, driver, timeout10): 初始化BasePage :param driver: WebDriver实例 :param timeout: 默认显式等待超时时间秒 self.driver driver self.timeout timeout self.wait WebDriverWait(driver, timeout) # 初始化日志 self.logger logging.getLogger(__name__) def find_element(self, locator): 查找单个元素集成显式等待 :param locator: 定位器元组如 (By.ID, ‘username‘) :return: WebElement对象 try: self.logger.info(f“正在查找元素: {locator}“) element self.wait.until(EC.presence_of_element_located(locator)) self.logger.info(f“元素查找成功: {locator}“) return element except TimeoutException: self.logger.error(f“元素查找超时: {locator}“) # 失败时自动截图便于排查 self.take_screenshot(“element_not_found”) raise def find_elements(self, locator): 查找多个元素 try: return self.wait.until(EC.presence_of_all_elements_located(locator)) except TimeoutException: self.logger.warning(f“未找到任何元素: {locator}“) return [] def click(self, locator): 点击元素 element self.find_element(locator) self.logger.info(f“点击元素: {locator}“) element.click() def input_text(self, locator, text): 向元素输入文本先清空原有内容 element self.find_element(locator) element.clear() self.logger.info(f“向元素 {locator} 输入文本: {text}“) element.send_keys(text) def get_text(self, locator): 获取元素的文本内容 element self.find_element(locator) text element.text self.logger.info(f“获取元素 {locator} 的文本: {text}“) return text def is_element_visible(self, locator, timeoutNone): 判断元素是否可见 wait_time timeout or self.timeout try: WebDriverWait(self.driver, wait_time).until(EC.visibility_of_element_located(locator)) return True except TimeoutException: return False def take_screenshot(self, name): 截取屏幕截图并保存到指定目录 screenshot_dir “./logs/screenshots/“ os.makedirs(screenshot_dir, exist_okTrue) timestamp datetime.now().strftime(“%Y%m%d_%H%M%S”) file_path os.path.join(screenshot_dir, f“{name}_{timestamp}.png”) self.driver.save_screenshot(file_path) self.logger.info(f“截图已保存至: {file_path}“) return file_path实操心得在find_element中集成显式等待和日志是黄金法则。这避免了在页面操作中到处写time.sleep并且当元素找不到时日志和自动截图能为你提供第一手的调试信息极大提升排查效率。3.3 构建页面对象以登录页和主页为例有了坚实的BasePage我们就可以开始构建具体的页面了。首先创建pages/login_page.py。from selenium.webdriver.common.by import By from base.base_page import BasePage class LoginPage(BasePage): 登录页面对象 # 1. 定义元素定位器核心 # 使用元组形式清晰且易于统一修改 USERNAME_INPUT (By.ID, ‘username’) # 假设实际ID为‘username’ PASSWORD_INPUT (By.ID, ‘password’) LOGIN_BUTTON (By.ID, ‘login-btn’) ERROR_MSG (By.CLASS_NAME, ‘error-message’) # 2. 页面URL可选用于直接跳转 URL ‘https://your-test-site.com/login’ def __init__(self, driver): super().__init__(driver) # 必须调用父类初始化 def open(self): 打开登录页面 self.logger.info(f“打开登录页面: {self.URL}“) self.driver.get(self.URL) return self # 支持链式调用 def login(self, username, password): 登录操作 :param username: 用户名 :param password: 密码 :return: 通常返回登录后到达的页面对象如HomePage self.logger.info(f“执行登录操作用户名: {username}“) self.input_text(self.USERNAME_INPUT, username) self.input_text(self.PASSWORD_INPUT, password) self.click(self.LOGIN_BUTTON) # 登录后通常页面会跳转。这里不返回具体页面由测试用例处理 # 也可以在这里返回 HomePage(driver)但耦合度稍高 def get_error_message(self): 获取登录错误提示信息 if self.is_element_visible(self.ERROR_MSG, timeout3): # 短时间等待错误信息出现 return self.get_text(self.ERROR_MSG) return None接下来创建pages/home_page.py。from selenium.webdriver.common.by import By from base.base_page import BasePage class HomePage(BasePage): 网站主页对象 SEARCH_INPUT (By.NAME, ‘q’) # 假设搜索框name为‘q’ SEARCH_BUTTON (By.CSS_SELECTOR, ‘button.search-btn’) USER_AVATAR (By.ID, ‘user-avatar’) # 登录成功后显示的用户头像 def __init__(self, driver): super().__init__(driver) def is_user_logged_in(self): 通过检查用户头像是否存在判断是否登录成功 return self.is_element_visible(self.USER_AVATAR, timeout5) def search_for(self, keyword): 在首页进行搜索 :param keyword: 搜索关键词 :return: 搜索结果的页面对象便于链式操作 self.logger.info(f“在首页搜索: {keyword}“) self.input_text(self.SEARCH_INPUT, keyword) self.click(self.SEARCH_BUTTON) # 导入避免循环引用 from pages.search_page import SearchResultPage return SearchResultPage(self.driver) # 返回结果页对象注意事项在home_page.py中我们from pages.search_page import SearchResultPage放在了方法内部。这是因为home_page.py和search_page.py可能会相互引用放在内部可以避免Python的循环导入错误。这是一种常见的处理技巧。3.4 集成测试框架使用pytest管理驱动与用例现在我们需要一个地方来创建WebDriver实例并把它传递给我们的页面对象。conftest.py是pytest的全局配置文件在这里定义fixture是标准做法。# conftest.py import pytest from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager pytest.fixture(scope“function”) # 每个测试函数运行一次 def driver(): 提供WebDriver实例的fixture # 使用webdriver-manager自动管理ChromeDriver版本省去手动下载的麻烦 service Service(ChromeDriverManager().install()) options webdriver.ChromeOptions() options.add_argument(‘--headless’) # 无头模式不打开浏览器窗口适合CI环境 options.add_argument(‘--no-sandbox’) options.add_argument(‘--disable-dev-shm-usage’) driver webdriver.Chrome(serviceservice, optionsoptions) driver.implicitly_wait(5) # 设置隐式等待备用主要依赖BasePage的显式等待 driver.maximize_window() yield driver # 将driver对象提供给测试用例 # 测试结束后执行清理 driver.quit() pytest.fixture(scope“function”) def login_page(driver): 提供LoginPage实例的fixture from pages.login_page import LoginPage page LoginPage(driver) page.open() # 自动打开登录页 return page pytest.fixture(scope“function”) def home_page(driver): 提供HomePage实例的fixture from pages.home_page import HomePage return HomePage(driver)conftest.py中的fixture是pytest的精华。scope“function”意味着每个测试函数都会获得一个全新的driver和页面实例保证了测试之间的独立性避免了状态污染。4. 基于POM编写高质量测试用例框架搭好了终于到了最激动人心的部分——写用例。基于POM的用例应该像写用户故事一样简单。4.1 用例结构清晰、独立、可读在test_cases/test_login_and_search.py中我们编写第一个测试用例。import pytest import logging class TestLoginAndSearch: 登录与搜索功能测试集 def test_successful_login_and_search(self, driver, login_page, home_page): 测试用例使用有效账号登录并在首页成功搜索商品 步骤 1. 打开登录页 2. 输入有效用户名和密码登录 3. 验证登录成功首页显示用户头像 4. 在首页搜索关键词‘Python’ 5. 验证搜索结果页面标题包含关键词 # 1. 登录操作 # login_page fixture已经打开了登录页 login_page.login(“test_userexample.com”, “your_secure_password”) # 2. 断言登录成功 # 使用home_page fixture提供的实例此时driver已在登录后页面 assert home_page.is_user_logged_in() is True, “登录后未显示用户头像登录可能失败” # 3. 执行搜索并获取搜索结果页对象 search_result_page home_page.search_for(“Python”) # 4. 断言搜索结果页标题 # 假设SearchResultPage有一个获取标题的方法 # 这里我们直接使用driver的title并断言 # 更佳实践是在SearchResultPage中封装一个get_page_title方法 assert “Python” in driver.title, f“搜索结果页标题不包含‘Python’实际标题为: {driver.title}“ # 记录成功日志 logging.info(“测试用例 test_successful_login_and_search 执行成功”) def test_login_with_invalid_password(self, login_page): 测试用例使用错误密码登录应提示错误信息 # 直接使用无效密码登录 login_page.login(“test_userexample.com”, “wrong_password”) # 获取并断言错误信息 error_msg login_page.get_error_message() assert error_msg is not None, “输入错误密码后未出现预期的错误提示信息” assert “密码错误” in error_msg or “invalid” in error_msg.lower(), f“错误信息不符合预期: {error_msg}“这个测试用例展示了基于POM的典型写法用例即文档函数名和文档字符串清晰描述了测试场景。逻辑清晰只有业务步骤登录、搜索和业务断言是否登录成功、标题是否正确。高度可读即使非技术人员也能理解这个测试在验证什么。依赖注入通过pytest.fixture自动注入driver和page对象用例本身不关心它们的创建和销毁。4.2 数据驱动测试让用例更强大上面的用例把测试数据用户名、密码硬编码在脚本里了。这不利于维护和扩展。我们可以使用pytest的pytest.mark.parametrize装饰器来实现数据驱动。import pytest class TestDataDrivenLogin: 数据驱动登录测试 # 测试数据列表中的每个元组都是一组独立的测试数据 pytest.mark.parametrize(“username, password, expected_success”, [ (“correctuser.com”, “correct_pwd”, True), # 正确账号 (“wronguser.com”, “correct_pwd”, False), # 错误用户名 (“correctuser.com”, “wrong_pwd”, False), # 错误密码 (“”, “correct_pwd”, False), # 用户名为空 (“correctuser.com”, “”, False), # 密码为空 ]) def test_login_with_different_data(self, driver, username, password, expected_success): 使用多组数据测试登录功能 from pages.login_page import LoginPage from pages.home_page import HomePage login_page LoginPage(driver) login_page.open() login_page.login(username, password) home_page HomePage(driver) actual_success home_page.is_user_logged_in() assert actual_success expected_success, \ f“登录断言失败! 用户名: ‘{username}‘, 密码: ‘{password}‘. 期望成功: {expected_success}, 实际成功: {actual_success}“数据驱动将测试逻辑与测试数据分离。你只需要维护这个数据列表就能轻松扩展大量的测试场景如边界值、异常值测试。这是提升测试覆盖率和效率的关键手段。4.3 页面跳转与对象传递的最佳实践在POM中一个常见的难题是页面操作后发生了跳转如何优雅地获取新页面的对象上面home_page.search_for()方法返回SearchResultPage实例是一种方式返回新页面对象。另一种常见模式是在测试用例中显式创建。两种方式各有优劣返回新页面对象封装性好页面流清晰。但可能导致页面类之间的导入依赖。用例中显式创建依赖关系明确但用例代码会稍显冗长。我的经验是对于明确的、单向的页面流如 登录页 - 主页 主页 - 搜索结果页使用返回新页面对象的方式代码更流畅。对于不确定的或分支较多的跳转则在用例中根据情况创建。5. 高级技巧与常见问题排查5.1 处理动态元素与复杂等待现代Web应用大量使用Ajax和前端框架元素经常动态加载。单纯的presence_of_element_located可能不够。# 在BasePage中增加更强大的等待方法 def wait_for_element_clickable(self, locator, timeoutNone): 等待元素可点击 wait_time timeout or self.timeout return WebDriverWait(self.driver, wait_time).until( EC.element_to_be_clickable(locator) ) def wait_for_text_in_element(self, locator, text, timeoutNone): 等待元素中包含特定文本 wait_time timeout or self.timeout return WebDriverWait(self.driver, wait_time).until( EC.text_to_be_present_in_element(locator, text) ) # 在具体的Page类中使用 class ProductPage(BasePage): ADD_TO_CART_BUTTON (By.ID, ‘add-to-cart’) def add_to_cart(self): # 等待“加入购物车”按钮变为可点击状态例如库存加载完成后 self.wait_for_element_clickable(self.ADD_TO_CART_BUTTON).click() # 等待操作成功的提示信息出现 success_locator (By.CLASS_NAME, ‘success-msg’) self.wait_for_text_in_element(success_locator, “已加入购物车”)避坑技巧对于动态加载的列表或表格避免使用find_elements后直接按索引取元素如elements[0]因为元素可能尚未完全加载。应该先等待至少一个元素出现wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ‘.item’)))再使用find_elements。5.2 使用Page Factory模式简化定位器声明如果你觉得在每个Page类里写一堆By.ID的定位器属性很繁琐可以考虑使用Page Factory模式源自Java的Selenium在Python中可通过selenium.webdriver.support.pagefactory模拟。它能用装饰器或元类来延迟查找元素。但对于Python而言显式声明定位器元组的方式更主流、更清晰我个人更推荐。5.3 常见问题排查速查表在实践POM时你肯定会遇到各种问题。下面这个表格总结了我踩过的坑和解决方案问题现象可能原因排查步骤与解决方案NoSuchElementException元素找不到1. 定位器写错了。2. 页面尚未加载完成。3. 元素在iframe或shadow DOM内。4. 页面发生了跳转或刷新旧的元素引用失效。1.优先检查定位器在浏览器开发者工具中手动用$x或$$验证。2.增加等待使用BasePage中封装的显式等待而非sleep。3.切换上下文如果是iframe使用driver.switch_to.frame()。4.使用更稳定的定位器优先选择ID、name其次CSS Selector尽量避免绝对XPath。StaleElementReferenceException元素已过时你持有的WebElement对象对应的DOM元素已经不在当前页面被刷新、删除、重建。这是POM中常见错误。解决方案是“用时再找”。不要在Page类属性中存储WebElement实例如self.button driver.find_element(...)而应该只存储定位器(By.ID, ‘btn’)。每次操作时通过定位器重新查找。这正是我们BasePage中click(self, locator)方法的设计哲学。测试用例之间相互影响1. 浏览器状态未清理Cookies、LocalStorage。2.driver或page对象被复用残留了上一个测试的数据。1.确保测试独立性使用pytest的scope“function”的fixture每个测试都有全新的driver。2.测试前置清理在fixture或setup_method中可以添加driver.delete_all_cookies()和driver.refresh()。3.使用无痕模式或新用户会话。运行速度慢1. 隐式等待时间设置过长。2. 使用了大量的time.sleep。3. 网络或应用本身慢。1.优化等待策略用显式等待替代隐式等待和sleep。显式等待是“等到条件满足”而sleep是“死等固定时间”。2.并行执行使用pytest-xdist插件并行运行测试用例。3.启用无头模式(--headless)节省GUI渲染时间。定位器维护成本依然高页面频繁改版需要经常更新定位器。1.与开发约定为关键测试元素添加稳定的>pytest test_cases/ -v --htmlreports/report.html --self-contained-html--self-contained-html会生成一个包含所有CSS/JS的单文件HTML报告方便分享。为了让报告更有用我们可以在conftest.py中添加钩子自动捕获失败用例的截图并附加到报告中# conftest.py (追加内容) import pytest from selenium.webdriver.remote.webdriver import WebDriver pytest.hookimpl(hookwrapperTrue) def pytest_runtest_makereport(item, call): 钩子函数用于在测试报告生成时附加额外信息如截图 outcome yield report outcome.get_result() # 只处理测试失败或错误的情况 if report.when “call” and report.failed: # 尝试从fixture中获取driver对象 driver_fixture item.funcargs.get(‘driver’) if driver_fixture and isinstance(driver_fixture, WebDriver): # 调用我们BasePage的截图方法 # 这里需要一点技巧来获取page对象或者直接使用driver try: # 简单示例直接用driver截图 screenshot driver_fixture.get_screenshot_as_base64() html f‘divimg src“data:image/png;base64,{screenshot}” alt“screenshot” style“width:600px;”/div’ report.extra getattr(report, ‘extra’, []) [pytest_html.extras.html(html)] except: pass # 截图失败也不影响主流程这样每次测试失败HTML报告中都会自动包含失败时刻的屏幕截图对于远程调试或历史追溯 invaluable。从零到一搭建一个基于PO设计模式的Web UI自动化测试框架核心在于理解“分离”与“封装”的思想。将易变的元素定位封装在Page类中将稳定的业务逻辑体现在测试用例里。这个过程开始可能会觉得多了一层抽象有点麻烦但一旦项目迭代超过两三个版本你就会发现前期投入的这点设计成本在维护阶段会带来十倍百倍的回报。我自己的经验是一个设计良好的POM框架能让自动化测试代码的寿命延长数倍真正成为支撑敏捷开发和质量保障的可靠资产。最后一个小建议在团队内推行POM时可以先在一个核心流程上试点让大家看到其维护性的优势再逐步推广到全项目这样阻力会小很多。

相关新闻

C++开发者如何驯服AI?内存安全、SIMD指令与实时推理场景下的代码生成心法

C++开发者如何驯服AI?内存安全、SIMD指令与实时推理场景下的代码生成心法

内存安全与资源管理现代C(C17/20)提供智能指针(std::unique_ptr、std::shared_ptr)和RAII机制管理内存。结合-fsanitizeaddress编译选项可检测内存泄漏。对于AI模型权重等大型数据,建议使用std::vector或专用内存池&am…

2026/7/2 22:52:59阅读更多 →
5分钟快速上手:BepInEx终极Unity游戏插件框架指南

5分钟快速上手:BepInEx终极Unity游戏插件框架指南

5分钟快速上手:BepInEx终极Unity游戏插件框架指南 【免费下载链接】BepInEx Unity / XNA game patcher and plugin framework 项目地址: https://gitcode.com/GitHub_Trending/be/BepInEx 还在为Unity游戏添加自定义功能而烦恼吗?想要为心爱的游戏…

2026/7/2 22:52:59阅读更多 →
5分钟掌握B站视频永久保存技巧:m4s-converter完全指南

5分钟掌握B站视频永久保存技巧:m4s-converter完全指南

5分钟掌握B站视频永久保存技巧:m4s-converter完全指南 【免费下载链接】m4s-converter 一个跨平台小工具,将bilibili缓存的m4s格式音视频文件合并成mp4 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 你是否曾遇到过这样的困境&…

2026/7/2 22:52:59阅读更多 →
分组气泡图(Packedbubble)实战:全球车企市值分层聚合可视化

分组气泡图(Packedbubble)实战:全球车企市值分层聚合可视化

本车企市值聚合气泡案例充分体现 Highcharts 专业气泡可视化能力&#xff0c;解决传统散点气泡布局混乱、多分类无法自动分区的痛点。完整可预览修复 HTML<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><met…

2026/7/3 0:13:42阅读更多 →
风控安全产品系统设计的思考与实践

风控安全产品系统设计的思考与实践

本篇文章会从系统架构设计的角度&#xff0c;分享在对业务安全风控相关基础安全产品进行系统设计时遇到的问题难点及其解决方案。 内容包括三部分&#xff1a;&#xff08;1&#xff09;风控业务架构&#xff1b;&#xff08;2&#xff09;基础安全产品的职责&#xff1b;&…

2026/7/3 0:13:42阅读更多 →
我的小树林

我的小树林

如果有非技术人员问你&#xff0c;HTML5是什么&#xff0c;你会怎么回答&#xff1f;新的HTML规范。。。给浏览器提供了牛逼能力&#xff0c;干以前不能干的事。。。&#xff08;确切地说应该是给浏览器规定了许多新的接口标准&#xff0c;要求浏览器实现牛逼的功能。。。 这里…

2026/7/3 0:13:42阅读更多 →
如何用SkillBridge实现Python与Cadence Virtuoso的无缝跨语言集成

如何用SkillBridge实现Python与Cadence Virtuoso的无缝跨语言集成

如何用SkillBridge实现Python与Cadence Virtuoso的无缝跨语言集成 【免费下载链接】skillbridge A seamless python to Cadence Virtuoso Skill interface 项目地址: https://gitcode.com/gh_mirrors/sk/skillbridge SkillBridge是一款专为电子设计自动化&#xff08;ED…

2026/7/3 0:13:42阅读更多 →
【ChatGPT批量任务处理终极指南】:20年AI工程实战总结的7种高并发、低错误率自动化方案

【ChatGPT批量任务处理终极指南】:20年AI工程实战总结的7种高并发、低错误率自动化方案

更多请点击&#xff1a; https://kaifayun.com 第一章&#xff1a;ChatGPT批量任务处理的核心挑战与设计哲学 在企业级AI应用中&#xff0c;将ChatGPT接入批量任务流水线&#xff08;如日志分析、多文档摘要、客服工单分类&#xff09;时&#xff0c;高频并发调用常触发API限流…

2026/7/3 0:13:42阅读更多 →
97.纯 ST 语言实现!S7-1200 电机正反转完整工程(带故障记忆)

97.纯 ST 语言实现!S7-1200 电机正反转完整工程(带故障记忆)

摘要 可编程逻辑控制器(PLC)是工业自动化领域的核心控制设备,广泛应用于流水线控制、过程控制、运动控制等场景。本文从PLC的硬件架构与扫描周期原理出发,以IEC 61131-3标准中的结构化文本(ST)语言为载体,系统讲解PLC编程的核心逻辑。文章提供一套完整的电机正反转控制…

2026/7/3 0:08:41阅读更多 →
AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

6个月前的2025年12月&#xff0c;Boris Cherny 公开宣布自己卸载了 IDE。一时间&#xff0c;Vibe Coding 成了全行业最热的话题。6个月后&#xff0c;当我们回过头来拉一份真实账本&#xff0c;发现事情远没有"一句话生成一个App"那么浪漫。本文从产品经理和研发两个…

2026/7/2 12:10:34阅读更多 →
审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

引言&#xff1a;审计结束三个月了&#xff0c;审计员的权限还没关某城商行每年按照监管要求开展至少一次数据安全审计。审计期间&#xff0c;内审部门需要抽样检查各类业务数据——交易流水、客户信息、员工操作日志、权限配置记录。这些数据分布在不同系统中&#xff0c;审计…

2026/7/2 12:10:34阅读更多 →
LV3296与PIC18F45K22的UART通信与USB扩展方案

LV3296与PIC18F45K22的UART通信与USB扩展方案

1. LV3296与PIC18F45K22的硬件搭档解析在嵌入式数据采集系统中&#xff0c;LV3296条形码扫描模块与PIC18F45K22微控制器的组合堪称经典搭配。LV3296作为一款工业级条码扫描头&#xff0c;其核心是一颗高性能CMOS图像传感器&#xff0c;配合专用解码芯片&#xff0c;能自动识别包…

2026/7/3 0:03:41阅读更多 →
AI初创生存指南:6个月完成可信度验证闭环

AI初创生存指南:6个月完成可信度验证闭环

1. 这不是“逆袭指南”&#xff0c;而是一份AI初创公司真实生存手记“How To Beat Odds As an AI Startup?”——这个标题乍看像一句热血口号&#xff0c;但在我带过7个从0到1的AI产品团队、亲手踩过融资失败、技术债崩盘、客户POC卡在最后一公里等23类典型坑之后&#xff0c;…

2026/7/3 0:03:41阅读更多 →
多模态+推理链+RAG 2.0+智能体:工业级AI系统落地四支柱

多模态+推理链+RAG 2.0+智能体:工业级AI系统落地四支柱

1. 这不是又一篇“AI趋势速览”&#xff0c;而是一份实操者手记&#xff1a;当多模态、推理链、检索增强与智能体协作真正撞进工程现场“LAI #73”这个编号本身就像一个暗号——它不属于某家大厂的白皮书&#xff0c;也不是学术会议的议程表&#xff0c;而是长期泡在模型训练集…

2026/7/3 0:03:41阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时&#xff0c;发现推理速度只有可怜的 1-2 FPS&#xff0c;而别人的演示视频却能跑到 30 FPS 以上&#xff0c;那么问题很可能不在模型本身&#xff0c;而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后&#xff0c;会直接使用官方示例…

2026/7/2 0:33:58阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一&#xff1a;为什么你需要了解 Coze 和 Dify&#xff1f;如果你对 AI 应用开发感兴趣&#xff0c;但一看到“大模型”、“智能体”、“工作流”这些词就头疼&#xff0c;觉得门槛太高&#xff0c;那这篇文章就是为你准备的。很多开发者&#xff0c;包括我自己&#…

2026/7/2 1:32:11阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会&#xff1a;配图一直是个让人头疼的问题。2026年&#xff0c;AI生图工具已经非常成熟了&#xff0c;但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1&#xff1a;速度之王2026年6月11日&#xff0c…

2026/7/2 1:50:13阅读更多 →