最近我在后台看到好多朋友提问,问题翻来覆去基本就是两类:一类是“接口自动化怎么写”、“Selenium 和 Playwright 到底选哪个”、“Appium 怎么连真机”;另一类是“Python 爬虫为什么跑完 Process finished with exit code 0 却什么都没抓到”、“JD 风控怎么对抗”、“网页滑块拼图能不能自动化”。很多人把自动化测试和爬虫当成两个没有交集的方向,招人 JD 也是分开写的。但真做下来你会发现,这两件事背后的技术栈重叠得很厉害,本质都是同一件事:用代码替代人工去稳定地操作数字产品。
这篇文章就围绕“自动化”和“爬虫”这两个关键词,把章节级的知识点拆成一套能直接落地的实操体系。如果你是个刚开始接触爬虫的 Python 新手、正在搭自动化测试框架的工程师,或者想把手头重复劳动交给代码的“非典型开发者”,这篇文章应该能帮你省掉至少一个月的瞎摸索时间。
1. 先看懂全貌:自动化与爬虫为什么是同一棵树上的两个分支
1.1 自动化的底层逻辑:把操作拆成可重复执行的指令
我在带新人时经常说一句话:别被“自动化”三个字吓住,你用过 Excel 的宏、写过自动回复邮件规则,就已经在写自动化了。真正的自动化系统无非三件事:一是把手工操作拆成确定的步骤;二是用代码把步骤固定下来;三是安排一个调度者定时或按事件触发它。放到自动化测试里,调度者是 pytest、Jenkins 这类工具;放到业务流程里,就是 cron 或 Windows 任务计划。
有个经典的误区是以为自动化等于“录制回放”。录制回放当然能跑通 demo,但真实场景里页面结构一变、网络一卡、元素出现慢个半秒,脚本就崩了。所以《第14章-自动化与爬虫》这类内容真正要训练的,不是录制脚本的手速,而是把“步骤”抽象成“数据流”的能力:输入、操作、等待条件、输出、异常兜底,每一环你都要能控制。UI 自动化、接口自动化、RPA 都逃不出这个框架。
1.2 爬虫是发生在“围墙外”的自动化
爬虫和自动化测试最本质的区别,在于你对目标系统有没有控制权。自动化测试的对象是你自己团队的网站和 App,你可以让开发开测试开关、改数据库、造测试账号;爬虫的目标通常不是自己的系统。你不能让对方给你开免验证码入口,也不能让前端开发帮你加一个便于抓取的字段。这种“无控制权”环境决定了爬虫必须多考虑三件事:伪装(请求头、行为频率)、解析(页面结构、异步接口)、合规(robots 协议、数据授权)。
我用一个类比帮你理解:自动化测试像是在自己家里打扫卫生,你想怎么搬家具都行;爬虫是去邻居家参观,你得按人家的规矩来,人家关门你只能等,人家装了监控你就得掂量一下该不该继续。所以爬虫工程化做的很多事,本质是在“不干扰对方系统运行”和“拿回自己需要的数据”之间找平衡。
1.3 为什么建议新手从这两个方向切入编程
没有编程基础的人常问“我能学会吗”。我的答案是可以,而且自动化和爬虫是编程里反馈最快的学习路径之一。你写一个 requests 请求,几十秒内就能看到抓回来的数据,这种即时正反馈是背语法书给不了的。更重要的是,这两个方向自带工程场景:定时任务、日志、异常处理、部署、监控,每一个模块拆开都是能写进简历的技能点。不少从我这里起步的朋友,第一份工作就是“Python 爬虫工程师”或者“测试开发工程师”,跨度没有想象中那么大。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从零搭一套能长期跑的爬虫:基础链路到工程化
2.1 基础链路:requests 拿数据只是第一步
很多 Python 爬虫教程都会从 requests 讲起,但“能拿到数据”和“稳定拿到数据”是两回事。一个标准的请求过程要处理四个细节:请求头伪装、超时与重试、响应编码、数据校验。请求头里最重要的不是 User-Agent,而是 Referer 和 Cookie 的完整性。有些服务端会校验请求是从哪个页面跳过来的,Referer 不对直接返回 403,这类问题用浏览器开发者工具复制完整的请求头就能解决。
我见过最典型的新手错误是只写 requests.get(url) 然后把 timeout 参数省了。网络抖动一次,脚本就卡死在等待响应上,几小时后你收到一堆连接超时的告警。所以哪怕最简脚本,我建议也保持这个结构:
python复制import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
session = requests.Session()
retry = Retry(total=3, backoff_factor=1, status_forcelist=[500, 502, 503])
adapter = HTTPAdapter(max_retries=retry)
session.mount("http://", adapter)
session.mount("https://", adapter)
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"Referer": "https://example.com/list",
}
resp = session.get("https://example.com/api/data", headers=headers, timeout=10)
resp.raise_for_status()
data = resp.json()
请求拿到响应后,下一步是解析。requests 本身只负责取回 HTML,解析得交给 BeautifulSoup、lxml 或者直接找 JSON 接口。这里要提醒一句:很多页面上显示的数据并不是写在 HTML 里,而是通过异步请求加载的。你要学会打开浏览器开发者工具的 Network 面板,筛选 XHR/Fetch 请求,往往能看到后端直接返回的 JSON。直接抓接口比抓 HTML 再清洗要稳定得多。我在实际项目里,大概有七成场景是直接请求 JSON 接口,两成需要额外带 token,只有一成需要真的去解析动态渲染后的 DOM。
2.2 当页面是 JS 渲染的:用 Playwright 做浏览器自动化
上一节讲的是第一层方案,但有些页面前端用 Vue/React 渲染,所有内容都靠 JS 动态填充,直接请求 HTML 拿到的只是一个空壳。这种情况下两条路线:一条是继续深挖它背后的接口,另一条就是用浏览器自动化工具直接渲染页面再取值。接口路线更节省资源,但遇到接口参数加密很重的时候,浏览器自动化反而更省事,这也是 Selenium 和 Playwright 这类工具在爬虫圈被大量使用的原因。
新版项目里我更推荐 Playwright 而不是 Selenium。理由不只是因为它会自动下载浏览器驱动、省去手动配 chromedriver 的麻烦,更关键的是它的自动等待机制很聪明。Selenium 里经典问题是 element not interactable 和 ElementClickInterceptedException,本质是页面没加载完就去操作了;Playwright 的操作 API 默认会等待元素可交互,稳定性提升非常明显。下面是一个最简示例,打开页面后等待某个选择器出现再抓取内容:
python复制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/items", wait_until="networkidle")
page.wait_for_selector(".item-card")
items = page.locator(".item-card").all_inner_texts()
for i in items[:5]:
print(i)
browser.close()
这段代码里的 wait_until="networkidle" 是很多场景下的安全选项,代表“网络空闲了再继续”。不过网络空闲的定义有时候很严格,页面一直有轮询请求就会卡住,所以实际调试时我更多会用 wait_until="domcontentloaded" 配合显式的 page.wait_for_selector。这两招组合起来,已经能覆盖大多数前端渲染页面的场景。
2.3 从单机脚本到分布式爬虫:先想清楚“有没有必要”
聊到“分布式爬虫”,好些初学者眼睛就亮了,觉得听起来很高级。但我给你泼一盆冷水:百分之八十的爬虫项目根本不需要分布式。单机 + 异步 + 定时任务已经能处理每天几十万条数据的小规模场景。分布式是当你遇到三种瓶颈才考虑的方向:一是单机带宽或 IP 被限导致抓取速度上不去;二是数据量太大,单机存储和处理跟不上;三是对方风控已经能做到按 IP 维度的精准限频,你需要多节点轮换来降低单节点压力。
分布式爬虫的主流架构并不神秘:多个爬虫节点从任务队列里领取 URL,抓取结果写入统一的消息队列或存储。消息队列常用的有 Redis、RabbitMQ,任务里去重用 Redis 的 set 结构最顺手。框架层如果是基于 Scrapy,可以配合 scrapy-redis 组件实现请求队列共享。但我想强调,架构不等于组件堆叠,你首先要定义清楚“谁来分配任务、谁来去重、谁来存储”。把这几个角色想清楚,用什么中间件只是选择问题。
2.4 爬虫的合规边界:再提醒也要单独写出来
这部分内容我不会跳过,因为它在实战中比技术更会影响你的项目走向。做爬虫一定要区分三类对象:第一类是开放接口或允许爬取的网站,比如很多政府公开数据、开发者文档站,robots.txt 里明确允许;第二类是虽有登录但你有合法授权的平台,比如你自己公司的后台、第三方合作方提供的接口;第三类是用户数据的采集和商业竞对数据的抓取,这往往涉及个人信息保护法和平台用户协议,风险很高。
我的实操建议是:学习阶段尽量去抓公开数据集、自己搭的测试站、或者明确允许爬虫的站点。真实项目里如果要抓某个平台的数据,先看对方的使用条款和 robots.txt,再评估抓取量级是否会影响对方服务。很多人问“xx平台风控怎么绕过”,这种问题背后大概率是想对付知名商业站点,我只能说,真正靠谱的做法是在授权范围内做技术验证,而不是把生产环境保护当作敌人。用自己搭的站点去研究反爬机制,效果其实一样。
3. 自动化测试落地:UI、接口、App 到底怎么选型
3.1 Web 自动化:Selenium 还是 Playwright,先搞懂一致性成本
Web 自动化如今最大的争论就是 Selenium 和 Playwright 谁更好。Selenium 的优点是生态老、教程多、兼容所有浏览器,跑久了依然稳定;Playwright 的优点是安装友好、自动等待、自带 Trace 和 Inspector,调试体验吊打 Selenium。我的态度很务实:老项目维护用 Selenium 别动它,新项目直接上 Playwright。理由很简单:Playwright 已经把 Web 自动化的稳定性问题解决了一大半。
不管是哪个框架,最常遇到的现实问题是浏览器驱动版本不匹配。Selenium 时代你得先去官网确认当前 Chrome 版本对应的 chromedriver 大号是否一致。比如你的 Chrome 是 122.x,那么 chromedriver 也应该选 122.x,否则大概率报 session not created 或 unknown error: cannot find Chrome binary。这一轮排查对新手很不友好,而 Playwright 使用自带的浏览器实例,driver 是自动安装的,所以能避开这个坑。如果你必须在团队的 Selenium 框架里排查驱动问题,先执行 chrome://version 查看浏览器版本,再到镜像站点下载一致的主版本驱动,替换系统 PATH 里的旧文件,这一步就解决了八成的问题。
下面我把选型过程中最核心的差异列个表,方便你直接对照决策:
| 对比项 | Selenium | Playwright |
|---|---|---|
| 驱动管理 | 手动下载,需匹配浏览器版本 | 自动下载对应浏览器 |
| 等待策略 | 显式等待为主,隐式等待易冲突 | 操作前自动等待元素可交互 |
| 调试工具 | 需要额外安装第三方录制脚本 | 自带 Inspector、Trace 回放 |
| 多标签/多上下文 | 操作复杂,需要窗口切换 | 原生支持 browser context 隔离 |
| 学习资料 | 多,老项目存量多 | 增长快,后端技术栈友好 |
3.2 UI 自动化的真正痛点不是“写”,而是“稳”
我辅导过不少从零开始做 Web UI 自动化的朋友,他们最常遇到的问题不是不会写定位器,而是脚本今天跑通明天挂。稳定性问题通常来自三个源头:定位方式不够健壮、等待不充分、操作与页面状态不同步。定位时尽量少用绝对路径和动态 id,多用数据属性或者文本就近定位;等待上一律使用条件型等待,不要用 time.sleep(2),因为固定等待在 CI 环境变慢时会误报,在本地变快时会浪费时间。
国内网站还有一个高发情况是触发了滑块验证码。很多人问“Selenium 网页拼图验证怎么自动化”,从技术上讲,滑块拼图可以拆成三步:找到缺口位置、计算拖动距离、模拟人工轨迹。但在真实项目里我强烈不建议你去硬刚生产环境的验证码,这不只是技术难度问题,更是合规问题。正规的自动化测试应该让开发在测试环境提供验证码开关,或使用公司内部的打码平台。研究滑块识别的技术原理可以当成学习课题,用在自己的测试站点上验证,但不要拿它去对抗别人的生产系统,否则容易惹上麻烦。
3.3 接口自动化:性价比最高的自动化测试投入
如果只能推荐一个自动化测试方向,我会推荐接口自动化。它没有 UI 自动化那些脆弱的元素定位问题,执行速度快,反馈也精准,而且天然适合集成到 CI/CD 流水线里。Java 项目里有 RestAssured、TestNG,Python 项目里最主流的组合就是 pytest + requests。一个接口自动化的用例结构包括:准备请求数据和认证信息、发起请求、对状态码和响应体做断言、最后清理测试数据。
举个简单例子,测试一个登录接口拿到 token:
python复制import requests
BASE_URL = "http://your-test-server.com"
def test_login_success():
payload = {"username": "tester01", "password": "123456"}
resp = requests.post(f"{BASE_URL}/api/login", json=payload, timeout=10)
assert resp.status_code == 200
data = resp.json()
assert data["code"] == 0
assert "token" in data["data"]
接口自动化框架再往后延伸,就是把用例做参数化,把环境地址抽到配置里,以及在 conftest.py 里实现登录 token 的 session 复用。到这里你会发现,它离爬虫的 requests 封装越来越像了,两边用的技术几乎是同一套。Jenkins 自动化部署在这里的作用,是让用例在每次代码提交后自动跑一遍,再把报告发到群里。你不需要一开始就很懂 Jenkins,只要会建一个 freestyle 任务,执行一段 pytest 命令,就已经完成了一个最基础的接口自动化流水线。
3.4 App 端及其他客户端:移动自动化与 Windows 自动化的取舍
App 自动化的代表工具是 Appium,它用的思路和 Selenium 很像,只是定位目标从浏览器元素换成了原生控件。Android 和 iOS 都要连真机或模拟器,通过 Appium Server 转发命令到对应的 driver。实操中你会遇到不少环境问题:adb 设备连不上、Appium Inspector 页面空白、iOS 需要 Xcode 环境支持等。我建议新手先只做 Android 端,等流程跑通了再扩展 iOS,Windows 桌面应用自动化可以尝试 WinAppDriver,但它的生态相对小众,不要排在优先位置。
这里想提醒一个“取舍”问题:不是所有流程都适合自动化。比如验证码短信登录、需要插入硬件的操作、OCR 识别结果不稳定的流程,自动化成本会很高。先把那些稳定、重复、高频的流程自动化,把员工从“每天点 100 次导出按钮”里解脱出来,这比盲目追求“全流程自动化”更靠谱。
4. 反爬与风控机制:从服务端视角看自动化与爬虫
4.1 服务端怎么识别你不是人:从 UA 到行为指纹
我们前面一直在讲怎么让爬虫更像人,但如果切换视角到服务端,你会发现反爬机制早就不是“看 User-Agent 是不是浏览器”这么简单了。第一代反爬是看 UA、Referer、访问频率,接着是看 IP 的分布、Cookie 的完整性,再往后就是行为分析:鼠标轨迹、键盘事件、页面停留时间、点击顺序、滚动速度,这些信息会组成一个多维度的“行为指纹”。这也解释了为什么早期 Selenium 直接设个 navigator.webdriver 就能被识别——检测方根本不需要看流量特征,只要检查浏览器环境里的自动化标记。
很多反爬手段会放在 WAF 或网关层,做实时概率评分。比如同一个 IP 在单位时间内请求次数超过阈值,返回一个验证码页面;或者根据 TLS 指纹判断请求库类型;甚至通过字体反爬把页面里的数字显示成加密字形,让你拿到 HTML 也解析不出真实价格。我见过不少中小型站点技术人员误以为“只要隐藏接口地址就万事大吉”,实际上一旦接口拿到前端源码,地址就不是秘密。真正的防线是服务端鉴权、参数校验、频率限制和风险评分。
4.2 滑块拼图验证码与行为检测:不只是找到缺口
开头提到“Selenium 网页拼图验证怎么自动化”,这里展开讲讲它的运作原理。滑块验证码通常被设计成两层防护:第一层要求你找到拼图缺口的坐标,并把滑块拖到对应位置;第二层是检测拖拽过程是否符合人类行为。第一层可以用 OpenCV 做模板匹配定位缺口,第二层则需要模拟出带加速度变化的拖动轨迹,不能是匀速直线移动,因为真人拖动滑块时会先快后慢、带轻微抖动。
所以你会看到网上的“过滑块方案”往往包含三部分:目标检测、轨迹模拟、环境隐藏。从研究角度,这三部分都很有意思,尤其是轨迹模拟涉及到把人类的操作建模成时序数据。但从站长的角度看,检测方也不会一直停留在缺口图上,很多平台早就升级成点选汉字、文字顺序辨识、无感验证等更复杂的交互。对抗永远是个无底洞,最佳策略仍是合规优先。
4.3 站长视角:用 Apache 屏蔽垃圾爬虫是一种防御习惯
如果你维护过自己的网站,可能遇到过服务器日志里频繁出现陌生 UA、疯狂 404 请求的情况。这未必是黑客攻击,很多时候是搜索引擎爬虫、AI 训练爬虫和其他采集程序在扫你的站。Apache 可以通过配置屏蔽特定 UA,也可以把这些请求返回 403,甚至转给一个假页面去消耗对方资源。
一个常见的 Apache 配置思路是用 mod_rewrite 拦截 UA:
apache复制RewriteEngine On
RewriteCond %{HTTP_USER_AGENT} (python-requests|scrapy|curl|MauiBot) [NC]
RewriteRule .* - [F,L]
这里的 [F] 会强制返回 403 Forbidden,[NC] 表示不区分大小写。但请注意,单纯拦截 UA 是最基础的防御,真正的垃圾爬虫可以伪造任意 UA。如果你要更健壮的防护,建议把限流做在应用层:统计同一个 IP 每小时的请求数,超过阈值就弹验证码或者延迟响应。这个思路反过来也适用于爬虫工程师:如果你发现目标网站这么做了,说明对方已经注意你了,最理性的选择是降低频率或换个时机再抓。别硬来,硬拼只会把自己的 IP 段拉黑。
5. 高频实战问题排查:爬虫与自动化的“拦路虎”合集
5.1 爬虫程序只显示 Process finished with exit code 0 却没有输出
这是很多人在 PyCharm 里跑爬虫时遇到的第一道坎:运行后控制台干干净净,只出现 Process finished with exit code 0,实际什么都没打印。退出码 0 表示程序本身正常结束,没有抛出异常,所以问题大概率出在“代码根本没有做你想让它做的事”。最常见的三种原因:文件里只定义了函数却没有调用;抓取逻辑在 if 分支里但没有进入;print 的输出因为缓存没有被刷新出来。前两种是好排查的,先确认 if __name__ == "__main__": 入口存在,并且函数确实被调用。第三种可以尝试在 print 后加 flush=True,或者运行前在 PyCharm 里把输出编码改成 UTF-8。如果以上都检查了还没输出,就一段一段打断点或加临时日志,别指望一上来就套框架调试。
5.2 Selenium 提示 Chrome failed to start 或 session not created
这种问题绝大多数是浏览器驱动版本不匹配。打开 Chrome 地址栏输入 chrome://version,查看“Google Chrome”的版本号,比如 121.0.6167.185。然后去下载对应大版本的 ChromeDriver,大版本号首位一致即可,比如 121.x 的 Chrome 可以配 121.0.6167.xx 的 chromedriver,不必精确到小版本。下载完成后把 chromedriver 可执行文件放到 PATH 目录里,或者直接在代码中指定 service=Service(executable_path="/path/to/chromedriver")。另外一个常见原因是你代码里配置了 headless 模式,但服务器上缺了运行 Chrome 所需的系统库,报错里通常会出现 missing X server 或 libXss.so.1 之类的字样,这时候需要安装对应的依赖库,和驱动版本就没关系了。
如果你已经决定换 Playwright,这类问题会大幅减少,因为驱动和浏览器由它自己维护。但团队原有的 Selenium 用例要迁移也别说搬就搬,建议先跑一个回归对比,把风险控制住再做替换。
5.3 Mac/Linux 上 cron 定时爬虫不执行
很多人在终端手动运行爬虫正常,但放到 crontab 里就怎么都不跑。除了脚本路径没写绝对路径之外,最容易被忽略的是环境变量。cron 运行时的 PATH 非常简略,可能找不到你的 Python 解释器,所以建议在 crontab 里直接写 Python 解释器全路径,不要只写 python xxx.py。另外脚本里涉及相对路径的文件读取,在 cron 下会找不到文件,因为工作目录变了,最好的做法是在脚本开头 os.chdir(os.path.dirname(os.path.abspath(__file__))) 切换到脚本所在目录。最后看 cron 日志确认有没有执行记录,大多数发行版日志在 /var/log/cron 或 /var/log/syslog 里,别靠猜。
5.4 Jenkins 执行 pytest 时找不到 allure 命令
使用 Jenkins 跑接口自动化时,如果脚本里本地已经装了 Allure,命令行执行没问题,Jenkins 里却提示 allure: command not found,原因同样是 Jenkins 执行环境 PATH 不完整。解决方式有两种:一种在 Jenkins 的“系统管理-全局工具配置”里添加 Allure 安装路径;另一种实在嫌麻烦,就不在 Jenkins 侧要求 allure 命令,而是改用 pytest --alluredir=./allure-results 生成原始结果,再用 Jenkins Allure 插件读取该目录生成报告。这样脚本侧不依赖 allure 命令,只负责产出结果文件,Java 环境和 Python 环境甚至可以混着用。
为了让你快速定位,我把上面几类问题汇总成一张速查表:
| 现象 | 可能原因 | 排查顺序 |
|---|---|---|
| 爬虫 exit code 0 无输出 | 没有调用入口 / print 未刷新 | 先查函数是否被调用,再加 flush |
| Chrome 启动失败或 session 报错 | 驱动版本不匹配、系统库缺失 | 查 chrome://version 对比驱动 |
| cron 脚本不执行 | PATH 不完整、路径不是绝对路径 | 写全解释器路径 + 查看 cron 日志 |
| Jenkins 找不到 allure | 执行环境 PATH 不包含本地工具 | 用全局工具配置或改用报告目录 |
5.5 请求被抓到了:先从这三个角度自查
最后分享一个排查“为什么爬虫被抓”的通用方法。如果对方返回了验证码、封了 IP,或者页面内容变成了异常文本,先别急着上代理分布式,按顺序看三个点:第一,请求头是否一致且完整,尤其是 User-Agent、Accept-Language、Referer,缺少或矛盾都会有风险;第二,访问频率是否均匀,集中在一分钟内发几十个请求,任何人都会觉得不对劲,建议加入随机延时;第三,Cookie 和 JS 生成的 token 是否处理了。很多网站会在首次访问时下发一段 JS 脚本,执行后生成一个新的 cookie,后续接口校验这个 cookie。你没执行 JS,相当于每次请求都没有“身份”,自然会被拦。在自动化测试里遇到这种问题,先看前端代码逻辑,手动在浏览器里复现一遍,再决定用 Playwright 渲染还是用纯 requests 模拟算法。
如果这些都已经排查到位,仍然频繁被限制,那大概率不是技术问题,而是数据量级和平台风控阈值不匹配。这时候合理的处理方式是降低目标量级,或者寻求官方接口和数据授权,而不是继续加代理池硬怼。
最后聊聊我的体会
做自动化和爬虫这几年,一个很深的感悟是:工具更新换代非常快,今天你用 Selenium,明天出现 Playwright,后天大模型又催生出 AI 自动化测试平台,但底层的思路几乎没变过——你得有能力把人工操作拆解成可验证、可恢复、可重放的步骤。学会 requests,爬虫就通了;学会 pytest,接口自动化就通了;学会 Jenkins,部署流水线也就通了,这些技能环环相扣,越往后越觉得当初没白折腾。如果你现在还在“学着学着就放弃”的阶段,我给一个很土但有效的建议:找一个你确实想用的公开数据源,比如天气接口或开源社区的数据,把它完整抓下来存到数据库里,再写个定时脚本每天更新一次。这个小小的闭环跑通之后,你对自动化和爬虫的信心会完全不同。
