1. 项目拆解:自动录入到底在解决什么问题
先交代一下背景。这个项目来自某企业内部的一个典型业务痛点:运营人员每天要处理大量Excel表格数据,比如产品信息、订单记录、备案资料,需要一条条手工复制粘贴到网页业务管理系统里做登记。数据量一上来,问题就非常明显——一个人一天录入两三百条已经是极限,而且字段一多就容易串行、漏填,等发现错误再回头排查,基本是一场灾难。
我当时接到的需求就是:把一张几千行的Excel数据表自动录入到网页端的内部业务管理系统里,要求录入结果可追溯、可查重、可断点续跑。技术栈很明确,Python + Selenium + MySQL。这个组合在公司内部技术团队里非常通用,Python负责逻辑编排,Selenium负责模拟真实用户操作浏览器,MySQL负责录入过程和结果的持久化。
要理解这个项目的价值,首先要理解一点:很多网页系统并不提供批量导入功能,甚至连API都没有开放。你拿到的只是一个人工操作的网页表单。在这种情况下,Selenium这类浏览器自动化工具几乎是唯一性价比最高的方案。有人可能会问,为什么不用requests直接模拟接口提交?原因很现实——多数这类系统要么有动态token校验,要么有加密参数,要么会校验浏览器指纹,requests去模拟接口,调试成本极高,而且页面一升级可能就全部失效。相比之下,Selenium是实打实操控浏览器,走的是“正门”,系统再怎么改登录逻辑、怎么加前端校验,只要表单还能人工填写,自动化就能跑。
整个项目并不复杂,但涉及的知识点覆盖了Python基础、Selenium页面交互、MySQL建表与回写、数据清洗、异常恢复等多个方面,非常适合作为自动化脚本开发的练手项目,也能直接迁移到其他相似场景,比如批量报名、数据登记、巡检上报等。
1.1 业务场景还原与痛点梳理
模拟项目X的运营团队每天会收到一份Excel清单,里面包含需要录入的字段:业务编号、名称、分类、数量、日期、备注等。员工的操作路径是:打开浏览器,登录系统,点击“新增记录”,逐项复制粘贴,选分类、填日期、写备注,点击保存,然后处理下一条。全过程每条大约需要一到两分钟,难点不在于单条速度,而在于长时间重复操作带来的疲劳和错误率。
两个最头疼的问题:一是Excel里数据的格式和网页表单要求不完全一致,比如Excel里的日期是“20240515”,网页端要求“2024-05-15”,分类字段Excel里是中文名称,网页端是下拉框选项;二是录入过程中没有人会逐条确认成功与失败,有些记录保存失败也不容易被发现,等月底统计时才发现数据对不上。
所以我搭建这个自动录入工具时,核心思路不只是“把数据塞进表单里”,而是把它做成一个完整的小系统:数据读取、字段清洗转换、模拟录入、结果回写、日志记录、断点续跑。MySQL在这里不是可有可无的附属品,它是整个流程的“记分牌+账本”,每次录入的结果都落库,后续查账和统计就有据可依。
1.2 技术选型:为什么要用Selenium而不是其他方案
先回答一个很多人会问的问题:既然要自动化,为什么不直接用Python的requests库去POST表单数据?答案是这种系统的前端通常有一些机制刻意防止非浏览器端提交,比如页面里有随机的隐藏字段、authentication token、时间戳校验等。你通过requests手工构造请求,第一次可能成功了,第二次就因为某个参数没有正确生成而失败。排错成本很高,而且代码非常脆弱,页面一改就要跟着改。
打个比方,这就好比你想进一栋楼,requests是研究门禁卡芯片然后去复制一张卡,Selenium是直接请楼里的人帮你把门拉开。显然后者更省力,也更不容易被门禁系统识别为异常。
MySQL在这个项目中承担的任务同样清晰:存储Excel导入的原始数据、记录每一条的录入状态、保存每次提交后的系统返回信息。没有数据库,程序如果跑到一半崩溃了,你根本不知道已经录了哪些,只能从头再来或者靠日志一行行翻,效率极低。有了状态字段,程序重启后可以直接恢复到上次断点,只处理status=0的记录。
1.3 系统整体设计与模块划分
整个自动录入工具可以分为四个模块:数据准备层、页面操作层、数据持久层、调度执行层。
- 数据准备层:读取Excel,清理空行、格式化字段、生成业务主键
- 页面操作层:封装浏览器启动、元素定位、输入框填充、下拉选择、日期处理、提交按钮点击
- 数据持久层:通过pymysql连接MySQL,提供写入、更新、查询状态等基础函数
- 调度执行层:控制整体循环流程,调用上述模块,处理异常重试与日志输出
这样分层的目的很明确,就是为了“解耦”。页面操作层如果换了一个系统,只需要重写定位逻辑,数据准备层、数据持久层几乎不用动;反过来,如果Excel格式变了,也只需要改数据准备层的解析逻辑,不影响页面操作。项目不大,但这种分层思路在日后扩展时非常有用。
2. 环境准备与工具选型
工欲善其事,必先利其器。这个项目对运行环境没有很高的要求,一台普通的办公电脑就能跑起来。但有几个细节值得提前确认,不然在折腾依赖的环节会浪费不少时间。
2.1 Python与依赖包的安装
Python版本建议使用3.8以上,推荐3.10或者3.11稳定版。项目依赖的核心库有四个:selenium负责浏览器自动化,pymysql负责MySQL交互,pandas和openpyxl配合完成Excel读取。安装命令一行搞定:
pip install selenium pymysql pandas openpyxl装完之后可以验证一下版本,避免因为版本不匹配导致后面出现莫名其妙的问题:
python -c "import selenium; print(selenium.__version__)"这里要特别提醒一点,pandas读取Excel时底层依赖openpyxl,所以两个库要一起装,不要只装pandas。另外,如果你的电脑同时装了多个Python版本,一定要确认pip安装到了你要用的那个解释器下面,建议直接用虚拟环境,避免环境污染。
2.2 浏览器驱动的版本匹配问题
Selenium本身不是浏览器,它是通过驱动(如chromedriver)来操控浏览器的。很多人第一次跑Selenium程序报错“session not created”或者“chromedriver executable needs to be in PATH”,就是因为浏览器和驱动版本对不上。
常用的组合是Chrome浏览器 + chromedriver,当然也可以选Edge和msedgedriver。Chrome的版本号可以在浏览器地址栏输入chrome://version查看,然后去知名镜像站下载对应版本的chromedriver。版本选择原则是“主版本号必须一致,小版本尽量接近”,比如Chrome 120就用chromedriver 120.x.x.x。
下载后可以把驱动放到和脚本同一个目录,然后在代码里指定路径:
from selenium import webdriver driver = webdriver.Chrome(executable_path=r"D:\chromedriver\chromedriver.exe")不过新版Selenium(4.6以上)支持了自动管理驱动,只需要把Chrome浏览器升级到较新版本,代码里直接写webdriver.Chrome()即可,Selenium Manager会自动下载匹配的驱动。对于公司内网环境不方便访问外网的场景,手动指定路径反而更可靠。
2.3 MySQL库表设计:状态字段决定断点续跑
MySQL这边不需要复杂的表结构,但几个关键字段一定要考虑到位。我设计的是一张业务数据表和一张操作日志表。
业务数据表保存从Excel里读出来的原始数据和录入状态:
CREATE TABLE `biz_record` ( `id` int NOT NULL AUTO_INCREMENT, `biz_no` varchar(64) NOT NULL COMMENT '业务编号', `biz_name` varchar(255) NOT NULL COMMENT '名称', `category` varchar(64) DEFAULT NULL COMMENT '分类', `quantity` int DEFAULT NULL COMMENT '数量', `entry_date` date DEFAULT NULL COMMENT '日期', `remark` varchar(500) DEFAULT NULL COMMENT '备注', `record_status` tinyint DEFAULT '0' COMMENT '0待录入 1成功 2失败 3跳过', `submit_message` varchar(500) DEFAULT NULL COMMENT '系统返回结果', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这里有两个关键点。一是业务编号建议加唯一索引,防止同一份数据被重复导入;二是record_status字段是整个工具的“心脏”,程序每次启动前只拉取status=0的记录,跑完一条立刻更新状态,这样无论程序因为什么原因中断,下次启动都能从上次中断的地方继续,不需要人工去记跑到哪一条了。
日志表则用于记录每次操作的关键信息:
CREATE TABLE `operate_log` ( `id` int NOT NULL AUTO_INCREMENT, `biz_id` int NOT NULL, `operate_type` varchar(32) DEFAULT 'submit', `result` varchar(1000) DEFAULT NULL, `log_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;日志表的作用体现在排错阶段。程序跑完一批数据后,不要只看成功数,还要能从日志表里回溯某一条记录失败时的上下文信息。
3. Selenium自动化核心代码实现
环境准备好、表结构建好,就进入最核心的环节——写自动化代码。这一部分是整个项目的重头戏,我拆成四块来讲:数据读取与清洗、浏览器初始化、页面元素操作封装、主流程控制。
3.1 Excel数据读取:格式问题都在这里踩坑
Excel数据读取本身不复杂,但格式问题特别多。我的建议是第一步就把数据清洗干净,而不是等到填表时再处理。举几个常见的坑:
第一个坑是“产品编号”这类字段很容易变成科学计数法或者小数。Excel里单元格如果是常规格式,长数字会被显示成类似8.19002E+17这样的形式。pandas读取时如果没有指定类型,拿到手的就是数字。解决办法是读取时强制指定dtype为字符串:
import pandas as pd df = pd.read_excel("data_2025.xlsx", dtype={"biz_no": str, "phone": str})第二个坑是空行和Null值。Excel表格里看起来空白的行,pandas会读成NaN,如果不过滤,后面在页面上输入“None”或者空字符串都会出问题。建议统一做一次dropna:
df = df.dropna(subset=["biz_no", "biz_name"]) df = df.fillna("")第三个坑是日期格式。Excel里可能是“20240515”“2024/05/15”“2024-05-15”等各种写法,而网页端表单大概率接受的是标准格式。清洗阶段就统一成datetime对象,再按需格式化:
from datetime import datetime def parse_date(value): if not value: return "" value = str(value).strip().replace("/", "-") try: return datetime.strptime(value, "%Y%m%d").strftime("%Y-%m-%d") except ValueError: try: return datetime.strptime(value, "%Y-%m-%d").strftime("%Y-%m-%d") except ValueError: return ""清洗完成后,把数据写入MySQL,作为本次任务的数据池:
import pymysql conn = pymysql.connect(host="localhost", user="root", password="yourpassword", database="auto_entry", charset="utf8mb4") cursor = conn.cursor() for row in df.to_dict("records"): sql = "INSERT INTO biz_record (biz_no, biz_name, category, quantity, entry_date, remark) VALUES (%s,%s,%s,%s,%s,%s)" cursor.execute(sql, (row["biz_no"], row["biz_name"], row["category"], row["quantity"], row["entry_date"], row["remark"])) conn.commit()注意:如果准备断点续跑,这里要先按业务编号查重,存在相同编号就直接跳过,避免重复插入:
check_sql = "SELECT COUNT(*) FROM biz_record WHERE biz_no=%s" cursor.execute(check_sql, (row["biz_no"],)) if cursor.fetchone()[0] > 0: continue3.2 浏览器初始化配置
Selenium操作浏览器的第一步是创建driver对象。除了最基本的启动,还需要做一些配置,比如最大化窗口、屏蔽不必要的日志、设置页面加载超时等。我常用的初始化模板如下:
from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_argument("--start-maximized") options.add_experimental_option("excludeSwitches", ["enable-automation"]) # 去掉自动化提示 options.add_experimental_option("useAutomationExtension", False) driver = webdriver.Chrome(options=options) driver.set_page_load_timeout(30) driver.implicitly_wait(10)这里有几个细节值得解释一下。第一,excludeSwitches的enable-automation参数可以去掉Chrome页面上的“Chrome正受到自动软件的控制”提示条,虽然不影响功能,但视觉上更干净,在某些页面中避免触发前端检测。第二,implicitly_wait设置的是全局隐式等待,意思是Selenium在查找元素时,如果没找到,会轮询等待最多10秒再报错。这个时间不用太长,3到10秒之间即可,太长了会拖慢整体速度。
另外,如果你的系统里需要登录才能进录入页面,我不建议把账号密码写死在代码里。更稳妥的做法是单独写一个登录函数,把账号密码从配置文件中读取,登录成功后等待一段时间再继续操作。登录逻辑如果涉及验证码,那就得配合人工处理,这部分后面单独讲。
3.3 元素定位与输入封装:这套函数可以复用
Selenium自动化的核心难点不在启动浏览器,而在于稳定地找到网页上的元素并把数据填进去。不同网页的元素属性千差万别,有的元素ID是动态生成的,有的控件不是简单的input框,而是自定义组件。我建议不要每个字段都单独写一段定位代码,而是封装一个通用的操作类。
先定义定位器,优先使用稳定的属性:
from selenium.webdriver.common.by import By # 示例:新记录表单页面 LOCATORS = { "biz_no": (By.ID, "bizNo"), "biz_name": (By.ID, "bizName"), "category_select": (By.ID, "category"), "quantity": (By.ID, "quantity"), "entry_date": (By.ID, "entryDate"), "remark": (By.ID, "remark"), "submit_btn": (By.CSS_SELECTOR, "button.submit-btn"), "success_msg": (By.CSS_SELECTOR, ".alert-success"), }然后封装安全的输入方法,这里用显式等待加异常捕获:
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def wait_and_send_keys(driver, locator, text): elem = WebDriverWait(driver, 10).until(EC.visibility_of_element_located(locator)) elem.clear() elem.send_keys(text)为什么不用time.sleep固定等几秒?因为页面加载速度受网络影响波动很大,固定sleep要么等多了浪费时间,要么等少了元素还没渲染出来。WebDriverWait是等到元素可见了才继续,效率高且稳定。这是Selenium新手最容易忽略的一个点。
3.4 下拉框选择:Select对象的正确用法
网页表单里最常见的控件除了输入框就是下拉框。如果是原生HTML的select标签,Selenium提供了Select类来处理:
from selenium.webdriver.support.ui import Select def select_by_text(driver, locator, text): elem = WebDriverWait(driver, 10).until(EC.element_to_be_clickable(locator)) Select(elem).select_by_visible_text(text)这里要说一个很常见的坑:如果text是动态变化的,比如分类选项是“电子产品-手机”这种带层级的前缀,直接按可见文本选可能因为多了一个空格而失败。建议提前把Excel里的分类值和网页下拉选项对应关系维护成一个字典,读取数据时先做映射,映射不到的数据标记为跳过,并写入日志,而不是在页面上反复尝试导致重复提交。
另一个坑是部分网页下拉框是经过JS改造的自定义组件,并不是原生的select标签。点击后会弹出一层div列表,这时候需要直接去点击对应的li或者div。这种情况没有统一解法,核心思路就是F12打开开发者工具,观察展开后的DOM结构,然后用XPath或CSS选择器定位选项元素。
3.5 日期控件的处理技巧
日期字段通常有两种形式:可直接输入的文本框,和只能点击选择的日历组件。
如果是可输入的文本框,最简单的办法是先清空再填入格式化好的日期字符串,这也是我在清洗数据阶段把日期统一成“YYYY-MM-DD”格式的原因。但要注意部分日期控件有readonly属性,直接send_keys不生效。解决办法是先通过JS移除readonly属性,再输入:
def set_date(driver, locator, date_str): elem = WebDriverWait(driver, 10).until(EC.visibility_of_element_located(locator)) driver.execute_script("arguments[0].removeAttribute('readonly');", elem) elem.clear() elem.send_keys(date_str)如果是日历点击型组件,则要先点击日期框弹出日历面板,再找到对应日期元素点击。日期数字通常在一个表格里,需要手动构造XPath:
# 日历面板中,按文本内容定位日单元格 day_xpath = f"//div[contains(@class,'calendar-panel')]//td[contains(@class,'day') and text()='{day}']" driver.find_element(By.XPATH, day_xpath).click()处理日期控件不要硬刚,灵活使用JS执行和XPath定位会省很多事。
3.6 主流程控制:循环、状态回写、断点续跑
单个表单的填充逻辑封装好之后,主流程就是一个循环控制的活。整体逻辑如下:
def process_pending_records(driver): pending = fetch_pending_records() for rec in pending: try: driver.get("http://internal-system.example.com/entry") wait_and_send_keys(driver, LOCATORS["biz_no"], rec["biz_no"]) wait_and_send_keys(driver, LOCATORS["biz_name"], rec["biz_name"]) select_by_text(driver, LOCATORS["category_select"], rec["category"]) wait_and_send_keys(driver, LOCATORS["quantity"], str(rec["quantity"])) set_date(driver, LOCATORS["entry_date"], rec["entry_date"]) wait_and_send_keys(driver, LOCATORS["remark"], rec["remark"]) driver.find_element(*LOCATORS["submit_btn"]).click() success = check_success(driver) if success: update_record_status(rec["id"], status=1, message="success") else: update_record_status(rec["id"], status=2, message=extract_error_msg(driver)) except Exception as e: update_record_status(rec["id"], status=2, message=str(e)) continue其中fetch_pending_records只查record_status=0的数据,update_record_status每跑完一条就更新状态。这样整个流程具备断点续跑能力,就算跑到一半登录态过期、程序崩了,重启后直接拉取剩余待处理记录继续跑就行。
check_success这个函数建议用显式等待提交后页面上的成功提示元素,而不是盲目sleep两秒再截图。常见实现:
def check_success(driver): try: WebDriverWait(driver, 10).until( EC.visibility_of_element_located(LOCATORS["success_msg"]) ) return True except Exception: return False这里再补充一个经验:对于数据量较大的批量录入,不要同时把所有数据灌进内存一次性循环。更好的方式是分批次从数据库取,比如每次取50条,跑完再取下一批,避免内存持续膨胀,也方便中途暂停。
4. 实际踩坑与排查技巧实录
自动化脚本看起来简单,实际跑起来幺蛾子特别多。我整理了一些高频问题和排查思路,都是实战中真实踩过的坑。
4.1 元素找不到:frame与iframe是罪魁祸首
很多后台管理系统的录入表单是嵌套在一个iframe里的。Selenium默认只能访问主文档中的元素,如果你直接通过ID查找iframe内部的元素,十有八九会报NoSuchElementException。遇到这种情况,先切到iframe再操作:
frame = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.TAG_NAME, "iframe"))) driver.switch_to.frame(frame) # 操作iframe内部元素... # 操作完成后切回主文档 driver.switch_to.default_content()判断页面有没有iframe,最简单的方式是浏览器F12打开控制台,输入document.querySelectorAll('iframe'),如果有返回值,那多半就是这个原因。
4.2 动态ID与属性变化:优先用XPath相对定位
有的表单每次打开页面时元素的ID会出现随机后缀,比如input_abc123、input_def456,这种就不能用固定ID定位。排查时优先看元素的name属性、class属性是否稳定,再看它的父级和兄弟节点是否有稳定的标识。
XPath相对定位在这种情况下会很顺手,比如:
# 找到label文本为“业务编号”的输入框 xpath = "//label[contains(text(),'业务编号')]/following-sibling::input" driver.find_element(By.XPATH, xpath)这种写法虽然啰嗦,但抗页面改版能力反而强。Selenium定位元素就像找人,如果你知道对方具体房间号(固定ID),就敲门;如果房间号总变,但你知道门口挂着“姓名:张三”的门牌,那就用XPath按门牌去找。
4.3 点击按钮被拦截:滚动与JS点击
有时候元素明明定位到了,但在调用click()时报错“element click intercepted”,这是因为按钮不在可视区域内,或者被某个悬浮层盖住了。解决办法是先滚动到元素位置,再点击:
element = driver.find_element(*LOCATORS["submit_btn"]) driver.execute_script("arguments[0].scrollIntoView();", element) element.click()如果滚动后还是被遮罩拦截,直接用JS强制点击:
driver.execute_script("arguments[0].click();", element)不过JS点击虽然能生效,但不会触发部分框架绑定的事件,某些页面上按钮的点击事件是绑定在鼠标按下、鼠标抬起上的,JS click可能无法触发完整逻辑。所以如果JS点击后页面没有反应,还是要回到“手动先操作一遍,观察控制台有无报错”的老路上去排查。
4.4 验证码与人工介入机制
这个项目里最棘手的问题就是验证码。有些系统登录时需要验证码,有些系统提交频率过高时也会弹出验证码。完全绕过验证码不在这个项目的讨论范围内,我的经验是给程序设计一个人工介入的“暂停-继续”机制。
具体做法是在代码里增加一个检测点,如果检测到验证码图片出现,就停止自动化流程,用程序弹窗或者命令行提示操作人员手动输入验证码:
def check_captcha(driver): try: captcha_img = driver.find_element(By.XPATH, "//img[contains(@class,'captcha')]") if captcha_img.is_displayed(): code = input("请手动查看验证码并输入:") driver.find_element(By.ID, "captchaInput").send_keys(code) return True except Exception: return False这里要注意的是,验证码输入框填完验证码后,不要立刻接着点提交,最好等待一两秒让人工确认页面上是否显示校验通过,避免因为验证码输入错误导致整条记录被判定失败。
4.5 数据库连接超时与重连机制
pymysql默认的connection有个问题:如果程序长时间空闲或者跑了很久,MySQL服务端会主动断开空闲连接。下次执行execute时就会报“MySQL server has gone away”。解决办法是在主循环里增加重连机制:
def execute_with_reconnect(conn, cursor, sql, params): try: cursor.execute(sql, params) except pymysql.err.OperationalError: conn = pymysql.connect(host="localhost", user="root", password="yourpassword", database="auto_entry", charset="utf8mb4") cursor = conn.cursor() cursor.execute(sql, params)或者更简单,每次更新状态时单独建立一次连接,执行完关闭。这种方式虽然浪费一点连接开销,但对于录入量几百到几千条的场景影响可以忽略,换来的是稳定性。
4.6 重复提交防护与幂等设计
最后也是最重要的一点:自动化脚本跑批量录入时,最怕的就是重复提交。原本想提交100条,因为网络抖动,某一条提交成功了但程序没收到成功提示,于是重试时又提交了一次,数据库里就多了一条重复数据。
解决思路有两种。第一种是前端判断,提交前先按业务编号在系统里查询是否已存在,存在就跳过;第二种是后端兜底,如果系统支持提交唯一标识字段,就把业务编号作为外壳字段提交上去,数据库层面做唯一约束。第一种方案见效快,但多了一次查询操作,影响整体速度;第二种方案更稳妥,但不是所有系统都支持。
我在这类项目里更推荐第一种方案配合状态机控制:每条记录只有在status=0时才允许提交,提交中先在本地把状态改成4(处理中),等系统返回明确结果后再更新为成功或失败。这样即使程序崩溃重启,也不会对一条记录发起重复提交。
4.7 高频排查问题速查表
| 问题现象 | 可能原因 | 排查方法 |
|---|---|---|
| NoSuchElementException | 元素不在当前frame里、ID动态变化、页面未加载完成 | 先检查iframe,打印当前页面HTML片段,使用WebDriverWait |
| ElementClickInterceptedException | 遮罩层覆盖、元素不在可视区 | scrollIntoView后点击,必要时JS点击 |
| SessionNotCreatedException | chromedriver版本与浏览器版本不一致 | 检查版本号,下载对应驱动 |
| MySQL server has gone away | 空闲连接被服务器断开 | 增加重连机制,或每次执行独立连接 |
| 成功提示出现了但数据没入库 | 前端校验拦截、JS事件未触发 | 手动操作一遍对比前端控制台日志 |
| 下拉框选项无法点击 | 自定义下拉组件、需要展开列表 | F12查看DOM,改为点击对应列表项 |
| 程序跑一半登录态过期 | Session或Cookie失效 | 增加登录态检测,过期后重新登录并继续 |
5. 项目经验总结与扩展方向
项目做完之后,几个同事也在他们自己的业务场景里复用了这套代码,换汤不换药,改改定位器和字段映射就跑起来了。我在这个过程中最大的体会是,自动化脚本写起来不难,难的是一开始就把“状态管理”和“异常恢复”想清楚。
很多初学者写Selenium脚本时,脑子里只有一个简单的“循环填表”模型:数据读完一遍,填完就结束。但真实业务环境下,脚本可能要跑几个小时,中途会遇到各种意外——网络卡顿、系统弹窗、元素偶发定位失败、数据库连接断开,甚至电脑休眠。如果没有状态字段和断点续跑机制,任何一次中途失败都意味着前面几小时的成果要全部重来。所以在我给这类项目搭框架时,第一步永远是设计好数据库表,把“待录入/处理中/成功/失败”这个状态机立起来,后续所有逻辑都围绕状态机转。
第二个体会是要给程序留“人控开关”。我在代码里设置了几个关键节点的人工确认环节,比如登录完成后等待用户按回车继续,遇到验证码时提示用户输入,批量提交过程中每处理50条数据暂停一次并输出进度。这些看似“不智能”的设计,在实机运行中反而大幅降低了无人值守的风险。自动化和人工不是二选一,而是要用机器做重复劳动,用人做判断和兜底。
这个项目后续的扩展空间也很大。比如可以加一个简单的Web界面,把MySQL里的记录状态可视化展示;也可以接入定时任务,让脚本每天自动从指定目录拉取新增Excel并执行录入,形成一条全自动的数据管道。再进一步,还可以加入邮件通知,批量任务结束后把成功数和失败明细发给相关人,进一步完善自动化闭环。
如果你正准备做一个类似的网页自动录入项目,我的建议是:先花半天时间用F12手动把目标页面的DOM结构摸透,不要急着写代码;再把数据清洗和状态管理设计好;最后才动手写Selenium操作逻辑。按照这个顺序来,项目进度反而会快很多,踩的坑也会少很多。