Selenium 是目前最流行的 Web 自动化工具之一,无论是做自动化测试还是网页数据采集,都绕不开一个核心问题:如何定位页面上的多个元素,并把它们的文本内容提取出来。很多初学者会在这里踩坑,比如把 find_element 和 find_elements 混用,或者定位到了元素列表却发现取出来的文本是空的。本文将系统地讲解多个元素定位与文本提取的正确姿势。

一、find_element 与 find_elements 的区别
这是初学者最容易混淆的地方。Selenium 提供了两个非常相似的方法:find_element 和 find_elements。前者只返回页面中匹配定位条件的第一个元素,返回类型是 WebElement;后者返回所有匹配的元素,返回类型是一个列表 list,即使页面上只有一个元素匹配,返回的也是包含一个元素的列表;如果一个都没匹配到,find_element 会抛出 NoSuchElementException 异常,而 find_elements 则返回一个空列表,不会报错。
举个例子,页面上有 10 个商品的标题,如果你想全部拿到,就必须用 find_elements。如果你误用了 find_element,脚本不会报错,但永远只能拿到第一条数据,这种逻辑错误往往很难察觉。在实际编码时,建议根据业务需求明确选择:需要批量操作用 find_elements,只操作某一个特定元素用 find_element。
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://ipipp.com/demo")
# find_element:只返回第一个匹配的元素
first_item = driver.find_element(By.CLASS_NAME, "item-title")
print(first_item.text)
# find_elements:返回所有匹配的元素组成的列表
all_items = driver.find_elements(By.CLASS_NAME, "item-title")
for item in all_items:
print(item.text)
driver.quit()二、常用的八种定位策略与批量定位写法
在新版本的 Selenium(4.x)中,定位元素推荐使用 By 类来指定定位策略,写法为 driver.find_elements(By.XXX, "定位值")。常用的策略包括:By.ID、By.NAME、By.CLASS_NAME、By.TAG_NAME、By.LINK_TEXT、By.PARTIAL_LINK_TEXT、By.CSS_SELECTOR 和 By.XPATH。其中适合批量定位的主要是后四种,因为 ID 通常要求全页唯一,用 ID 批量定位意义不大。
By.TAG_NAME 常用于获取某一类标签的全部实例,比如提取页面上所有的表格行或链接。By.CSS_SELECTOR 性能好、语法简洁,例如 .list li .title 可以一次选中列表中所有标题节点。By.XPATH 功能最强,支持文本匹配和层级关系,例如 //div[@class='list']/ul/li 能精确定位某容器下的所有列表项。需要注意的是,By.CLASS_NAME 的值只能是单个 class 名,如果元素的 class 属性包含多个类名如 class="item title red",不能把整个字符串传进去,此时应改用 CSS 选择器 .item.title.red。
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://ipipp.com/news")
# 通过标签名批量定位所有链接
links = driver.find_elements(By.TAG_NAME, "a")
for link in links:
print(link.get_attribute("href"))
# 通过 CSS 选择器定位列表中所有标题
titles = driver.find_elements(By.CSS_SELECTOR, "ul.news-list li span.title")
for t in titles:
print(t.text)
# 通过 XPATH 定位包含特定文本的所有节点
hot = driver.find_elements(By.XPATH, "//a[contains(text(), '热点')]")
print("包含热点的链接数量:", len(hot))
driver.quit()三、提取文本的正确方式:text 与 get_attribute
拿到元素列表之后,提取内容主要依赖两个方法。element.text 返回元素的可见文本,也就是渲染后用户能看到的文字,它会自动拼接所有子节点的文本。element.get_attribute("textContent") 则获取 DOM 中的原始文本,包括被 CSS 隐藏的内容;如果想要元素的 HTML 片段,可以使用 get_attribute("innerHTML")。
两者的区别在实际项目中非常重要。当元素在页面上处于不可见状态(例如设置了 display:none,或者位于需要滚动才能显示的区域)时,text 可能返回空字符串,而 textContent 依然能拿到内容。另外,如果目标文字写在 value 属性中(比如输入框),就要用 get_attribute("value"),text 是取不到的。还有一点需要注意:如果要提取属性而不是文本,比如链接地址、图片地址,应分别使用 get_attribute("href") 和 get_attribute("src")。
items = driver.find_elements(By.CSS_SELECTOR, "div.product")
for item in items:
# 可见文本
name = item.text
# 包含隐藏内容的原始文本
raw = item.get_attribute("textContent")
# 元素内部 HTML
html = item.get_attribute("innerHTML")
print(name, raw[:20], html[:20])四、页面未加载完导致取不到文本怎么办
批量定位时最常见的问题是明明选择器写对了,find_elements 却返回空列表,或者取到的文本为空。根本原因通常是页面还没加载完,脚本就已经开始定位了。现代网页大量使用异步请求和动态渲染,DOM 是逐步生成的,直接定位自然找不到目标。
解决思路有三种。第一种是设置隐式等待 driver.implicitly_wait(10),它会对后续所有的定位操作生效,在限定时间内不断轮询直到元素出现,适合结构简单的页面。第二种是使用显式等待 WebDriverWait 配合 expected_conditions,例如 presence_of_all_elements_located 等待全部元素出现在 DOM 中,或用 visibility_of_all_elements_located 确保它们可见,这种方式更精确,是推荐做法。第三种是针对需要滚动加载的页面,先执行 driver.execute_script("window.scrollTo(0, document.body.scrollHeight)") 触发懒加载,再结合等待获取元素。要特别提醒的是,隐式等待和显式等待尽量不要混用,否则可能出现难以预测的等待时间。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get("https://ipipp.com/list")
# 显式等待:等待所有目标元素加载完成
wait = WebDriverWait(driver, 15)
items = wait.until(
EC.presence_of_all_elements_located(
(By.CSS_SELECTOR, "ul.list li")
)
)
for item in items:
print(item.text.strip())
driver.quit()五、实战建议与常见坑总结
在真实项目中批量提取文本,建议养成几个好习惯。第一,提取文本后调用 strip() 去掉首尾空白,网页源码中的缩进和换行经常混进文本里。第二,对 find_elements 返回的列表先判空再处理,避免页面结构变化导致脚本崩溃。第三,如果元素列表是通过一次 find_elements 拿到的,后续 DOM 变化不会自动同步到这个列表,需要重新执行定位方法刷新列表。第四,提取大量数据时可以配合列表推导式一次性收集结果,代码更简洁。
常见的坑还包括:class 名写成了复合形式导致 InvalidSelectorException;XPath 中使用了单引号包裹的属性值却和 Python 字符串引号冲突(可改用双引号或拼接变量);在 iframe 内的元素没有先执行 driver.switch_to.frame() 就去定位,结果始终找不到。只要理解了等待机制、定位策略和文本提取方法的适用边界,Selenium 的批量元素处理就会变得非常稳定可靠。
Selenium元素定位find_elements修改时间:2026-08-31 15:18:52