Python自动化实现Selenium浏览器操作高级技巧
·
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高级技巧要点:
- 反检测配置:让自动化更像真人操作
- 智能等待:避免不稳定的时间等待
- 截图存档:便于问题排查和记录
- 复杂交互:拖拽、悬停、下拉框处理
- JavaScript桥接:处理特殊场景
更多推荐
所有评论(0)