如何让Selenium读取一次页面后多次查找元素,避免重复读取提升效率?
find_element_by_css_selector速度慢的问题 这问题我太有共鸣了!之前爬静态页面时踩过一模一样的坑——每次调用find_element_by_css_selector,Selenium都要和浏览器驱动做一次远程通信,400次下来光来回请求的延迟就够让人崩溃的。好在你说页面内容不会变,那完全可以把页面内容一次性拉到本地,之后的元素查找都在本地完成,彻底摆脱频繁通信的开销。
下面给你两种最实用的解决方案:
方案1:一次性获取页面源码,用本地解析库处理(推荐)
这是效率最高的做法:先让Selenium帮你加载完页面,然后把完整的HTML源码拿下来,之后用BeautifulSoup或者lxml这类本地解析库来查找元素,全程不用再和浏览器打交道。
代码示例
from selenium import webdriver from bs4 import BeautifulSoup # 初始化浏览器并加载目标页面 driver = webdriver.Chrome() driver.get("你的目标页面URL") # 仅一次通信,获取完整页面源码 page_source = driver.page_source # 不需要浏览器了?直接关掉省资源 driver.quit() # 用BeautifulSoup本地解析HTML soup = BeautifulSoup(page_source, "lxml") # 用select方法查找元素,语法和Selenium的CSS选择器几乎完全一致 target_elements = soup.select("你的CSS选择器") # 批量处理400个元素,全程本地操作,速度飞起 for elem in target_elements: # 获取文本内容(strip=True去除多余空格) print(elem.get_text(strip=True)) # 获取属性,比如href、data-id等 print(elem.get("href"))
为什么推荐这个?因为本地解析库的速度比Selenium的远程调用快几个数量级,而且BeautifulSoup的select方法支持绝大多数CSS选择器,你几乎不用改原来的选择器代码,学习成本极低。
方案2:一次性获取所有匹配元素(保留Selenium API)
如果你还需要和元素做交互(比如点击、输入),不想切换到BeautifulSoup,那可以用Selenium的复数版查找方法,一次性把所有符合条件的元素拉到本地列表,之后遍历这个列表就不会再发起远程请求了。
代码示例
from selenium import webdriver driver = webdriver.Chrome() driver.get("你的目标页面URL") # 仅一次远程通信,获取所有匹配的元素列表 all_target_elements = driver.find_elements_by_css_selector("你的CSS选择器") # 遍历本地缓存的元素列表,操作属性不会再触发httplib请求 for elem in all_target_elements: # 获取文本内容 print(elem.text) # 获取自定义属性 print(elem.get_attribute("data-id")) driver.quit()
原理说明
原来你用find_element_by_css_selector(单数)时,每次调用都是向浏览器驱动发送一个查询请求,拿到单个元素后返回;而find_elements_by_css_selector(复数)会一次性把所有匹配的元素信息都拉取到本地,之后你访问元素的text、get_attribute等属性,都是读取本地缓存的内容,不会再发起新的远程请求,400次操作的开销直接变成1次,速度提升非常明显。
为什么原来的方法这么慢?
本质上是因为Selenium的Python客户端和浏览器驱动之间是通过HTTP(或WebSocket)通信的,每个find_element调用都是一次完整的请求-响应循环。400次调用就有400次来回的网络延迟,叠加起来自然慢到难以忍受。上面两种方案都是把"多次请求"变成"单次请求",剩下的逻辑在本地处理,从根源上解决了效率问题。
内容的提问来源于stack exchange,提问作者oshi2016

