1. 自动化脚本的本质:先别急着写代码,先搞清楚要自动化什么
"自动化与脚本"这个话题,这几年被炒得越来越热。自动化测试、自动化办公、爬虫脚本、RPA机器人,好像谁不沾点自动化就跟不上时代。但你真去搜一圈,会发现绝大多数教程都在教具体工具——这个框架怎么装、那个命令怎么敲——很少有人先把"自动化到底在解决什么问题"讲透。
我自己最早接触这块,就是因为一个小需求:每周五下班前要手动处理几十份日志文件,做归档、清理超期文件、生成周报摘要。第一次手动跑完将近一个小时,第二次我开始想:"这种破事,能不能让电脑自己干?"于是第一次认真写了段Shell脚本。写完那一刻确实觉得"写代码好厉害",但后面做多了才明白,真正值钱的不是那几行代码,而是**"我居然把自己脑子里那套判断逻辑完整拆了出来"**。
很多人一上来就问"学Python还是学Shell""pytest怎么用""playwright怎么装",其实都问错了顺序。自动化脚本真正要过的第一关,是把任务拆到机器能理解的程度。机器特别笨,你脑子里"大概处理一下""差不多就行"的想法,它完全接不住。你必须把流程拆成:什么样的输入 → 做怎样的处理 → 输出什么样的结果 → 出错了怎么办。
打个比方,写脚本就像给一个新来的实习生写一份极其详细的工作说明书。你自己干活可以靠经验临场判断,但实习生只能按你写好的步骤执行。这份说明书写得越清晰,执行就越稳定;写漏一步,结果就可能全错。
那么,什么样的任务适合自动化?我总结了三层筛选标准:
- 频率高:每天、每周要重复做的事。这是自动化的第一价值来源。
- 规则明确:现在就说得清楚"什么情况怎么处理"。规则模糊的任务,脚本会变成一场灾难。
- 出错有代价:手动操作容易漏、容易错,机器跑反而更可靠。比如批量改名、大批量数据校验。
反过来,有三种情况我劝你别急着自动化:一次性的活,脚本写完活都干完了;需求三天两头变的活,改脚本的时间比重做还长;完全没有稳定操作路径的活,每次处理方式都不一样,脚本根本没法固化。
真正成熟的自动化负责人,心里都有一笔账:自动化不是把时间省成零,而是把"必须人在场的时间"压缩成"偶尔瞟一眼的时间"。脚本跑的时候,你可以去开会、去写文档、甚至去睡觉,这就是自动化的魅力所在。所以从这篇文章开始,我建议你把思路从"我要学某个工具"切换成"我要解决某个具体的、重复的、讲得清楚规则的问题"。工具只是手段,问题才是起点。
2. 工具选型:Shell、Python、RPA还是测试框架,各管哪一段
方向对了之后,第二个容易让人纠结的问题就是:到底学哪个工具?网上的声音太杂,有人说"Shell是Linux基本功必须学",有人说"Python天下第一",还有人说"现在都用RPA不用写代码了"。其实这些说法都对,但它们各自解决的问题根本不在一个层面。
我自己这几年的经验是,工具选型不按"哪个流行"来,而是按任务发生的环境层级来分:
- 操作的是系统本身、文件、进程 → 选Shell或者PowerShell。
- 操作的是数据、接口、需要复杂逻辑判断 → 选Python。
- 操作的是多个软件界面、跨系统搬数据 → 选RPA工具。
- 验证软件功能是否正确、回归测试 → 选pytest这类自动化测试框架。
我用张表格把常见的几个选项捋一下,帮你对照着自己手头的任务来选择。
| 工具/方向 | 最擅长的场景 | 上手门槛 | 典型代表 | 不建议的场景 |
|---|---|---|---|---|
| Shell脚本 | 文件批处理、定时任务、系统运维 | 低,命令基础即可 | bash、PowerShell | 复杂数据处理、需要第三方库的活 |
| Python脚本 | 数据处理、接口调用、脚本编排 | 中,需要一点编程基础 | requests、pandas | 大量依赖系统底层命令的活 |
| UI自动化框架 | 浏览器操作、APP操作回归测试 | 中高,需要理解选择器和等待机制 | playwright、appium、maestro | 重数据逻辑的活,纯靠UI点太脆 |
| RPA工具 | 跨软件桌面自动化、模拟人工操作 | 低,可视化编排为主 | 影刀、UIBot | 高并发、大批量任务,效率和稳定不如代码 |
这里举一个我实际遇到的例子,你可以直观感受到选型差异。有一次我接了个需求:"每天早晨把系统里的报表导出,转成PDF,发给业务群。"听了之后我的第一反应是,这活至少有三层:
- 导出报表:系统如果是Web端,点鼠标能导出,那用playwright录一遍点击流程挺合适;如果系统有接口,直接调接口拿数据更快。
- 转PDF:这是文件格式转换,属于数据处理,用Python一行库的调用就搞定。
- 发到群:这是跨应用操作,Python可能需要研究各种协议,或者用RPA模拟人工操作。
最后我把三件事拆开用了三个工具:playwright跑导出、Python做转换、RPA负责发送。每个工具用在自己最顺手的环节,整体才稳定。"一个工具包打天下"的念头趁早放弃。
另外针对自动化测试,多说一句。很多初学者把"写脚本调接口"和"自动化测试框架"混为一谈。实际上,如果你只是调一次接口看看返回,那用requests写个几十行的脚本就够了;但如果你希望长期维护一套用例,跑完自动出报告、断言能提示哪条挂了,那就需要pytest这种框架来帮你管理用例、组织数据、生成报告。第4章我会用实际例子拆解这个差异。
3. Shell脚本实战:从"手动敲命令"到"一条命令搞定"的完整路径
Shell脚本是自动化领域最容易被低估的一环。很多玩Python的人看不起Shell,觉得它"不算编程语言"。但实际上,凡是跟文件、进程、系统状态打交道的自动化,Shell的效率几乎无可替代。它不需要装任何依赖,几乎每一台服务器、每一台Mac、每一套Linux发行版都自带,而且执行逻辑非常直白。
3.1 一个真实案例:日志归档与清理脚本
我们从一个我真实做过的任务开始。场景是这样的:服务器上某个应用每天产生大量日志文件,放在/data/app/logs/目录下,按日期命名。我需要做两件事:
- 保留最近7天的完整日志,把超过7天的日志压缩归档。
- 删除30天以前的归档文件,防止磁盘被写满。
手动做的话,无非就是ls看看文件,tar打压缩包,rm删旧文件。但每天都手动来一遍纯属浪费时间。写成Shell脚本,内容大概是这样的:
#!/bin/bash # 日志归档与清理脚本 LOG_DIR="/data/app/logs" ARCHIVE_DIR="/data/app/logs/archive" KEEP_DAYS=7 DELETE_DAYS=30 # 创建归档目录(如果不存在) mkdir -p "$ARCHIVE_DIR" # 查找超过7天且未被压缩的日志文件,逐个打包 find "$LOG_DIR" -type f -name "*.log" -mtime +$KEEP_DAYS ! -name "*.tar.gz" | while read -r file; do filename=$(basename "$file") tar -czf "$ARCHIVE_DIR/${filename}.tar.gz" -C "$LOG_DIR" "$filename" if [ $? -eq 0 ]; then rm -f "$file" echo "$(date '+%Y-%m-%d %H:%M:%S') 归档并删除: $filename" else echo "$(date '+%Y-%m-%d %H:%M:%S') 归档失败: $filename" >&2 fi done # 删除30天以前的归档文件 find "$ARCHIVE_DIR" -type f -name "*.tar.gz" -mtime +$DELETE_DAYS -delete echo "清理完成"这段脚本看起来不复杂,但里面藏了几个新手最容易踩的坑。第一个坑是路径带空格。如果目录路径里有空格,比如/data/My App/logs,不带引号的写法直接裂开。所以"$LOG_DIR"这种引号一定不能省。第二个坑是**while read逐行读取**,比for file in $(find ...)更安全——后者遇到文件名带空格时会把一个文件名拆成好几个。第三个坑是判断上一条命令是否成功,这里用$?检查tar的返回值,只有压缩成功才删原文件,避免"文件没打成包却被删了"这种惨剧。
3.2 定时执行:crontab让脚本彻底隐身
脚本写好只是第一步,让它按时自动跑才是自动化的精髓。服务器上最常用的就是crontab。执行crontab -e编辑当前用户的任务表,加一行:
30 2 * * * /usr/local/bin/archive_logs.sh >> /var/log/archive_logs.log 2>&1这一行代表:每天凌晨2点30分执行这个脚本,并且把标准输出和错误输出都追加到日志文件里。我觉得"加日志"这个习惯特别重要。裸跑脚本不记日志,等于让一个员工闯了祸还不留记录。脚本执行过程中的echo输出、报错信息,全落到日志文件里,第二天出任何问题都能查。
说到crontab的时间规则,简单记就行:五个星号分别代表"分 时 日 月 周"。30 2 * * *就是每天2:30;0 9 * * 1-5就是工作日早上9点;*/10 * * * *就是每10分钟跑一次。真记不住就用在线生成器,没必要硬背。
3.3 for循环:Shell自动化最常用的结构
热词里有个"shell脚本for循环",这确实是Shell里出现频率最高的结构。它的本质就是"把一批东西挨个处理一遍"。我列举几种最常见的写法:
# 遍历当前目录下所有txt文件 for file in *.txt; do echo "处理文件: $file" done # 遍历数字范围(1到10) for i in {1..10}; do echo "第 $i 轮" done # 遍历命令的输出结果 for ip in $(cat ip_list.txt); do ping -c 1 "$ip" >/dev/null 2>&1 && echo "$ip 通了" || echo "$ip 不通" done这里有个重要的教训:for里遍历文件时,如果目录是空的,*.txt这个通配符会原样当成一个文件名传给循环,导致它处理一个不存在的"字面意义上的*.txt"。应对方法是在循环开头加一句if [ ! -f "$file" ]; then continue; fi。这种边界问题,只有真正被坑过才会记得住。
Shell脚本练到能熟练处理"文件查找 + for循环 + 条件判断 + 定时执行",就已经能覆盖日常一大半的系统自动化需求了。下一步如果发现某个自动化里开始出现复杂的字符串处理、JSON解析、正则替换,那就说明该请Python出场了。
4. Python + pytest:从"能跑的脚本"到"能维护的测试项目"
如果说Shell是自动化的第一级台阶,那Python就是那只"啥都能干"的瑞士军刀。而pytest作为Python生态里最主流的自动化测试框架,值得单独开一章来讲——不是因为它是唯一选择,而是因为它把"写脚本"这件事往"做工程"推了一大步。
4.1 为什么用框架,而不是写一堆"能跑的脚本"
可能有人会问:"我直接用Python写个脚本,循环调用接口、打印结果,不也能自动化测试吗?"能,但那是"能跑"和"能维护"的区别。
裸脚本最大的问题是:所有逻辑全部混在一起,接口地址写死在代码里,断言失败后脚本继续跑,最后在终端输出里翻半天找哪行是错误。当你有5个接口、10个用例时还能忍;当你有50个接口、300个用例时,这种搞法就是灾难。
pytest替我解决了几个核心痛点:
- 用例组织:一个文件放一类测试,一个函数就是一个用例,不用自己写复杂的调度逻辑。
- 断言清晰:用
assert写判断,失败时自动展示期望值和实际值,一眼定位问题。 - 夹具机制(fixture):把"前置准备"和"环境清理"抽出来复用,不用每个用例重复写。
- 报告生成:配合pytest-html插件或者Allure,跑完直接出可视化报告,同事看得懂,领导看得懂。
- 选择执行:用
-k按用例名筛选、用-m按标记分组,我可以在1000个用例里只跑冒烟测试。
4.2 一个最小可用的接口自动化测试项目
举个最典型的场景:测试一个用户查询接口。接口地址是https://api.example.com/user/{id},返回JSON,格式是{"code":0,"data":{"name":"张三","age":18}}。正常逻辑是:id存在时,code为0且data里有数据;id不存在时,code为1001且message给出提示;id非法时,返回400。
用pytest实现,项目结构可以这样安排:
test_project/ ├── requirements.txt ├── conftest.py # 放共享fixture ├── test_user_api.py # 用户接口测试用例 └── config.py # 放环境配置config.py里放基础信息:
BASE_URL = "https://api.example.com" TIMEOUT = 10conftest.py里定义一个会话级的session,发请求时可以复用连接,省得每个用例都重新握手:
import pytest import requests @pytest.fixture(scope="session") def session(): s = requests.Session() yield s s.close()test_user_api.py里写具体的用例:
import pytest def test_get_user_success(session, base_url): """正常查询:返回用户信息""" resp = session.get(f"{base_url}/user/1001", timeout=10) assert resp.status_code == 200 body = resp.json() assert body["code"] == 0 assert body["data"]["name"] != "" def test_get_user_not_found(session, base_url): """查询不存在的用户:返回业务错误码""" resp = session.get(f"{base_url}/user/999999", timeout=10) body = resp.json() assert resp.status_code == 200 assert body["code"] == 1001 @pytest.mark.parametrize("user_id", ["abc", "-1", "0", "1.5"]) def test_get_user_invalid_id(session, base_url, user_id): """非法id:参数校验层应拦截""" resp = session.get(f"{base_url}/user/{user_id}", timeout=10) assert resp.status_code == 400注意最后一个用例用了@pytest.mark.parametrize,这是pytest非常好用的一个功能——同一个用例逻辑,喂四组不同的参数,就变成四个用例。这在接口自动化里应用极广,尤其是输入边界值、异常值、类型错误值这些场景,用参数化写起来又干净又全面。
4.3 跑通之后,如何让测试结果真正被人依赖
跑通用例只是第一步。我见过很多团队做了自动化测试,但没人看结果,最后沦为"死项目"。要让测试结果真正产生价值,至少要接上两件事:
- 持续集成:在代码流水线里加一步,提交代码后自动跑pytest。跑挂了就拦住发布,用例才开始有牙齿。
- 报告可视化:
pytest --html=report.html就能生成一份不错的HTML报告。在公司环境里,可以再搭个简单的服务或者用已有的CI平台展示报告页面。
这里分享一个我踩过的坑:别上来就追求100%覆盖率和全量用例稳定运行。UI自动化和接口自动化里,总有几个用例因为环境波动、第三方依赖不稳定而时不时挂一下。你要做的是先挑出核心链路、最稳定的20条用例,让它们保证100%通过且每次都跑,然后再慢慢往里加。让团队成员对自动化结果建立"它挂就是真有bug"的信任,比用例数量重要得多。做成"十次挂五次"的测试,最后大家只会选择不看结果,项目就废了。
5. UI自动化:playwright覆盖浏览器,RPA接管跨软件桌面流程
聊到UI自动化,很多人的第一反应是Selenium。但近两年playwright这个后起之秀,在浏览器自动化领域的体验比Selenium好太多。安装简单、API设计合理、自带等待机制、还能录脚本回放,非常适合快速上手。与此同时,RPA工具(比如影刀)则在"跨软件桌面自动化"这个方向上不可替代。这一章我把这两条路线放在一起讲,帮你分清你该用哪条。
5.1 playwright:用"录制回放"打开UI自动化的大门
playwright最大的优点之一,是它自带一个录制器。启动命令:
playwright codegen https://example.com它会打开一个浏览器窗口,你在里面正常操作页面,代码面板会同步实时生成对应的Python代码。操作完之后,把代码复制下来改吧改吧,第一个UI自动化脚本就成了。
比如说,我要实现"打开登录页、输入账号密码、点击登录、检查是否跳转到首页"这个过程。用playwright录制得到代码后,再加上断言,大概长这样:
from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(headless=False) page = browser.new_page() page.goto("https://example.com/login") page.fill("#username", "test_user") page.fill("#password", "123456") page.click("button[type=submit]") # 等待跳转并断言 page.wait_for_url("**/home") assert page.title() == "首页" browser.close()这里重点说一下等待机制。Selenium时代最烦的就是"元素还没加载好就点了"这类问题,你需要手写各种time.sleep和显式等待。而playwright的很多操作自带自动等待,比如click会等元素先出现、可点击、稳定后再执行。这极大提升了脚本的稳定性,但注意也不是万能保险,涉及复杂异步加载的页面,还是建议有意识地用expect来等待关键状态出现。
5.2 UI自动化的稳定性问题:设置正确看待期望
做UI自动化最难的不是写脚本,是维护稳定性。选择器随前端改版就失效、页面弹窗突然遮挡按钮、网络慢导致超时……这些问题没经历过的人不会懂。我从失败中总结出几条实践建议:
- 给元素加稳定的定位锚点:优先用
>python --version如果这个也报"无法识别",说明Python都没装好,直接去官网重新装。如果
python --version正常,进入下一步。第二步,找到Python的安装路径。在PowerShell里执行:
where.exe python它会输出Python可执行文件的完整路径,比如
C:\Users\你的用户名\AppData\Local\Programs\Python\Python311\python.exe。第三步,推断pip所在目录。pip通常就在Python安装目录下的
Scripts子目录里。所以在上面的例子中,pip应该在这个位置:C:\Users\你的用户名\AppData\Local\Programs\Python\Python311\Scripts\pip.exe试着直接运行这个完整路径,看看能不能出版本号。能出的话,问题就锁定在环境变量PATH里没有加
Scripts目录。第四步,把
Scripts目录加进PATH。在Windows里,打开"系统设置"→"关于"→"高级系统设置"→"环境变量",编辑Path变量,把Scripts目录路径追加进去。加完之后,务必新开一个PowerShell窗口再试——老窗口不会自动加载新改的环境变量。这几步走下来,问题基本解决。之所以这么说,是因为这种"能运行但找不到命令"的错误,九成都是PATH的问题。
再来看另一个很常见的问题:Windows脚本文件双击后一闪而过。你写了一个
.bat或.ps1脚本,双击执行,窗口闪了一下就没了,里面内容看到没看到。其实脚本大概率是执行了,然后因为控制台窗口的默认行为是"执行完毕自动关闭",有没有报错你根本来不及看。解决方式有两个。调试时,在PowerShell里直接运行脚本而不是双击:
.\myscript.ps1这样错误信息就会留在当前窗口里。或者,在.bat脚本的最后加一行
pause,窗口会停在"请按任意键继续..."。这个pause在调试期几乎相当于"脚本调试的安全气囊",我自己的脚本在开发阶段,十有八九都会加它来观察输出。从更大一点的视角看,环境配置类的坑几乎都有一个共同特征:报错信息里藏着答案,但大多数人不读原文。
pip报错说是"无法识别",不是"找不到模块",这两个问题方向完全不同。我建议新手养成一个习惯:把整段报错原文复制下来,去搜索引擎里搜,而不是只描述"我的Python好像出问题了"。原样搜索报错,是排查效率最高的动作,没有之一。7. 几个从踩坑中总结的脚本开发习惯,现在分享给你
文章写到这,"自动化与脚本"从理念到选型、从Shell到Python、从浏览器到RPA、从环境到排错,基本说全了。最后这部分不按理论来,纯粹分享几个我打磨了两三年才养成的习惯,每一个都是拿加班和线上事故换的。
第一个习惯:先手动跑通路径,再写自动化脚本。不管是Shell、pytest还是playwright,第一步永远别是打开编辑器写代码。先去命令行手动执行一遍流程,把每一步涉及的命令、参数、输出、可能的异常都记录下来。脚本是手动流程的固化,不是凭空想象出来的。我见过太多人上来就写代码,结果写出来的脚本每一步都在猜,跑起来全是错。手动路径跑通了,写脚本只是"翻译"而已,难度骤降。
第二个习惯:脚本必须有日志,并且日志里要有时间戳。我前面说过很多次,这里再强调一次。没有日志的自动化脚本,出问题的时候你就是盲人摸象。最简单的做法,脚本全局给所有关键动作加上
echo "$(date '+%Y-%m-%d %H:%M:%S') 做了什么",或者Python里用logging模块统一管理。你永远不知道明天会不会需要查这个脚本为什么半夜三点跑挂了。第三个习惯:留好"最后一道保险"。执行删除类、覆盖类、清理类操作的脚本,要特别小心。我的做法通常是:脚本默认进入"演练模式"(dry-run),只打印将要做什么但不真做;确认无误后用
--execute参数才真正执行。比如Shell里用find ... -delete之前,先跑一遍不带-delete的版本看看列出来的是不是真想删的文件。花了十分钟做验证,可能帮你省下恢复数据好几天的时间。第四个习惯:拥抱"少量多次"的迭代节奏,不要贪心。刚学自动化的人容易犯一个毛病:憋大招,想一次性把整个流程全自动化。我的建议正好相反——先把流程里最小的一步自动化跑通,哪怕只是"自动给文件改名";跑通了再串下一步。每增加一小截,都验证一下没破坏前面已经通的部分。自动化项目死在"憋大招"上的,比死在技术难度上的多得多。
既然技术的路已经铺好,剩下的就是选一个你手头最烦的重复性任务,照着这篇的思路去拆,去固化,去让它自己跑起来。第一次成功的那个深夜,你会觉着以前熬过的那些夜,挺值的。