Selenium是浏览器自动化的利器,但很多同学只会基础的元素操作。今天分享一些实用的高级技巧,让你的自动化脚本更稳定、更高效。

基础配置优化

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.firefox.options import Options as FirefoxOptions
from selenium.webdriver.edge.options import Options as EdgeOptions
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import logging

class BrowserConfig:
    """浏览器配置管理器"""
    
    @staticmethod
    def get_chrome_options(headless: bool = False, proxy: str = None) -> Options:
        """获取Chrome配置"""
        options = Options()
        
        # 基础配置
        if headless:
            options.add_argument('--headless=new')
        
        # 反检测配置
        options.add_argument('--disable-blink-features=AutomationControlled')
        options.add_experimental_option('excludeSwitches', ['enable-automation'])
        options.add_experimental_option('useAutomationExtension', False)
        
        # 用户数据配置
        options.add_argument('--disable-dev-shm-usage')
        options.add_argument('--no-sandbox')
        options.add_argument('--disable-gpu')
        options.add_argument('--window-size=1920,1080')
        
        # 语言和时区
        options.add_argument('--lang=zh-CN')
        options.add_argument('--timezone=Asia/Shanghai')
        
        # 代理配置(可选)
        if proxy:
            options.add_argument(f'--proxy-server={proxy}')
        
        # 禁用图片加载(提速)
        # prefs = {"profile.managed_default_content_settings.images": 2}
        # options.add_experimental_option("prefs", prefs)
        
        return options
    
    @staticmethod
    def get_firefox_options(headless: bool = False) -> FirefoxOptions:
        """获取Firefox配置"""
        options = FirefoxOptions()
        
        if headless:
            options.add_argument('--headless')
        
        # 反检测
        options.set_preference('dom.webdriver.enabled', False)
        options.set_preference('useAutomationExtension', False)
        
        return options

class EnhancedDriver:
    """增强型浏览器驱动"""
    
    def __init__(self, browser='chrome', headless=False, implicit_wait=10):
        self.browser = browser.lower()
        self.headless = headless
        self.implicit_wait = implicit_wait
        self.driver = None
        self.wait = None
    
    def __enter__(self):
        self.start()
        return self
    
    def __exit__(self, *args):
        self.quit()
    
    def start(self):
        """启动浏览器"""
        if self.browser == 'chrome':
            service = Service()  # 使用系统的chromedriver
            options = BrowserConfig.get_chrome_options(self.headless)
            self.driver = webdriver.Chrome(service=service, options=options)
        elif self.browser == 'firefox':
            options = BrowserConfig.get_firefox_options(self.headless)
            self.driver = webdriver.Firefox(options=options)
        elif self.browser == 'edge':
            options = EdgeOptions()
            if self.headless:
                options.add_argument('--headless')
            self.driver = webdriver.Edge(options=options)
        
        # 隐式等待
        self.driver.implicitly_wait(self.implicit_wait)
        
        # 显式等待
        self.wait = WebDriverWait(self.driver, 20)
        
        # 反检测脚本
        self._hide_automation()
        
        return self
    
    def _hide_automation(self):
        """隐藏自动化特征"""
        self.driver.execute_script("""
            Object.defineProperty(navigator, 'webdriver', {
                get: () => undefined
            });
        """)
    
    def quit(self):
        """关闭浏览器"""
        if self.driver:
            self.driver.quit()
            self.driver = None

智能等待策略

class SmartWait:
    """智能等待工具"""
    
    def __init__(self, driver, timeout=20):
        self.driver = driver
        self.timeout = timeout
        self.wait = WebDriverWait(driver, timeout)
    
    def element_visible(self, locator, by='css'):
        """等待元素可见"""
        from selenium.webdriver.common.by import By
        by_map = {
            'css': By.CSS_SELECTOR,
            'xpath': By.XPATH,
            'id': By.ID,
            'name': By.NAME,
            'class': By.CLASS_NAME,
            'tag': By.TAG_NAME
        }
        return self.wait.until(
            EC.visibility_of_element_located((by_map.get(by, By.CSS_SELECTOR), locator))
        )
    
    def elements_visible(self, locator, by='css'):
        """等待多个元素可见"""
        from selenium.webdriver.common.by import By
        by_map = {
            'css': By.CSS_SELECTOR,
            'xpath': By.XPATH,
            'id': By.ID,
            'name': By.NAME,
            'class': By.CLASS_NAME,
        }
        return self.wait.until(
            EC.presence_of_all_elements_located((by_map.get(by, By.CSS_SELECTOR), locator))
        )
    
    def element_clickable(self, locator, by='css'):
        """等待元素可点击"""
        from selenium.webdriver.common.by import By
        by_map = {
            'css': By.CSS_SELECTOR,
            'xpath': By.XPATH,
            'id': By.ID,
        }
        return self.wait.until(
            EC.element_to_be_clickable((by_map.get(by, By.CSS_SELECTOR), locator))
        )
    
    def text_in_element(self, locator, text, by='css'):
        """等待元素包含指定文本"""
        from selenium.webdriver.common.by import By
        by_map = {
            'css': By.CSS_SELECTOR,
            'xpath': By.XPATH,
            'id': By.ID,
        }
        return self.wait.until(
            EC.text_to_be_present_in_element((by_map.get(by, By.CSS_SELECTOR), locator), text)
        )

# 自定义等待条件
from selenium.webdriver.support.expected_conditions import _find_element

class element_has_class:
    """等待元素包含特定class"""
    def __init__(self, locator, class_name):
        self.locator = locator
        self.class_name = class_name
    
    def __call__(self, driver):
        element = _find_element(driver, self.locator)
        return self.class_name in element.get_attribute('class')

class page_loaded:
    """等待页面完全加载"""
    def __init__(self, timeout):
        self.timeout = timeout
    
    def __call__(self, driver):
        state = driver.execute_script('return document.readyState')
        return state == 'complete'

截图与页面分析

import base64
from datetime import datetime
from pathlib import Path

class PageCapture:
    """页面截图与分析"""
    
    def __init__(self, driver):
        self.driver = driver
        self.screenshot_dir = Path('screenshots')
        self.screenshot_dir.mkdir(exist_ok=True)
    
    def save_screenshot(self, name: str = None) -> str:
        """保存截图"""
        if name is None:
            name = datetime.now().strftime('%Y%m%d_%H%M%S')
        
        filepath = self.screenshot_dir / f'{name}.png'
        self.driver.save_screenshot(str(filepath))
        return str(filepath)
    
    def save_element_screenshot(self, locator, name: str = None) -> str:
        """保存元素截图"""
        from selenium.webdriver.common.by import By
        
        if isinstance(locator, str):
            element = self.driver.find_element(By.CSS_SELECTOR, locator)
        else:
            element = locator
        
        # 截图整个页面
        full_ss = self.driver.get_screenshot_as_base64()
        
        # 获取元素位置和大小
        location = element.location
        size = element.size
        
        # 裁剪元素区域(需要Pillow)
        import io
        from PIL import Image
        
        img = Image.open(io.BytesIO(base64.b64decode(full_ss)))
        left = location['x']
        top = location['y']
        right = location['x'] + size['width']
        bottom = location['y'] + size['height']
        
        cropped = img.crop((left, top, right, bottom))
        
        if name is None:
            name = datetime.now().strftime('%Y%m%d_%H%M%S')
        
        filepath = self.screenshot_dir / f'{name}.png'
        cropped.save(filepath)
        return str(filepath)
    
    def get_page_source(self, pretty: bool = False) -> str:
        """获取页面源码"""
        if pretty:
            try:
                from bs4 import BeautifulSoup
                return BeautifulSoup(self.driver.page_source, 'html.parser').prettify()
            except ImportError:
                pass
        return self.driver.page_source
    
    def analyze_page(self) -> dict:
        """分析页面结构"""
        return {
            'title': self.driver.title,
            'url': self.driver.current_url,
            'forms': len(self.driver.find_elements(By.TAG_NAME, 'form')),
            'inputs': len(self.driver.find_elements(By.TAG_NAME, 'input')),
            'buttons': len(self.driver.find_elements(By.TAG_NAME, 'button')),
            'links': len(self.driver.find_elements(By.TAG_NAME, 'a')),
            'images': len(self.driver.find_elements(By.TAG_NAME, 'img')),
            'iframes': len(self.driver.find_elements(By.TAG_NAME, 'iframe')),
        }

复杂交互操作

from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
import time

class AdvancedInteractions:
    """高级交互操作"""
    
    def __init__(self, driver):
        self.driver = driver
        self.actions = ActionChains(driver)
    
    def hover_and_click(self, hover_locator, click_locator):
        """悬停后点击"""
        from selenium.webdriver.common.by import By
        
        hover = self.driver.find_element(By.CSS_SELECTOR, hover_locator)
        click_target = self.driver.find_element(By.CSS_SELECTOR, click_locator)
        
        self.actions.move_to_element(hover)
        self.actions.pause(0.5)  # 等待动画
        self.actions.click(click_target)
        self.actions.perform()
    
    def double_click(self, locator):
        """双击"""
        from selenium.webdriver.common.by import By
        element = self.driver.find_element(By.CSS_SELECTOR, locator)
        self.actions.double_click(element).perform()
    
    def right_click(self, locator):
        """右键点击"""
        from selenium.webdriver.common.by import By
        element = self.driver.find_element(By.CSS_SELECTOR, locator)
        self.actions.context_click(element).perform()
    
    def drag_and_drop(self, source_locator, target_locator):
        """拖拽"""
        from selenium.webdriver.common.by import By
        source = self.driver.find_element(By.CSS_SELECTOR, source_locator)
        target = self.driver.find_element(By.CSS_SELECTOR, target_locator)
        self.actions.drag_and_drop(source, target).perform()
    
    def select_dropdown(self, locator, value, by='value'):
        """下拉框选择"""
        from selenium.webdriver.common.by import By
        element = self.driver.find_element(By.CSS_SELECTOR, locator)
        select = Select(element)
        
        if by == 'value':
            select.select_by_value(value)
        elif by == 'text':
            select.select_by_visible_text(value)
        elif by == 'index':
            select.select_by_index(value)
    
    def upload_file(self, locator, filepath):
        """文件上传"""
        from selenium.webdriver.common.by import By
        element = self.driver.find_element(By.CSS_SELECTOR, locator)
        element.send_keys(str(filepath))
    
    def execute_script(self, script, *args):
        """执行JavaScript"""
        return self.driver.execute_script(script, *args)
    
    def scroll_to_element(self, locator):
        """滚动到元素"""
        from selenium.webdriver.common.by import By
        element = self.driver.find_element(By.CSS_SELECTOR, locator)
        self.driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", element)
    
    def smooth_scroll(self, pixels: int):
        """平滑滚动"""
        self.driver.execute_script(f"""
            (function scroll() {{
                const step = {pixels / 10};
                let scrolled = 0;
                const timer = setInterval(() => {{
                    window.scrollBy(0, step);
                    scrolled += step;
                    if (scrolled >= {pixels}) clearInterval(timer);
                }}, 30);
            }})();
        """)

实际使用示例

if __name__ == '__main__':
    with EnhancedDriver(browser='chrome', headless=False) as driver:
        wait = SmartWait(driver.driver)
        capture = PageCapture(driver.driver)
        
        # 打开网页
        driver.driver.get('https://www.example.com')
        
        # 等待页面加载
        wait.page_loaded()
        capture.save_screenshot('page_loaded')
        
        # 智能等待元素
        search_box = wait.element_visible('#search-input')
        search_box.send_keys('Selenium tutorial')
        
        # 点击搜索按钮
        search_btn = wait.element_clickable('#search-button')
        search_btn.click()
        
        # 等待结果
        time.sleep(2)
        capture.save_screenshot('search_results')
        
        # 分析页面
        analysis = capture.analyze_page()
        print(f"页面分析: {analysis}")
        
        # 获取页面源码
        source = capture.get_page_source(pretty=True)
        print(f"页面大小: {len(source)} 字符")

总结

Selenium高级技巧要点:

  1. 反检测配置:让自动化更像真人操作
  2. 智能等待:避免不稳定的时间等待
  3. 截图存档:便于问题排查和记录
  4. 复杂交互:拖拽、悬停、下拉框处理
  5. JavaScript桥接:处理特殊场景

更多推荐