Java+Selenium爬取网页表格速度缓慢,求原因及优化方案
爬取速度缓慢的原因分析与优化方案
核心问题原因
- Excel写入的重复IO操作:当前
XLUtils.setCellData方法每写入一个单元格,都会重复执行「打开文件流→创建Workbook→写入单元格→关闭流/Workbook」的流程,随着行数增多,磁盘IO开销会指数级上升,这是速度越来越慢的核心原因。 - Selenium元素重复定位:内层循环中每次通过
rows.get(r).findElement(By.xpath(".//td["+(c+1)+"]"))查找单元格,相当于每次都重新遍历DOM树,重复的元素定位会累积大量耗时。 - 冗余的文本获取:空行判断时重复调用
getText(),该方法会触发Selenium与浏览器的通信,两次调用完全没必要。 - 全局隐式等待的累加影响:设置了10秒全局隐式等待,单次操作影响不明显,但大量元素查找会把等待时间的开销累积起来。
针对性优化方案
1. 重构Excel工具类,实现批量写入
将Workbook、文件流的打开/关闭操作放到整个写入流程的首尾,仅执行一次,所有单元格写入完成后再统一保存到文件,彻底消除重复IO的开销。
修改后的XLUtils示例:
package com.internetBanking.utilities; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Cell; public class XLUtils { private HSSFWorkbook wb; private HSSFSheet ws; private String xlFile; // 构造方法初始化Workbook和Sheet public XLUtils(String xlFile, String xlsheet) throws IOException { this.xlFile = xlFile; FileInputStream fi = new FileInputStream(xlFile); wb = new HSSFWorkbook(fi); ws = wb.getSheet(xlsheet); fi.close(); } // 写入单个单元格(仅内存操作,无磁盘IO) public void setCellData(int rowNum, int colNum, String data) { HSSFRow row = ws.getRow(rowNum); if (row == null) { row = ws.createRow(rowNum); } Cell cell = row.createCell(colNum); cell.setCellValue(data); } // 统一保存所有修改到文件 public void save() throws IOException { FileOutputStream fo = new FileOutputStream(xlFile); wb.write(fo); fo.close(); wb.close(); } // 保留原有工具方法(按需使用) public static int getRowCount(String xlfile, String xlsheet) throws IOException { FileInputStream fi = new FileInputStream(xlfile); HSSFWorkbook wb = new HSSFWorkbook(fi); HSSFSheet ws = wb.getSheet(xlsheet); int rowcount = ws.getLastRowNum(); wb.close(); fi.close(); return rowcount; } }
主类中对应的Excel写入逻辑修改:
// 初始化Excel工具类(仅执行一次) xl = new XLUtils(".\\datafiles\\covid.xls", "Covid Data"); // 写入表头 for(int col=1; col<header.size()-1; col++) { xl.setCellData(0, col-1, header.get(col).getText()); } int xlRow = 1; for(int r=1; r<rows.size(); r++) { String rowText = rows.get(r).getText(); if(rowText.equals("")) { System.out.println("Skipped row: "+r); continue; } System.out.println("Writing row "+r); // 一次性获取当前行所有单元格,避免重复DOM查找 List<WebElement> cells = rows.get(r).findElements(By.xpath(".//td")); for(int c=1; c<header.size(); c++) { // 处理单元格索引边界,避免数组越界 String cellData = cells.size() > c ? cells.get(c).getText() : ""; xl.setCellData(xlRow, c-1, cellData); } xlRow++; } // 所有数据写入完成后,统一保存到文件 xl.save();
2. Selenium端性能优化
- 缓存行内单元格元素:遍历行时一次性获取所有td元素并缓存,避免内层循环重复调用
findElement。 - 减少重复的getText()调用:行文本仅获取一次,同时用于空行判断和后续逻辑。
- 改用显式等待替代全局隐式等待:只在表格加载等关键节点使用显式等待,避免全局等待的不必要延迟:
// 替换原有的implicitlyWait WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("main_table_countries_today"))); - 启用无头模式:无需可视化界面时,开启Chrome无头模式减少渲染开销:
WebDriverManager.chromedriver().setup(); ChromeOptions options = new ChromeOptions(); options.addArguments("--headless=new"); options.addArguments("--disable-gpu"); driver = new ChromeDriver(options); - 替换XPath为CSS选择器:CSS选择器的定位速度通常优于XPath,比如将行定位改为:
rows = table.findElements(By.cssSelector("tr[role='row']"));
3. 细节优化
- 降低控制台输出频率:如果每行都打印
Writing row xxx,IO输出会占用额外资源,可以改为每10行打印一次,或仅打印关键节点信息。 - 提前过滤空行:在获取行列表后,先过滤掉空行,减少后续循环次数:
rows = rows.stream().filter(row -> !row.getText().isEmpty()).collect(Collectors.toList());
内容的提问来源于stack exchange,提问作者fdama
相关产品推荐
相关产品推荐

