当前位置: 首页 > news >正文

python 自动化学习(四) pyppeteer 浏览器操作自动化

背景

之前我在工作中涉及到了很多地方都是重复性的页面点点点工作,又因为安全保密原则不开放接口和数据库,只有一个页面来提供点击进行操作,就想着用前面学的自动化来实现,但发现前面学的模拟操作对浏览器来说并没有那么友好,而后改用“selenium”,但是存在一个问题,我这里并不能直接访问外网,好不容易找到selenium的库文件,发现又需要相对应版本的浏览器引擎,导致我无法使用,在此期间我发现了另一个不需要浏览器引擎的库pyppeteer 成功实现了一部分功能,这里做一下笔记

 介绍

1、selenium   //跨浏览器,官方维护的比较好,资料也多,各个版本比较稳定,源码读起来舒服//缺点是配置时需要留心程序语言的版本和驱动版本以及浏览器版本,还有就是本身不支持步//需要重写源码或者利用grid分布式来实现异步2、pyppeteer  //是基于chrome官方为chromium定制的自动化测试框架puppeteer而//实现的一个python包装的非官方版本框架,最后一次更新是在2018年//优点就是速度比selenium快,支持异步,常被拿来做爬虫,//缺点就是兼容性很差,而且它没有跟随chromium以及puppeteer的迭代而更新//使用时会有很多问题。

安装

pip install pyppeteer==1.0.2

入门案例

我们打开浏览器、输入、点击按钮什么的都是是耗时的操作,我们下面通过使用异步关键字 async 和 await,定义了一个异步函数 main。通过在异步函数中使用 await 关键字,可以将耗时的操作转化为非阻塞的异步调用

import asyncio                          # 导入 asyncio 模块,用于编写异步代码
from pyppeteer import launch           # 导入 pyppeteer 的 launch 函数,用于启动浏览器async def main():                      # 定义一个异步函数 mainasyncio.get_event_loop().run_until_complete(main())    # 运行 main 函数

一、定义浏览器并打开页面

import asyncio
from pyppeteer import launchasync def main():browser = await launch(executablePath="C:\Program Files\Google\Chrome\Application\chrome.exe",headless=False,args=['--start-maximized'])page = await browser.newPage()await page.setViewport({'width':0,'height':0,'deviiceScaleFactor':1})await page.goto('https://www.baidu.com')await page.waitFor(10000)await browser.close()asyncio.get_event_loop().run_until_complete(main())

参数说明

async def main():browser = await launch(executablePath="C:\Program Files\Google\Chrome\Application\chrome.exe",headless=False,args=['--start-maximized'])#launch 定义一个浏览器实例#executablePath   本地谷歌浏览器路径#headless=False   有界面的浏览器#args=['--start-maximized']浏览器窗口最大化//在浏览器上创建一个新页面page = await browser.newPage()   //width 和height 自动匹配浏览器大小//deviiceScaleFactor 将页面的视口设置为浏览器的默认大小,并将设备像素比设置为 1await page.setViewport({'width':0,'height':0,'deviiceScaleFactor':1})//打开浏览器并跳转到指定地址   await page.goto('https://www.google.com')  //上面任务结束后等待10sawait page.waitFor(10000)//关闭浏览器await browser.close()

我们这里能打开浏览器并且跳转了,下面我们正常情况下需要做的就是模拟鼠标键盘的一些操作,我们这里以码云的登录注册平台来做测试

#注册地址,可能有变动直接百度搜索
https://gitee.com/signup?redirect_to_url=%2F%3Fchannel_utm_content%3D%25E5%25B9%25BF%25E5%2591%258A%25E8%2583%258C%25E6%2599%25AF%25E5%259B%25BE%26channel_utm_medium%3Dsem%26channel_link_type%3Dweb%26channel_utm_source%3D%25E7%2599%25BE%25E5%25BA%25A6%26sat_cf%3D2%26channel_utm_campaign%3D%25E5%2593%2581%25E4%25B8%2593%26channel_utm_term%3D%25E5%25B9%25BF%25E5%2591%258A%25E8%2583%258C%25E6%2599%25AF%25E5%259B%25BE%26_channel_track_key%3Du1BDg7fB%26link_version%3D1%26wl_src%3Dbaidu

二、寻找页面元素信息

浏览器页面根据我们分辨率大小和窗口大小,跳转浏览器的位置都会导致我们无法直接通过之前的方法获取坐标,这里我们依赖的是直接获取web页面的元素信息(span a dir id class等等)通过他们定位具体的元素坐标

登录页面查看元素

我们登录到注册页面按F12 进入开发者模式,点击查看栏能看到html的信息,点击左边的箭头,选择我们要查看的页面元素,下面图中是选择了第一个输入框的位置

 

 得到html信息

<input class="session-register__name" required="required" placeholder="姓名" maxlength="60" size="60" type="text" name="user[name]" id="user_name">

三、常见的几种获取元素坐标方法

各个场景的html编写的不相同,同一种方法切换场景后很可能就不好使了,这里放几种我常用的方法

1、通过元素class或id获取坐标

#基于class名称获取坐标
async def click_radio(page,selector):await page.waitForSelector(selector)  #等待元素出现element = await page.querySelector(selector)  #查找指定的元素信息if element:box = await  element.boundingBox()  #获取元素坐标和尺寸x = box['x']y = box['y']widht = box['width']height = box['height']await page.mouse.move(x + widht / 2,  y + height / 2)await page.mouse.down()  #模拟鼠标点击一次await page.mouse.up()else:print("element not found")

调用函数

async def main():
...#修改地址为码云await page.goto('https://gitee.com/signup?redirect_to_url=%2F%3Fchannel_utm_content%3D%25E8%25BF%259B%25E5%2585%25A5%25E5%25AE%2598%25E7%25BD%2591%26channel_utm_medium%3Dsem%26channel_link_type%3Dweb%26channel_utm_source%3D%25E7%2599%25BE%25E5%25BA%25A6%26sat_cf%3D2%26channel_utm_campaign%3D%25E5%2593%2581%25E4%25B8%2593%26channel_utm_term%3D%25E4%25B8%25BB%25E6%258C%2589%25E9%2592%25AE1%26_channel_track_key%3Dsee7zmAJ%26link_version%3D1%26wl_src%3Dbaidu')#调用自定义函数,传参page  class名称前面要加点"."await click_radio(page,".session-register__name")#模拟输入文本信息await page.keyboard.type("12345678")
...
asyncio.get_event_loop().run_until_complete(main())

注意

上面案例中使用的是class获取的坐标,如果没有定义class或者有多个相同的class时可以通过id获取,区别在于class传参是 "." 加class名称   而id传参是  "#" 加id名称

小知识

//其实在遇到不是特别复杂的情况下,可以不用上面的方法,比如输入账户密码之类的
await page.waitForSelector(#id名称/.类名)  

2、通过文本获取坐标

这个在申请某些东西的时候可能会经常用到,比如申请云服务,某某产品,自研平台等等,会有大量需要挨个点击的图标,用上面第一个的时候不好使了就用这个方法

获取按钮html

<button name="button" type="submit" id="btn-submit" class="ui orange fluid submit button register-btn-submit large" sa_evt="click_GiteeCommunity_signup_signup">立即注册</button>

 向上面有文本内容显示的就可以用,我们获取到他的文本内容"立即注册" 和元素名称button

async def click_center(page,selector,type):#定义检索元素格式test = "//" + type +"[text()=" + "\'" + selector + "\'" + "]"element = await page.waitForXPath(test)  #获取对应元素box = await element.boundingBox()   #获取坐标target_x = box['x'] + box['width'] // 2target_y = box['y'] + box['height'] // 2await page.mouse.click(target_x,target_y)

调用

async def main():browser = await launch(executablePath="C:\Program Files\Google\Chrome\Application\chrome.exe",headless=False,args=['--start-maximized'])page = await browser.newPage()await page.setViewport({'width':0,'height':0,'deviiceScaleFactor':1})await page.goto('https://gitee.com/signup?redirect_to_url=%2F%3Fchannel_utm_content%3D%25E8%25BF%259B%25E5%2585%25A5%25E5%25AE%2598%25E7%25BD%2591%26channel_utm_medium%3Dsem%26channel_link_type%3Dweb%26channel_utm_source%3D%25E7%2599%25BE%25E5%25BA%25A6%26sat_cf%3D2%26channel_utm_campaign%3D%25E5%2593%2581%25E4%25B8%2593%26channel_utm_term%3D%25E4%25B8%25BB%25E6%258C%2589%25E9%2592%25AE1%26_channel_track_key%3Dsee7zmAJ%26link_version%3D1%26wl_src%3Dbaidu')#调用函数,定义文本+元素并点击await click_center(page,"立即注册","button")await page.waitFor(10000)await browser.close()asyncio.get_event_loop().run_until_complete(main())

 测试通过上面俩方法能解决绝大部分问题,如果遇到的坐标有偏移,比如说我文本输入框在文本的右侧,我通常会用下面的方法

    target_x = box['x'] + box['width'] + 200target_y = box['y'] + box['height'] // 2

小知识

你遇到点击某个按钮后会跳转到下一步的时候,最好按钮按下后面的第一个步骤中添加 await page.waitForNavigation()  这个是等等页面加载完成

3、修改页面元素,然后基于页面元素输入

特殊情况下,上面的方法都不适用,比如说我遇到过一个常见,需要申请两个文件系统/app1 和/app2  一个50G  一个100G ,但他的输入框上只有一个class,并且每个class都是完全已有的,没有id什么其他的元素,特征就是初始值为一个输入框,可以新增一个输入框,我的想法我把第一个输入框的元素class进行修改,第二个元素出现时class类就和第一个元素不同了,然后基于这个修改后的class名称在做具体的操作

我这里通过密码那一栏获取到下面的代码
<input required="required" autocomplete="new-password" placeholder="密码不少于6位" data-password-regx="^(?=.*[0-9])(?=.*[a-zA-Z!@_#$%^&amp;*()\-+=,.?]).{6,32}$" type="password" name="user[password]" id="user_password">可以看到,他有一个id,但是没有做class我们用这个做实验

 案例

    await page.evaluate('''() => {const elements = document.querySelectorAll('#user_password');elements.forEach(element => {element.classList.add("ddd");});}''')#我们需要等等元素出现后在进行下面的操作await page.waitForSelector(".ddd")...#另外,单独说个事,如果切换页面后逻辑中存在多个等待页面加载完成,那么页面就不动了

可以看到上面图里他帮忙添加了一个class的名称,我们可以在后面去调用他,需要注意的是,如果你要添加的元素的id不唯一那么所有的元素都会去添加相同的class,如果碰到了多个class名称,如  app1 app2 app3    class的名称则需要设置为 .app1 .app2 .app3  如果是id则是 #app1  #app2 #app3

4、查找特定文本元素并点击

我又碰到一个特殊的案例,我没找到演示用的页面,当记录下笔记了,这个是应用于li的一个下拉选项的场景,和第二步类似

async def click_multiple(page,selector):elements = await page.JJ('li')for element in elements:text_content = await element.getProperty("textContent")text_content = await text_content.jsonValue()if selector in text_content:box = await  element.boundingBox()coordinates = {'x': box['x'],'y': box['y'],'width': box['width'],'height': box['height']}await element.click()

上面的函数是查找页面所有的 li 元素,并检查每个元素的文本内容中是否包含给定的选择器。如果找到了匹配的元素,则对该元素执行点击操作。

参数说明

async def click_multiple(page, selector):elements = await page.JJ('li')  # 查找页面中所有的 <li> 元素并保存在 elements 变量中for element in elements:  # 遍历 elements 中的每个元素text_content = await element.getProperty("textContent")  # 获取元素的文本内容text_content = await text_content.jsonValue()  # 将文本内容转换为 JSON 格式if selector in text_content:  # 检查文本内容是否包含给定的选择器box = await element.boundingBox()  # 获取元素的位置和大小coordinates = {'x': box['x'],  # 元素的 x 坐标'y': box['y'],  # 元素的 y 坐标'width': box['width'],  # 元素的宽度'height': box['height']  # 元素的高度}await element.click()  # 点击元素

四、读取excel表数据并使用

def open_xlsx():from openpyxl import load_workbookwb = load_workbook("111.xlsx")ws = wb["Sheet1"]data = []for row in ws.iter_rows(min_row=2): #从第二行开始算row_values = []for cell in row:row_values.append(cell.value)data.append(row_values)wb.close()return datadata = open_xlsx()#这里的user_list取出来的是表中每一行的数据,下面的0-1-2-3是每一列的数据
for user_list in data:print(user_list[0])

说明

def open_xlsx():from openpyxl import load_workbook# 打开 Excel 文件wb = load_workbook("111.xlsx")# 选择要读取的工作表ws = wb["Sheet1"]# 创建一个空的列表用于存储读取的数据data = []# 从第二行开始遍历每一行for row in ws.iter_rows(min_row=2):row_values = []# 遍历当前行的每一个单元格for cell in row:# 将单元格的值添加到行值列表中row_values.append(cell.value)# 将该行的值列表添加到数据列表中data.append(row_values)# 关闭 Excel 文件wb.close()# 返回读取的数据return data# 调用 open_xlsx 函数并获取数据
data = open_xlsx()# 遍历数据列表的每一行,并打印出每行的第一列数据
for user_list in data:print(user_list[0])

相关文章:

python 自动化学习(四) pyppeteer 浏览器操作自动化

背景 之前我在工作中涉及到了很多地方都是重复性的页面点点点工作&#xff0c;又因为安全保密原则不开放接口和数据库&#xff0c;只有一个页面来提供点击进行操作&#xff0c;就想着用前面学的自动化来实现&#xff0c;但发现前面学的模拟操作对浏览器来说并没有那么友好&…...

P1009 阶乘之和

[NOIP1998 普及组] 阶乘之和 题目描述 用高精度计算出 S 1 ! 2 ! 3 ! ⋯ n ! S 1! 2! 3! \cdots n! S1!2!3!⋯n!&#xff08; n ≤ 50 n \le 50 n≤50&#xff09;。 其中 ! 表示阶乘&#xff0c;定义为 n ! n ( n − 1 ) ( n − 2 ) ⋯ 1 n!n\times (n-1)…...

Linux内核源码剖析之TCP保活机制(KeepAlive)

写在前面&#xff1a; 版本信息&#xff1a; Linux内核2.6.24&#xff08;大部分centos、ubuntu应该都在3.1。但是2.6的版本比较稳定&#xff0c;后续版本本质变化也不是很大&#xff09; ipv4 协议 https://blog.csdn.net/ComplexMaze/article/details/124201088 本文使用案例…...

后端 springboot 给 vue 提供参数

前端 /** 发起新增或修改的请求 */requestAddOrEdit(formData) {debuggerif(formData.id undefined) {formData.id }getAction(/material/getNameModelStandard, {standard: this.model.standard,name: this.model.name,model: this.model.model}).then((res) > {if (res …...

《vue3实战》运用radio单选按钮或Checkbox复选框实现单选多选的试卷制作

文章目录 目录 系列文章目录 1.《Vue3实战》使用axios获取文件数据以及走马灯Element plus的运用 2.《Vue3实战》用路由实现跳转登录、退出登录以及路由全局守护 3.《vue3实战》运用Checkbox复选框实现单选多选的试卷展现&#xff08;本文&#xff09; 文章目录 前言 radio是什…...

排序算法-冒泡排序(C语言实现)

简介&#x1f600; 冒泡排序是一种简单但效率较低的排序算法。它重复地扫描待排序元素列表&#xff0c;比较相邻的两个元素&#xff0c;并将顺序错误的元素交换位置&#xff0c;直到整个列表排序完成。 实现&#x1f9d0; 以下内容为本人原创&#xff0c;经过自己整理得出&am…...

星际争霸之小霸王之小蜜蜂(一)

目录 前言 一、安装pygame库 1、pygame库简介 2、在windows系统安装pygame库 二 、搭建游戏框架 1、创建游戏窗口 2、改变窗口颜色 总结 前言 大家应该都看过或者都听说过python神书“大蟒蛇”&#xff0c;上面有一个案例是《外星人入侵》&#xff0c;游戏介绍让我想起了上…...

图数据库_Neo4j基于docker服务版安装_Neo4j Desktop桌面版安装---Neo4j图数据库工作笔记0004

然后我们来看看如何用docker来安装Neo4j community server 首先去执行docker pull neo4j:3.5.22-community 去拉取镜像 然后执行命令就可以安装了 可以用docker ps查看一下 看看暴露了哪些端口 然后再看一下访问一下这个时候,要用IP地址了注意 然后再来看一下安装Desktop 去下…...

docker-compose部署可道云

文章目录 一. Mac1.1 下载源码1.2 部署1.2.1 修改密码部署(可忽略)1.2.2 直接部署 1.3 卸载1.4 访问 二. Win2.1 下载源码2.2 部署2.2.1 修改密码部署(可忽略)2.2.2 直接部署 2.3 卸载 一. Mac 1.1 下载源码 mkdir -p /Users/wanfei/docker-compose && cd /Users/wan…...

Windows上使用FFmpeg实现本地视频推送模拟海康协议rtsp视频流

场景 Nginx搭建RTMP服务器FFmpeg实现海康威视摄像头预览&#xff1a; Nginx搭建RTMP服务器FFmpeg实现海康威视摄像头预览_nginx rtmp 海康摄像头_霸道流氓气质的博客-CSDN博客 上面记录的是使用FFmpeg拉取海康协议摄像头的rtsp流并推流到流媒体服务器。 如果在其它业务场景…...

单片机之从C语言基础到专家编程 - 4 C语言基础 - 4.8 运算符

1.算术运算符 运算符名称备注加法运算符双目运算&#xff0c;a b-减法运算符双目运算&#xff0c;a - b*乘法运算符双目运算&#xff0c;a * b/除法运算符双目运算&#xff0c;a / b%求余运算符双目运算, a % b自增运算符单目运算, a–自减运算符单目运算, a– 2.关系运算符…...

轮腿机器人的PID控制

1 PID介绍 PID&#xff08;Proportional Integral Derivative&#xff09;控制系统。其实质是根据输入的偏差值&#xff0c;按比例、积分、微分的函数关系进行运算&#xff0c;运算结果用以输出进行控制。它是在长期的工程实践中总结出来的一套控制方法&#xff0c;实际运行经…...

ChatGPT爆火,会给教育带来什么样的影响或者冲击?

近来&#xff0c;人工智能聊天机器人ChatGPT连上热搜&#xff0c;火爆全网。ChatGPT拥有强大的信息整合能力、自然语言处理能力&#xff0c;可谓是“上知天文&#xff0c;下知地理”&#xff0c;而且还能根据要求进行聊天、撰写文章等。 ChatGPT一经推出&#xff0c;便迅速在社…...

Servlet+JDBC实战开发书店项目讲解第三篇:商品查询实现

ServletJDBC实战开发书店项目讲解第三篇&#xff1a;商品查询实现 本篇博客将介绍如何在ServletJDBC实战开发书店项目中实现商品查询功能。我们将从设计数据库表结构和实体类开始&#xff0c;一步一步详细讲解代码实现过程&#xff0c;包括前端页面的设计和后端Servlet代码的编…...

爬虫逆向实战(十七)--某某丁简历登录

一、数据接口分析 主页地址&#xff1a;某某丁简历 1、抓包 通过抓包可以发现数据接口是submit 2、判断是否有加密参数 请求参数是否加密&#xff1f; 通过查看“载荷”模块可以发现有一个enPassword加密参数 请求头是否加密&#xff1f; 通过查看请求头可以发现有一个To…...

《安富莱嵌入式周报》第320期:键盘敲击声解码, 军工级boot设计,开源CNC运动控制器,C语言设计笔记,开源GPS车辆跟踪器,一键生成RTOS任务链表

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 视频版&#xff1a; https://www.bilibili.com/video/BV1Cr4y1d7Mp/ 《安富莱嵌入式周报》第320期&#xff1a;键盘敲击…...

DRF 缓存

应用环境 django4.2.3 &#xff0c;python3.10 由于对于服务而言&#xff0c;有些数据查询起来比较费时&#xff0c;所以&#xff0c;对于有些数据&#xff0c;我们需要将其缓存。 最近做了一个服务&#xff0c;用的时 DRF 的架构&#xff0c;刚好涉及缓存&#xff0c;特此记…...

Collada .dae文件格式简明教程【3D】

当你从互联网下载 3D 模型时&#xff0c;可能会在格式列表中看到 .dae 格式。 它是什么&#xff1f; 推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景。 1、Collada DAE概述 COLLADA是COLLAborative Design Activity&#xff08;中文&#xff1a;协作设计活动&#xff09…...

在K8s上处理nginx

基本说明 创建一个名为ssl的TLS类型的Secret对象&#xff0c;用于存储证书和密钥信息。 kubectl create secret tls ssl --certserver.crt --keyserver.key配置Nginx的events块&#xff0c;设置worker连接数为1024。 events {worker_connections 1024; }配置Nginx的http块&a…...

嵌入式:ARM Day4

一、自己编写代码实现三盏灯点亮 源码&#xff1a; .text .global _start _start: 进行一次初始化bl RCC_INITbl LED1_INITbl LED2_INITbl LED3_INITb looploop: 循环开关灯bl LED1_ONbl delay_1sbl LED1_OFFbl delay_1sbl LED2_ONbl delay_1sbl LED2_OFFbl delay_1sbl…...

成都不良资产收包出包难?专业处置破局存量盘活困境

不仅如此&#xff0c;规范化的不良资产处置模式&#xff0c;还能助力区域化解债务风险&#xff0c;稳定地方金融环境&#xff0c;激活存量资产活力&#xff0c;对地方经济发展起到正向推动作用。不良资产收包出包&#xff0c;拼的从来不是蛮力与时间&#xff0c;而是专业、合规…...

从COCO到Cityscapes:实例分割指标mAP和mIOU在不同数据集上的表现差异与陷阱

从COCO到Cityscapes&#xff1a;实例分割指标mAP和mIOU在不同数据集上的表现差异与陷阱 当你在COCO数据集上训练的Mask R-CNN模型取得了0.85的mAP&#xff0c;满怀信心地将其部署到自动驾驶项目的Cityscapes数据集上时&#xff0c;却发现mIOU从预期的0.75骤降到0.52——这种&qu…...

并发编程小记---5.17

final类型的特点&#xff1a;final 变量&#xff1a;赋值后不能改&#xff08;引用地址不可变&#xff09;final 方法&#xff1a;不能被子类重写final 类&#xff1a;不能被继承引用类型&#xff1a;Java 数据类型就两种&#xff1a;基本数据类型&#xff1a;byte short int l…...

转行对谈:转向AI是破茧成蝶还是折翼未来?

01前言&#xff5c;AI时代下的土建人 一、AI浪潮&#xff1a;开启一个崭新的时代 人工智能&#xff08;AI&#xff09;已经从学术前沿走向产业中心&#xff0c;成为当前时代最具颠覆性的技术之一。从最早“出圈”的对话式模型ChatGPT的火爆到AI绘画、AI写作等AIGC&#xff08;生…...

TP-LINK AX300 网卡驱动

TP-LINK AX300无线网卡的驱动一直不更新&#xff0c;只好自己动手 适配&#xff1a;TL-XDN6000H 免驱版 操作系统&#xff1a;Ubuntu 24.04.4 LTS 内核版本&#xff1a;6.17.0-29-generic #29~24.04.1-Ubuntu https://download.csdn.net/download/zzzhy/92882718...

从电路哲学到工程实践:无源与有源器件设计心法全解析

1. 从“人生如电路”到“玩电路设计&#xff0c;也可以这样有情怀”看到“人生如电路”这个比喻&#xff0c;很多电子爱好者或工程师都会心一笑。它把抽象的电子元件特性&#xff0c;巧妙地映射到我们每个人的学习、工作和生活状态上&#xff0c;确实挺有道理&#xff0c;也很有…...

开发同城短途散步治愈路线生成程序,根据定位生成小众风景散步路线,适配日常解压。

基于创新思维与创业实验方法的「同城短途散步治愈路线生成程序&#xff0c;保持中立、去营销化、无引流。 一、实际应用场景描述 城市上班族常见状态&#xff1a; - 工作日长期处于高压、久坐状态 - 周末不想远行&#xff0c;但市内缺乏“新鲜感” - 热门公园人多、吵闹&…...

高端工程场景实测:OpenAI Codex CLI 在微服务重构中的 3 类能力边界

1. 微服务重构现场:Codex CLI 不是万能胶,但能精准补上三块关键拼图 我接手一个运行了四年的电商微服务集群时,它正卡在「订单履约链路」的重构临界点上。17个服务、32个跨服务调用点、4种异步消息协议、2套数据库分片策略——人工梳理接口契约要两周,写迁移脚本要三天,验…...

【亲测免费】 ST官方开源电机库FOC5.0:电机控制的利器

ST官方开源电机库FOC5.0&#xff1a;电机控制的利器 【下载地址】ST官方开源电机库FOC5.0下载仓库 ST官方开源电机库FOC5.0 下载仓库本仓库提供ST官方开源的电机库FOC5.0的资源文件下载 项目地址: https://gitcode.com/open-source-toolkit/a21b5 项目介绍 在电机控制领…...

Excel VBA编程实例(150例):助你轻松掌握办公自动化利器

Excel VBA编程实例(150例)&#xff1a;助你轻松掌握办公自动化利器 【下载地址】ExcelVBA编程实例150例资源下载 本仓库提供了一个名为“Excel VBA编程实例(150例)”的资源文件下载。该资源文件包含了150个Excel VBA编程实例&#xff0c;旨在帮助用户通过实际案例学习和掌握Exc…...