
1. 项目概述从手动填验证码到自动化代理的跃迁如果你做过任何需要批量注册账号、薅羊毛或者自动化测试的工作那你一定对手机验证码这个环节深恶痛绝。手动操作不仅效率低下遇到图形验证码、滑块验证更是让人头大。这个项目就是为解决这个痛点而生——一个能够自主完成从接收短信到填写验证码全流程的自动化代理。简单来说我构建了一个“手机验证码机器人”。它的核心逻辑是模拟一个真实的用户操作环境自动监听短信智能解析其中的验证码并自动填写到目标网页或应用的表单中完成验证流程。整个过程无需人工干预7x24小时待命。这不仅仅是写几行代码调用个API那么简单它涉及到浏览器自动化、短信网关集成、验证码识别策略、反反爬虫机制以及一套健壮的错误处理逻辑。无论是用于开发测试环境的数据构造还是某些合规的自动化流程请务必确保你的使用场景符合相关服务条款和法律法规这个项目都能将你从重复劳动中解放出来。2. 核心架构与工具选型解析2.1 为什么选择“代理”架构而非单一脚本在项目初期我考虑过写一个简单的Python脚本用requests库发请求对接一个短信接收平台API。但很快发现这条路走不通。现代Web应用尤其是那些对安全有要求的注册/登录页面大量使用了JavaScript动态加载、加密参数以及复杂的人机验证如Cloudflare Turnstile, reCAPTCHA。纯HTTP请求脚本难以应对这些情况。因此我选择了基于真实浏览器的“代理”架构。这里的“代理”并非网络代理而是一个自动化代理程序它驱动一个真实的浏览器实例如Chrome来模拟人类用户的所有操作打开网页、点击、输入、等待、读取短信、填写验证码。这样做最大的好处是高兼容性和低被侦测风险。浏览器环境能天然执行JavaScript加载所有资源使得自动化脚本能够与绝大多数现代Web前端无缝交互。2.2 技术栈深度拆解一个稳定的自动化代理需要多个组件协同工作。以下是我经过多次迭代后确定的核心技术栈及其选型理由1. 浏览器自动化框架Playwright我放弃了更早的Selenium选择了微软开源的Playwright。原因有三点首先Playwright支持Chromium、Firefox和WebKit三大浏览器引擎且API设计更现代、简洁。其次它的自动等待机制更智能减少了编写大量time.sleep的需要。最重要的是Playwright能够更有效地模拟真实设备指纹如WebGL、硬件并发数、屏幕分辨率等并提供了拦截网络请求、修改User-Agent等高级功能这对于绕过一些基础的反爬检测至关重要。2. 短信接收服务集成这是项目的关键依赖。你需要一个能够提供临时手机号并接收短信的服务。市场上有多种选择在线短信接收网站如receive-sms-online.info等。优点是免费、无需注册适合轻量、低频测试。缺点是不稳定、号码可能被重复使用、无法保证号码可用性且难以通过API集成。专业短信接码平台API如TextVerified、SMSPool等。这是生产级选择。它们提供稳定的号码池、清晰的API接口、按条或按时间收费。我最终选择了这类服务因为我们需要可靠的、可编程的短信接收能力。集成时你需要处理获取号码、监听短信、释放号码等生命周期。3. 验证码解析引擎收到短信后如何从中准确提取出6位或4位数字验证码这比听起来要复杂。短信内容可能是“您的验证码是 123456”也可能是“【XX平台】登录验证码1234565分钟内有效”还可能包含推广信息。我设计了一个多策略的解析器正则表达式匹配基础策略匹配连续的数字串并优先提取长度为4或6的。关键词定位查找“验证码”、“code”、“verification”等关键词周围的数字。上下文分析如果短信有固定模板可以编写针对性的解析规则。备用方案对于无法解析的情况记录原始短信内容并触发人工处理或重试告警。4. 状态管理与错误处理代理需要长时间运行必须健壮。我引入了状态机来管理每个验证任务的生命周期等待号码-获取号码-打开目标页-输入号码请求验证码-监听短信-解析并填写-完成/失败。每个状态转换都有超时控制和错误处理。例如如果2分钟内未收到短信则自动释放当前号码并重试或切换下一个号码。5. 部署与运行环境为了让代理能持续运行我将其部署在了一台Linux服务器上。使用systemd或supervisor来管理进程确保崩溃后能自动重启。同时需要为Playwright安装浏览器二进制文件和无头运行所需的依赖库如xvfb用于虚拟显示如果服务器没有图形界面。注意法律与道德边界在开始编码前这是最重要的一环。自动化工具是一把双刃剑。你必须确保你的使用场景仅用于你自己拥有或获得明确授权的账户和服务。遵守目标网站的服务条款ToS很多网站明确禁止自动化注册或登录。不用于骚扰他人、发送垃圾信息、进行欺诈或任何非法活动。合理控制请求频率避免对目标服务器造成拒绝服务攻击DoS的嫌疑。 本教程分享的技术思路仅用于教育目的和合规的自动化测试场景。3. 分步实现与核心代码详解3.1 环境搭建与依赖安装首先我们需要一个干净的Python环境建议3.8。使用虚拟环境是个好习惯。# 创建并激活虚拟环境 python -m venv venv source venv/bin/activate # Linux/macOS # venv\Scripts\activate # Windows # 安装核心依赖 pip install playwright # 安装Playwright所需的浏览器Chromium即可 playwright install chromium pip install requests # 用于调用短信API pip install python-dotenv # 用于管理配置如API密钥接下来创建项目结构phone_verification_agent/ ├── config.py # 配置文件 ├── sms_service.py # 短信服务抽象层 ├── browser_agent.py # 浏览器自动化核心 ├── parser.py # 验证码解析器 ├── task_manager.py # 任务状态机 ├── main.py # 主程序入口 └── .env # 环境变量切勿提交到Git3.2 集成短信接收服务我以假设的“SMSHub”API为例你需要替换为真实服务的文档。在.env文件中配置你的API密钥SMSHUB_API_KEYyour_api_key_here TARGET_COUNTRYus # 目标国家代码 SERVICE_CODEopt # 服务代码不同平台不同sms_service.py的核心是封装获取号码和读取短信的逻辑import requests import time from typing import Optional, Tuple from dotenv import load_dotenv import os load_dotenv() class SMSHubService: def __init__(self): self.api_key os.getenv(SMSHUB_API_KEY) self.base_url https://smshub.org/stubs/handler_api.php # 示例URL self.current_number None self.current_id None def get_phone_number(self) - Optional[Tuple[str, str]]: 获取一个临时手机号。返回 (国家代码号码, 订单ID) 或 None params { api_key: self.api_key, action: getNumber, service: os.getenv(SERVICE_CODE, opt), country: os.getenv(TARGET_COUNTRY, us) } try: resp requests.get(self.base_url, paramsparams, timeout30) if resp.status_code 200: # 假设API返回格式ACCESS_NUMBER:1234567890:1234567 parts resp.text.split(:) if parts[0] ACCESS_NUMBER: self.current_number parts[1] self.current_id parts[2] print(f[SMS] 获取号码成功: {self.current_number}, ID: {self.current_id}) return self.current_number, self.current_id print(f[SMS] 获取号码失败: {resp.text}) except Exception as e: print(f[SMS] 获取号码请求异常: {e}) return None def wait_for_sms(self, timeout120, interval5) - Optional[str]: 等待并获取短信内容。返回验证码或None if not self.current_id: return None start_time time.time() while time.time() - start_time timeout: params { api_key: self.api_key, action: getStatus, id: self.current_id } try: resp requests.get(self.base_url, paramsparams, timeout30) if resp.status_code 200: # 假设状态码STATUS_OK:123456 (验证码在冒号后) if resp.text.startswith(STATUS_OK:): full_sms resp.text.split(:, 1)[1] print(f[SMS] 收到短信: {full_sms}) return full_sms elif STATUS_WAIT in resp.text: # 还在等待中 time.sleep(interval) continue else: # 其他错误状态 print(f[SMS] 等待短信异常状态: {resp.text}) break except Exception as e: print(f[SMS] 查询短信状态异常: {e}) time.sleep(interval) print(f[SMS] 等待短信超时 ({timeout}秒)) return None def release_number(self): 释放当前使用的号码 if self.current_id: params { api_key: self.api_key, action: setStatus, id: self.current_id, status: 8 # 假设8是释放号码的状态码 } requests.get(self.base_url, paramsparams) print(f[SMS] 已释放号码: {self.current_number}) self.current_number None self.current_id None实操心得短信服务的选择与坑不同的短信平台API差异巨大。在集成前务必仔细阅读文档重点关注1) 获取号码的响应格式和可能的状态码如无可用号码2) 短信到达的推送方式是主动回调还是需要像上面这样轮询3) 号码的存活时间和费用有些按分钟计费超时未收到短信也要扣费4) 号码的质量是否是新号、是否被大量滥用过。建议先用平台提供的测试余额或免费额度进行完整的流程测试。3.3 构建浏览器自动化代理这是项目的核心。browser_agent.py负责所有与网页交互的操作。from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutError import logging class BrowserAgent: def __init__(self, headlessFalse): # 调试时可设为False看浏览器操作 self.playwright sync_playwright().start() # 重点使用更真实的启动参数模拟普通浏览器 self.browser self.playwright.chromium.launch( headlessheadless, args[ --disable-blink-featuresAutomationControlled, # 隐藏自动化特征 --disable-dev-shm-usage, --no-sandbox, --disable-setuid-sandbox, --disable-web-security, # 谨慎使用可能影响某些功能 --disable-featuresIsolateOrigins,site-per-process, ] ) # 创建上下文可以设置更真实的视窗、User-Agent、地理位置等 self.context self.browser.new_context( viewport{width: 1920, height: 1080}, user_agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, localeen-US, timezone_idAmerica/Los_Angeles, ) # 拦截并修改一些可能暴露自动化的属性 self.context.add_init_script( Object.defineProperty(navigator, webdriver, { get: () undefined }); window.chrome { runtime: {} }; ) self.page self.context.new_page() self.page.set_default_timeout(60000) # 全局超时设为60秒 def navigate_to_target(self, url): 导航到目标页面 try: response self.page.goto(url, wait_untilnetworkidle) # 等待网络空闲 if response and response.status 400: logging.error(f页面加载失败状态码: {response.status}) return False return True except PlaywrightTimeoutError: logging.error(页面加载超时) return False except Exception as e: logging.error(f导航发生未知错误: {e}) return False def fill_phone_number_and_request_sms(self, phone_number, phone_input_selector, submit_button_selector): 填写手机号并点击发送验证码按钮 try: # 等待输入框出现并填写 self.page.wait_for_selector(phone_input_selector, statevisible) self.page.fill(phone_input_selector, phone_number) print(f[Browser] 已填写手机号: {phone_number}) # 点击发送按钮 self.page.wait_for_selector(submit_button_selector, statevisible) # 有时需要模拟人类点击的延迟 self.page.click(submit_button_selector, delay100) print(f[Browser] 已点击发送验证码按钮) # 等待页面反应例如出现“已发送”提示或倒计时 self.page.wait_for_timeout(3000) return True except Exception as e: logging.error(f填写手机号或点击发送按钮失败: {e}) # 可以在这里截图保存便于调试 self.page.screenshot(pathferror_fill_{phone_number}.png) return False def fill_verification_code(self, code, code_input_selector, final_submit_selectorNone): 填写验证码并提交 try: self.page.wait_for_selector(code_input_selector, statevisible, timeout10000) self.page.fill(code_input_selector, code) print(f[Browser] 已填写验证码: {code}) if final_submit_selector: self.page.click(final_submit_selector, delay150) print(f[Browser] 已点击最终提交按钮) # 等待跳转或成功提示 self.page.wait_for_timeout(5000) # 可以检查页面元素确认成功例如 # success_element self.page.wait_for_selector(.success-message, timeout10000, statevisible) return True except Exception as e: logging.error(f填写验证码失败: {e}) self.page.screenshot(pathferror_code_{code}.png) return False def close(self): 清理资源 self.context.close() self.browser.close() self.playwright.stop()3.4 实现智能验证码解析器sms_parser.py负责从纷杂的短信文本中精准提取数字验证码。import re class VerificationCodeParser: def __init__(self): # 编译一些常用的正则模式 self.patterns [ re.compile(r\b(\d{4,6})\b), # 匹配4-6位独立数字 re.compile(r(?:code|验证码|verification code)[:\s]*(\d{4,6}), re.IGNORECASE), re.compile(r(\d{4,6})(?\s*是您的验证码)), ] # 排除某些常见但非验证码的数字如年份、短信号码 self.exclude_patterns [ re.compile(r^\d{10,}$), # 10位以上长数字可能是电话号码本身 re.compile(r202[0-9]), # 年份 ] def parse(self, sms_text: str) - str: 从短信文本中解析验证码返回字符串形式的数字如未找到返回空字符串 if not sms_text: return candidates [] # 策略1使用预定义模式匹配 for pattern in self.patterns: matches pattern.findall(sms_text) for match in matches: # 检查是否在排除列表中 if any(exclude_pattern.match(match) for exclude_pattern in self.exclude_patterns): continue candidates.append(match) # 策略2查找短信中所有连续数字段优先选择4或6位的 all_numbers re.findall(r\d, sms_text) for num in all_numbers: if len(num) in [4, 6] and num not in candidates: # 简单启发式如果这个数字前后有关键词优先级更高 idx sms_text.find(num) context sms_text[max(0, idx-10): min(len(sms_text), idx10len(num))] if any(keyword in context.lower() for keyword in [code, 验证码, verif]): candidates.insert(0, num) # 提到前面 else: candidates.append(num) # 决策逻辑优先返回第一个候选如果没有则返回空 if candidates: # 可以加入更多逻辑比如如果多个候选根据位置、上下文评分 print(f[Parser] 从短信中解析出候选验证码: {candidates}, 选择: {candidates[0]}) return candidates[0] else: print(f[Parser] 警告无法从短信中解析出验证码。原始短信: {sms_text}) return 3.5 组装任务状态管理器task_manager.py将以上所有模块串联起来形成一个有状态、可重试的自动化流程。import time from enum import Enum from sms_service import SMSHubService from browser_agent import BrowserAgent from sms_parser import VerificationCodeParser class TaskState(Enum): IDLE 空闲 ACQUIRING_NUMBER 获取号码中 BROWSING 浏览器操作中 WAITING_FOR_SMS 等待短信中 PARSING_CODE 解析验证码中 SUBMITTING_CODE 提交验证码中 SUCCESS 成功 FAILED 失败 class VerificationTask: def __init__(self, target_url, phone_input_selector, submit_button_selector, code_input_selector, final_submit_selectorNone): self.target_url target_url self.phone_input_selector phone_input_selector self.submit_button_selector submit_button_selector self.code_input_selector code_input_selector self.final_submit_selector final_submit_selector self.sms_service SMSHubService() self.browser_agent BrowserAgent(headlessTrue) # 生产环境用无头模式 self.parser VerificationCodeParser() self.state TaskState.IDLE self.retry_count 0 self.max_retries 3 def run(self): 执行单次验证任务 self.state TaskState.ACQUIRING_NUMBER number_info self.sms_service.get_phone_number() if not number_info: print([Task] 获取手机号失败任务终止。) self.state TaskState.FAILED self.cleanup() return False phone_number, order_id number_info self.state TaskState.BROWSING if not self.browser_agent.navigate_to_target(self.target_url): print([Task] 浏览器导航失败。) self._handle_failure(phone_number) return False if not self.browser_agent.fill_phone_number_and_request_sms(phone_number, self.phone_input_selector, self.submit_button_selector): print([Task] 填写手机号或请求短信失败。) self._handle_failure(phone_number) return False self.state TaskState.WAITING_FOR_SMS print(f[Task] 等待短信发送至 {phone_number}...) sms_text self.sms_service.wait_for_sms(timeout180) # 等待3分钟 if not sms_text: print([Task] 未在指定时间内收到短信。) self._handle_failure(phone_number) return False self.state TaskState.PARSING_CODE verification_code self.parser.parse(sms_text) if not verification_code: print([Task] 无法从短信中解析出验证码。) self._handle_failure(phone_number) return False self.state TaskState.SUBMITTING_CODE if not self.browser_agent.fill_verification_code(verification_code, self.code_input_selector, self.final_submit_selector): print([Task] 填写或提交验证码失败。) self._handle_failure(phone_number) return False # 这里可以添加成功后的检查比如确认页面跳转或出现成功元素 print(f[Task] 验证码 {verification_code} 提交成功) self.state TaskState.SUCCESS self.cleanup(successTrue) return True def _handle_failure(self, phone_number): 处理失败情况决定是否重试 self.retry_count 1 if self.retry_count self.max_retries: print(f[Task] 任务失败准备重试 ({self.retry_count}/{self.max_retries})...) self.sms_service.release_number() # 释放当前问题号码 time.sleep(5) # 重试前稍作等待 self.state TaskState.IDLE self.run() # 递归重试 else: print(f[Task] 任务失败已达最大重试次数 {self.max_retries}。) self.state TaskState.FAILED self.cleanup() def cleanup(self, successFalse): 清理资源 if not success: self.sms_service.release_number() self.browser_agent.close() print([Task] 资源清理完毕。)3.6 主程序入口与配置最后在main.py中我们配置具体的目标网站信息并启动任务。from task_manager import VerificationTask if __name__ __main__: # 示例假设我们要自动化某个测试网站“example-test.com”的注册验证 # **重要请务必替换为目标网站的实际元素选择器并确保你有权进行自动化操作** TARGET_URL https://example-test.com/signup # 替换为你的目标URL # 使用浏览器的开发者工具F12检查元素获取稳定且唯一的CSS选择器 PHONE_INPUT_SELECTOR input[namephone] # 手机号输入框选择器 REQUEST_SMS_BUTTON_SELECTOR button[typesubmit] # “发送验证码”按钮选择器 CODE_INPUT_SELECTOR input[nameverificationCode] # 验证码输入框选择器 FINAL_SUBMIT_SELECTOR button[typesubmit] # 最终的注册/登录提交按钮如果有 task VerificationTask( target_urlTARGET_URL, phone_input_selectorPHONE_INPUT_SELECTOR, submit_button_selectorREQUEST_SMS_BUTTON_SELECTOR, code_input_selectorCODE_INPUT_SELECTOR, final_submit_selectorFINAL_SUBMIT_SELECTOR ) success task.run() if success: print( 自动化验证任务执行成功) else: print(❌ 自动化验证任务失败。)4. 部署、优化与高级技巧4.1 服务器部署与守护进程开发完成后我们需要让代理在服务器上稳定运行。使用systemd是一个可靠的选择。创建一个服务文件/etc/systemd/system/phone-verification-agent.service[Unit] DescriptionPhone Verification Agent Afternetwork.target [Service] Typesimple Userubuntu WorkingDirectory/path/to/your/phone_verification_agent EnvironmentPATH/path/to/your/venv/bin ExecStart/path/to/your/venv/bin/python main.py Restarton-failure RestartSec10 StandardOutputsyslog StandardErrorsyslog [Install] WantedBymulti-user.target然后启用并启动服务sudo systemctl daemon-reload sudo systemctl enable phone-verification-agent sudo systemctl start phone-verification-agent # 查看日志 sudo journalctl -u phone-verification-agent -f4.2 对抗反爬虫与检测机制现代网站有各种手段检测自动化脚本。以下是我在实践中总结的对抗策略1. 指纹伪装Playwright启动时我们已经通过args和new_context的参数修改了一些基础指纹。但还不够。更高级的检测会检查navigator.plugins,navigator.languages,WebGL Vendor等。可以使用像playwright-stealth这样的社区插件或者手动注入更多脚本self.context.add_init_script( // 覆盖plugins属性 Object.defineProperty(navigator, plugins, { get: () [1, 2, 3, 4, 5], }); // 覆盖languages属性 Object.defineProperty(navigator, languages, { get: () [en-US, en], }); )2. 行为模拟人类操作是有随机性和延迟的。在点击、输入等操作中加入随机延迟和轨迹。import random def human_like_type(page, selector, text): element page.wait_for_selector(selector) element.click() for char in text: page.keyboard.type(char, delayrandom.uniform(50, 150)) # 随机延迟50-150毫秒 if random.random() 0.95: # 5%的概率打错并修正 page.keyboard.press(Backspace, delayrandom.uniform(30, 80)) page.keyboard.type(char, delayrandom.uniform(50, 150))3. 使用代理IP池如果从单一服务器IP发起大量请求很容易被封锁。集成代理IP池如从服务商购买轮换代理是必须的。可以在创建浏览器上下文时指定代理self.context self.browser.new_context( proxy{ server: http://your-proxy-ip:port, username: user, password: pass } )4. 处理人机验证CAPTCHA这是自动化最大的挑战。如果目标网站使用了reCAPTCHA v2/v3或hCaptcha纯前端自动化几乎无法绕过。可能的方案规避寻找没有验证码的入口点如移动端API、旧版页面。第三方打码服务集成如2Captcha、Anti-Captcha等服务它们提供API由真人或AI识别验证码。这需要额外成本且响应时间不定。人工介入兜底当检测到验证码时暂停脚本发出告警如发送邮件、Telegram消息等待人工处理后再继续。4.3 监控、日志与告警一个生产级的代理需要可观测性。结构化日志使用Python的logging模块将不同级别的日志INFO, WARNING, ERROR输出到文件和控制台并包含时间戳、任务ID等信息。关键指标监控记录成功率、平均耗时、失败原因分布如超时、解析失败、验证码错误。告警机制当连续失败次数超过阈值或服务长时间无响应时通过Telegram Bot、钉钉机器人或邮件发送告警。4.4 性能优化与扩展并发控制如果需要处理多个验证任务可以使用asyncio和playwright.async_api实现异步并发。但必须谨慎控制并发度避免对目标网站和短信服务造成过大压力。资源复用对于同一网站的任务可以考虑复用浏览器上下文或页面只更换手机号减少启动开销。配置化将不同网站的URL和选择器配置存储在数据库或JSON文件中使代理能够灵活适配多个目标而无需修改代码。5. 常见问题排查与实战心得在实际运行中你会遇到各种各样的问题。下面是我踩过的一些坑和解决方案问题1Playwright脚本被网站检测到页面显示“检测到自动化工具”。排查检查启动参数是否完整特别是--disable-blink-featuresAutomationControlled。使用chrome://version/在非无头模式下查看实际生效的参数。解决尝试更新Playwright和浏览器版本。使用更全面的指纹隐藏脚本。考虑在每次任务后完全重启浏览器实例并随机化启动参数如视窗大小、User-Agent。问题2收不到短信或者短信延迟极高。排查首先在短信服务商的后台查看号码状态和短信记录确认请求是否发出、号码是否被屏蔽。检查目标网站是否真的发送了短信查看浏览器网络请求中是否有发送短信的API调用成功。解决更换短信服务商或号码所属国家/地区。有些虚拟号码被大量网站拉黑。尝试使用“高质量”号码如果服务商提供此选项虽然更贵但成功率更高。增加等待短信的超时时间。问题3解析验证码失败提取出错误数字。排查打印出收到的原始短信全文分析其格式。检查解析器的正则表达式是否覆盖了所有可能格式。解决增强解析器的容错性。可以引入简单的机器学习模型如基于规则的特征匹配或使用OCR如果短信是图片形式但极少见。对于固定格式的短信编写针对性强的解析函数。问题4填写验证码后提交失败提示“验证码错误”或“已过期”。排查确认从收到短信到填写完成的耗时。验证码通常只有60-300秒有效期。检查填写验证码的输入框是否正确有时页面有多个输入框。解决优化流程减少收到短信后的操作延迟。实现收到短信后立即触发填写操作。如果网站有倒计时显示可以尝试从页面元素中读取剩余时间。问题5任务运行一段时间后浏览器崩溃或内存泄漏。排查监控服务器内存使用情况。检查Playwright和浏览器日志。解决确保在每个任务结束后无论成功失败都正确调用browser.close()和playwright.stop()。对于长时间运行的服务定期如每完成50个任务完全重启整个Playwright进程。使用Docker容器化部署可以设置内存限制和自动重启策略。我个人最深刻的体会是稳定性高于一切。一个成功率95%但偶尔会卡死需要人工重启的代理远不如一个成功率80%但能无人值守运行一周的代理。因此在代码中加入大量、细致的异常捕获和状态检查并为每一个可能的失败点设计明确的恢复或重试逻辑是构建这类自动化系统的核心。不要追求一次性100%的成功率而要追求流程的可观测、可干预、可恢复。每次失败都是一次学习机会通过日志分析失败原因不断迭代你的代理让它越来越聪明和健壮。