Python Selenium 实战指南:精准定位、高效交互与智能等待
一、 引言
Selenium 是 Python 中进行 Web 自动化测试和动态网页爬取的“瑞士军刀”。它能模拟真实用户操作浏览器,完美解决 JavaScript 渲染、Ajax 异步加载等 requests 库无法处理的难题。
本文跳过繁琐的环境安装(假设你已准备好 Python 和 ChromeDriver),直接深入 Selenium 的核心灵魂:元素定位、页面交互和等待机制。最后,我们将通过一个完整的“Bing 图片爬虫”实战项目,串联所有知识点。
二、 Selenium 基础控制流
在开始操作元素前,我们需要掌握控制浏览器的基本动作。
核心代码示例:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
import time
# 1. 初始化浏览器驱动 (假设你已安装好)
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service)
try:
# 2. 访问页面
driver.get("https://www.baidu.com")
# 3. 基础控制
print(f"标题: {driver.title}")
print(f"URL: {driver.current_url}")
# 窗口操作
driver.maximize_window() # 最大化
# driver.set_window_size(1024, 768) # 设置大小
# 页面导航
driver.get("https://www.taobao.com") # 跳转
driver.back() # 后退
time.sleep(1)
driver.forward() # 前进
time.sleep(1)
driver.refresh() # 刷新
finally:
# 4. 关闭浏览器 (推荐使用 quit 而不是 close)
driver.quit()
三、 元素定位:8 种武器(核心)
定位元素是自动化最难也最关键的一步。Selenium 提供了 8 种标准定位方式,建议优先级:ID > Name > Class > CSS > XPath。
. 单元素定位 (find_element)
表格
定位方式 语法示例 适用场景
By.ID driver.find_element(By.ID, "kw") 最快最稳,页面唯一ID
By.NAME driver.find_element(By.NAME, "wd") 表单元素常用
By.CLASS_NAME driver.find_element(By.CLASS_NAME, "s_ipt") 单个class样式
By.TAG_NAME driver.find_element(By.TAG_NAME, "input") 获取标签类型,常用于爬取所有链接
By.LINK_TEXT driver.find_element(By.LINK_TEXT, "新闻") 精确匹配超链接文本
By.PARTIAL_LINK_TEXT driver.find_element(By.PARTIAL_LINK_TEXT, "新") 模糊匹配超链接文本
. 多元素定位 (find_elements)
返回列表,用于批量处理。
# 获取页面所有链接
all_links = driver.find_elements(By.TAG_NAME, "a")
for link in all_links:
print(link.get_attribute("href"))
. 高级定位法(最强大)
当没有 ID 时,CSS 和 XPath 是我们的终极武器。
CSS 选择器 (推荐): 语法简洁,速度快。
# 通过ID
driver.find_element(By.CSS_SELECTOR, "#kw")
# 通过Class
driver.find_element(By.CSS_SELECTOR, ".s_ipt")
# 属性定位
driver.find_element(By.CSS_SELECTOR, "[name='wd']")
# 组合定位
driver.find_element(By.CSS_SELECTOR, "input#kw.s_ipt[name='wd']")
XPath 定位 (万能): 适合复杂层级结构。
# 绝对路径 (不推荐,太脆弱)
# driver.find_element(By.XPATH, "/html/body/div[1]/div[2]/...")
# 相对路径 (推荐)
# 通过属性
driver.find_element(By.XPATH, "//input[@id='kw']")
# 通过文本内容
driver.find_element(By.XPATH, "//a[text()='新闻']")
# 模糊匹配文本
driver.find_element(By.XPATH, "//a[contains(text(), '新')]")
# 逻辑运算
driver.find_element(By.XPATH, "//input[@id='kw' and @name='wd']")
四、 元素操作与交互
定位到元素后,我们需要模拟人类的操作。
. 输入与点击
from selenium.webdriver.common.keys import Keys
# 1. 输入文本
search_box = driver.find_element(By.ID, "kw")
search_box.clear() # 清空
search_box.send_keys("Selenium 教程")
# 2. 点击
search_button = driver.find_element(By.ID, "su")
search_button.click()
# 3. 模拟键盘事件 (如回车搜索)
search_box.send_keys(Keys.ENTER)
. 获取元素信息
# 获取属性和文本
print(f"标签名: {search_box.tag_name}")
print(f"输入框值: {search_box.get_attribute('value')}")
print(f"文本内容: {search_box.text}") # 注意: text通常用于div/span等有显示文本的标签
# 状态判断
print(f"是否可见: {search_box.is_displayed()}")
print(f"是否可用: {search_box.is_enabled()}")
. 下拉框处理 (Select)
对于 <select> 标签,需要使用 Select 类。
from selenium.webdriver.support.select import Select
# 定位下拉框
select_element = driver.find_element(By.ID, "cars")
select = Select(select_element)
# 三种选择方式
select.select_by_visible_text("Audi") # 通过文本
select.select_by_value("opel") # 通过value属性
select.select_by_index(0) # 通过索引 (从0开始)
# 获取选项
all_options = select.options
first_selected = select.first_selected_option
五、 等待机制:告别超时错误
这是新手最容易踩坑的地方! 网页加载需要时间,盲目使用 time.sleep() 是低效的。
. 隐式等待 (Implicit Wait)
全局设置,一次生效。告诉 WebDriver 轮询查找 DOM,直到元素出现或超时。
# 设置全局隐式等待10秒
driver.implicitly_wait(10)
# 之后的 find_element 操作都会应用这个等待
element = driver.find_element(By.ID, "some_dynamic_element")
缺点:无法判断页面特定元素的状态(如是否可点击)。
. 显式等待 (Explicit Wait) —— 强烈推荐
针对特定元素设置条件,更加精准和高效。
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 创建等待对象:最长等待10秒,每0.5秒检查一次
wait = WebDriverWait(driver, 10, poll_frequency=0.5)
# 等待条件示例:
# 1. 元素出现 (在DOM中)
element = wait.until(EC.presence_of_element_located((By.ID, "kw")))
# 2. 元素可见 (不仅在DOM中,而且宽高不为0)
element = wait.until(EC.visibility_of_element_located((By.ID, "lg")))
# 3. 元素可点击
element = wait.until(EC.element_to_be_clickable((By.ID, "su")))
# 4. 文本出现在元素中
element = wait.until(EC.text_to_be_present_in_element((By.LINK_TEXT, "新闻"), "新闻"))
六、 综合实战:Bing 图片自动爬取工具
目标: 自动搜索关键词,滚动加载图片,提取标题和描述并保存。
功能亮点:
使用显式等待确保页面加载。
模拟滚动条触发 Ajax 加载。
解析隐藏在 HTML 属性中的 JSON 数据。
多格式数据保存 (JSON/CSV/TXT)。
完整代码:
"""
Bing 图片爬虫 - Selenium 实战
功能:自动搜索并获取图片描述信息
"""
import os
import time
import json
import csv
from datetime import datetime
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager
class BingImageCrawler:
def __init__(self, headless=False):
self.data_dir = "bing_images_data"
if not os.path.exists(self.data_dir):
os.makedirs(self.data_dir)
options = webdriver.ChromeOptions()
if headless:
options.add_argument('--headless')
options.add_argument('--window-size=1920,1080')
service = Service(ChromeDriverManager().install())
self.driver = webdriver.Chrome(service=service, options=options)
self.wait = WebDriverWait(self.driver, 10)
def search_images(self, keyword, max_images=10):
url = f"https://cn.bing.com/images/search?q={keyword}"
self.driver.get(url)
# 等待搜索框出现,确认页面加载
self.wait.until(EC.presence_of_element_located((By.ID, "sb_form_q")))
# 滚动加载更多图片
self._scroll_to_load_images(max_images)
# 解析数据
return self._parse_images(max_images)
def _scroll_to_load_images(self, max_images):
images_count = 0
while images_count < max_images:
# 查找当前所有图片块
image_elements = self.driver.find_elements(By.CSS_SELECTOR, '.iusc')
current_count = len(image_elements)
if current_count >= max_images:
break
# 滚动到底部
self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(1.5) # 等待加载动画
# 尝试点击"加载更多"按钮
try:
load_more = self.driver.find_element(By.CLASS_NAME, 'btn_seemore')
if load_more.is_displayed():
load_more.click()
time.sleep(2)
except:
pass # 没有按钮则忽略
def _parse_images(self, max_count):
images_data = []
seen_urls = set() # 去重
image_elements = self.driver.find_elements(By.CSS_SELECTOR, '.iusc')[:max_count]
for img_element in image_elements:
try:
# Bing 图片信息通常隐藏在 'm' 属性的 JSON 字符串中
m_value = img_element.get_attribute('m')
if m_value:
img_info = json.loads(m_value)
image_url = img_info.get('murl', '')
# 跳过空URL和重复URL
if not image_url or image_url in seen_urls:
continue
seen_urls.add(image_url)
image_data = {
'标题': img_info.get('t', '无标题'),
'描述': img_info.get('desc', '无描述'),
'图片URL': image_url,
'来源网站': img_info.get('purl', ''),
'采集时间': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
images_data.append(image_data)
except Exception as e:
continue
return images_data
def save_data(self, images_data, keyword):
if not images_data:
return
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
base_filename = f"{keyword}_{timestamp}"
# 1. 保存 JSON
json_file = os.path.join(self.data_dir, f"{base_filename}.json")
with open(json_file, 'w', encoding='utf-8') as f:
json.dump(images_data, f, ensure_ascii=False, indent=2)
# 2. 保存 CSV
csv_file = os.path.join(self.data_dir, f"{base_filename}.csv")
with open(csv_file, 'w', encoding='utf-8', newline='') as f:
writer = csv.DictWriter(f, fieldnames=images_data[0].keys())
writer.writeheader()
writer.writerows(images_data)
print(f"成功保存 {len(images_data)} 条数据到 {self.data_dir}")
def close(self):
self.driver.quit()
def main():
crawler = BingImageCrawler(headless=False) # 新手设为False可见操作
try:
keyword = "Python" # 修改关键词
max_images = 20 # 修改数量
data = crawler.search_images(keyword, max_images)
crawler.save_data(data, keyword)
except Exception as e:
print(f"运行出错: {e}")
finally:
input("按回车键关闭浏览器...")
crawler.close()
if __name__ == "__main__":
main()
七、 常见异常与避坑指南
NoSuchElementException: 元素不存在。
解决: 检查定位方式是否正确,是否使用了显式等待。
ElementNotInteractableException: 元素不可操作(如被遮挡、不可见)。
解决: 使用 element_to_be_clickable 等待,或使用 execute_script("arguments[0].click();", element) 执行 JS 点击。
TimeoutException: 超时。
解决: 增加等待时间,或检查网络/网址是否正确。
StaleElementReferenceException: 元素过期(DOM结构已变化)。
解决: 重新定位元素,不要复用旧的元素对象。
八、 总结
本文带你从基础控制到核心定位,再到实战项目,完整掌握了 Selenium 的使用脉络。
定位是基础:优先使用 ID 和 Name,复杂场景用 CSS 或 XPath。
等待是关键:坚决摒弃 time.sleep(),拥抱 WebDriverWait。
实战出真知:通过爬虫项目,你已经具备了处理大部分动态网页的能力。

请先 登录后发表评论 ~