Python Selenium批量send_keys循环逐轮变慢原因及优化方案
问题背景
使用Selenium WebDriver开展网页爬取工作时,需要向网站指定输入字段提交总计19000条固定格式的登记编号,以下载对应信息的.csv文件。测试阶段编写的批量提交代码如下:
df = pd.read_csv('arMestre.csv') x = 0 y = 200 i = 0 while i < 5: t1 = time.time() element.send_keys(df[x:y].to_string(header=False, index=False) + "\n") i += 1 x = y+1 y += 200 t2 = time.time() print(t2-t1)
测试得到5轮循环(每轮提交200条数据)的执行耗时逐轮上升:
7.61 - first loop 13.44 - second loop 23.53 - third loop 28.70 - fourth loop 35.12 - fifith loop
待查询的登记编号为14位数字格式,样例如下:
93040132242591 93040132254075 93040132277390 93040132314829 93040132426137 93040132838473 93040134252423 93040134211776
循环速度逐轮下降的根本原因
核心问题出在输入框内容累计和send_keys的实现逻辑上,具体有三点:
- 每次调用
send_keys()都是在输入框已有内容的基础上追加文本,从未清空输入框。第一轮提交时输入框为空,仅需处理200条文本;第二轮提交时输入框已存200条内容,追加后总长度达到400条;到第五轮时输入框累计已有1000条文本。浏览器渲染输入框内容、响应输入事件的开销随文本总长度线性上涨,直接导致耗时逐轮增加。 send_keys()的底层逻辑是模拟真人逐字符敲击键盘输入,每输入一个字符都会触发输入框绑定的所有前端事件(比如格式校验、输入联想、内容统计等),输入框内已有内容越长,这类事件的计算开销越高。- 原代码的切片逻辑
x = y+1存在bug,会导致每一批次的第一条数据被跳过(比如第一批取0-199,第二批x从201开始,会漏掉索引为200的编号)。
更高效的实现方案
按执行效率从高到低排序,可根据网站的校验强度选择:
方案1:通过JavaScript直接赋值输入框(效率最高)
send_keys的逐字符模拟逻辑是最大的性能瓶颈,可以直接通过Selenium调用JS接口修改输入框的DOM值,跳过逐字符输入、逐次触发事件的开销,提交速度是原生send_keys的几十到上百倍。
实现代码参考:
import pandas as pd import time from selenium.webdriver.common.by import By # 读取数据时直接把登记编号列转为字符串,避免长数字被转成科学计数法 df = pd.read_csv('arMestre.csv', dtype={0: str}) all_codes = df.iloc[:, 0].str.strip().tolist() # 批次大小可根据网站承载能力调整,无严格限制时可设为500-1000 batch_size = 500 # 替换成实际的输入框定位逻辑 input_element = driver.find_element(By.CSS_SELECTOR, "输入框对应的CSS选择器") for batch_idx in range(0, len(all_codes), batch_size): start = time.time() # 取当前批次数据,用换行拼接 current_batch = all_codes[batch_idx : batch_idx + batch_size] batch_content = "\n".join(current_batch) # JS直接清空、赋值输入框,并手动触发事件通知网站内容更新 driver.execute_script( """ const inputDom = arguments[0]; inputDom.value = arguments[1]; // 触发标准输入、变更事件,兼容大部分网站的前端校验逻辑 inputDom.dispatchEvent(new Event('input', {bubbles: true})); inputDom.dispatchEvent(new Event('change', {bubbles: true})); """, input_element, batch_content ) # 此处补充提交查询、等待CSV下载完成的逻辑 cost = time.time() - start print(f"第{batch_idx//batch_size + 1}批提交耗时:{cost:.2f}s")
如果JS赋值后网站识别不到输入内容(部分做了严格反爬的网站会监听真实键盘事件),再用下面的兼容方案。
方案2:保留send_keys逻辑,每次提交前清空输入框(兼容性最好)
只需要在每轮提交前调用clear()方法清空输入框的历史内容,就能避免文本累计带来的开销,每轮提交耗时会稳定在和第一轮接近的水平,同时修正原代码的切片bug。
实现代码参考:
import pandas as pd import time from selenium.webdriver.common.by import By df = pd.read_csv('arMestre.csv', dtype={0: str}) all_codes = df.iloc[:, 0].str.strip().tolist() batch_size = 200 input_element = driver.find_element(By.CSS_SELECTOR, "输入框对应的CSS选择器") for batch_idx in range(0, len(all_codes), batch_size): start = time.time() current_batch = all_codes[batch_idx : batch_idx + batch_size] batch_content = "\n".join(current_batch) + "\n" # 关键:提交前先清空输入框历史内容 input_element.clear() input_element.send_keys(batch_content) # 此处补充提交查询、等待下载的逻辑 cost = time.time() - start print(f"第{batch_idx//batch_size + 1}批提交耗时:{cost:.2f}s")
额外提效建议
- 不要用
df.to_string()生成批次文本,这个方法会自动做对齐补空格,既拖慢速度,还可能导致提交的编号带多余空格格式错误,直接转成字符串列表用换行拼接效率最高。 - 读取CSV时一定要把登记编号列指定为字符串类型,否则14位的长数字会被pandas自动转为科学计数法,导致提交的编号完全错误。
- 如果网站没有严格的浏览器环境校验,可以抓包获取查询接口的地址、参数规则,直接用
requests库批量发请求提交数据,速度会比Selenium方案快几个数量级。
内容的提问来源于stack exchange,提问作者Vawkay
相关产品推荐
相关产品推荐

