Selenium
Selenium Automation Framework
Selenium is the standard tool for automating browser interactions. It is particularly useful in web scraping when pages render content dynamically with JavaScript.
- Install:
pip install selenium - Official docs: https://www.selenium.dev/
You also need to download a browser driver:
- ChromeDriver: http://chromedriver.storage.googleapis.com/index.html
- Mirror (China): http://npm.taobao.org/mirrors/chromedriver/
Selenium Driver Management
There are three ways to configure the browser driver.
Driver Manager Package
Most machines update the browser automatically, but not the driver. Third-party libraries can keep the driver in sync with the browser.
# Install the driver manager
pip install webdriver-manager
# Use with Chrome
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
driver = webdriver.Chrome(ChromeDriverManager().install())
# Alternative syntax using Service
service = Service(executable_path=ChromeDriverManager().install())
driver = webdriver.Chrome(service=service)
# Firefox
from selenium import webdriver
from webdriver_manager.firefox import GeckoDriverManager
driver = webdriver.Firefox(executable_path=GeckoDriverManager().install())
# Edge
from selenium import webdriver
from webdriver_manager.microsoft import EdgeChromiumDriverManager
driver = webdriver.Edge(EdgeChromiumDriverManager().install())
# Opera on Linux
from selenium import webdriver
from webdriver_manager.opera import OperaDriverManager
driver = webdriver.Opera(executable_path=OperaDriverManager().install())
# Opera on Windows
from selenium import webdriver
from webdriver_manager.opera import OperaDriverManager
options = webdriver.ChromeOptions()
options.add_argument('allow-elevated-browser')
options.binary_location = "C:\\Users\\USERNAME\\FOLDERLOCATION\\Opera\\VERSION\\opera.exe"
driver = webdriver.Opera(executable_path=OperaDriverManager().install(), options=options)
# Internet Explorer
from selenium import webdriver
from webdriver_manager.microsoft import IEDriverManager
driver = webdriver.Ie(IEDriverManager().install())PATH Environment Variable
Download the driver first, then add it to the system PATH.
Windows:
# See which directories are already on PATH
echo %PATH%
# Add the driver directory to PATH
setx PATH "%PATH%;C:\WebDriver\bin"
# Test that it was added correctly
chromedriver.exeLinux:
echo 'export PATH=$PATH:/path/to/driver' >> ~/.bash_profile
source ~/.bash_profileHard-coded Path
# Download chromedriver first, then specify the path directly
service = Service(executable_path="/path/to/chromedriver")
driver = webdriver.Chrome(service=service)Basic Usage
# Basic example
from selenium import webdriver
from time import sleep
# 1. Instantiate a browser object
bro = webdriver.Chrome(executable_path='./chromedriver.exe')
# 2. Navigate to a URL
bro.get('https://www.jd.com/')
# 3. Locate an element
text_input = bro.find_element_by_xpath('//*[@id="key"]')
# 4. Type text into the element
text_input.send_keys('iphone 12')
sleep(1)
btn = bro.find_element_by_xpath('//*[@id="search"]/div/div[2]/button')
btn.click()
sleep(1)
# 5. Execute JavaScript to scroll to the bottom of the page
bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
sleep(2)
bro.find_element_by_xpath('//*[@id="J_bottomPage"]/span[1]/a[7]').click()
sleep(3)
# Close the browser
bro.quit()Common Selenium Operations
Commonly Used Classes and Methods
import time
from selenium import webdriver # Controls the browser
from selenium.webdriver import ActionChains # Mouse operations (e.g., slider verification)
from selenium.webdriver.common.by import By # Element locator strategies
from selenium.webdriver.common.keys import Keys # Keyboard key constants
from selenium.webdriver.support import expected_conditions as EC # Conditions for waits
from selenium.webdriver.support.wait import WebDriverWait # Explicit wait
from webdriver_manager.chrome import ChromeDriverManager
# Load Chrome driver
driver = webdriver.Chrome(ChromeDriverManager().install())
# Navigate to a URL
driver.get("https://www.baidu.com")
# Maximize the browser window
driver.maximize_window()
print("Current URL: %s" % driver.current_url)
print("Page title: %s" % driver.title)
print("Browser name: %s" % driver.name)
print(driver.current_window_handle) # Get the current window handle
print(driver.get_cookies()) # Get all cookies
print(driver.page_source) # Get the full page HTML source
time.sleep(5)
driver.close() # Close the current tab
driver.quit() # Close the entire browserElement Selectors
from selenium import webdriver # Controls the browser
from selenium.webdriver.support.wait import WebDriverWait # Explicit wait
from webdriver_manager.chrome import ChromeDriverManager
driver = webdriver.Chrome(ChromeDriverManager().install())
wait = WebDriverWait(driver, 10)
driver.get('https://www.baidu.com')
driver.find_element_by_id('su') # ID selector
driver.find_element_by_class_name('xx') # Class selector, single element
driver.find_elements_by_class_name('xx') # Class selector, multiple elements
driver.find_element_by_link_text('xxx') # Link text selector
driver.find_element_by_xpath('xxxx') # XPath selector
driver.find_element_by_tag_name('h1') # Tag name selector, single element
driver.find_elements_by_tag_name('h1') # Tag name selector, multiple elements
driver.find_element_by_css_selector('xxx') # CSS selectorSelenium Remote Browser (Grid)
- Docker Hub images: https://hub.docker.com/u/selenium
- GitHub repository: https://github.com/SeleniumHQ/docker-selenium
- Official docs: http://selenium.dev/documentation/
- Hub: Receives browser automation commands from WebDriver, distributes them to the appropriate Node, and returns the results to WebDriver.
- Node: Receives commands from the Hub and invokes the browser driver to perform page operations.
Hub and Node can run on different machines and communicate over HTTP.
| Image Name | Description |
|---|---|
| selenium/base | Base image containing Java and Selenium Server |
| selenium/hub | Hub image for Selenium Grid (used with node-xxx images) |
| selenium/node-base | Base Node image with a virtual desktop for Grid |
| selenium/node-chrome | Grid Node image with Chrome |
| selenium/node-firefox | Grid Node image with Firefox |
| selenium/node-edge | Grid Node image with Edge |
| selenium/node-chromium | Grid Node image with Chromium |
| selenium/node-chrome-debug | Grid Node image with Chrome + VNC server |
| selenium/node-firefox-debug | Grid Node image with Firefox + VNC server |
| selenium/standalone-chrome | Standalone image with Chrome |
| selenium/standalone-firefox | Standalone image with Firefox |
| selenium/standalone-edge | Standalone image with Edge |
| selenium/standalone-chromium | Standalone image with Chromium |
| selenium/standalone-chrome-debug | Standalone image with Chrome + VNC server |
| selenium/standalone-firefox-debug | Standalone image with Firefox + VNC server |
Starting with the JAR File
The Grid JAR requires Java 11 or later. Standalone mode is the quickest way to get started.
Download the latest selenium-server-<version>.jar from github.com/SeleniumHQ/selenium, then start it:
java -jar selenium-server-<version>.jar standaloneAfter Grid starts, open http://localhost:4444 to see available browsers and session status.
To use Hub + Node mode:
# Start Hub
java -jar selenium-server-<version>.jar hub
# Start Node 1
java -jar selenium-server-<version>.jar node --port 5555
# Start Node 2
java -jar selenium-server-<version>.jar node --port 6666Docker Deployment
Start the Hub:
docker run -d --name myhub -p 5555:4444 selenium/hubLink a Chrome debug Node to the Hub container (--link connects to the container aliased as hub):
docker run -d --name node -p 5902:5900 --link myhub:hub selenium/node-chrome-debugLink a Firefox Node to the Hub:
docker run -d --name node1 -p 5901:5900 --link myhub:hub selenium/node-firefox-debugOpen http://<vm-ip>:5555/grid/console to verify the Grid is running. The containers provide isolation, so you don’t need separate physical machines for each Node.
VNC remote viewing: images ending in -debug include a VNC server. Install a VNC client on your local machine and connect (default password: secret).
Full deployment example:
# 1. Deploy Hub
docker run -d --name tencent-sgp-hub -p 4442-4444:4442-4444 selenium/hub
# 2. Deploy Chrome Node
docker run -d \
--name tencent-sgp-selenium-chrome \
-p 5900:5900 \
-e SE_EVENT_BUS_HOST=172.17.0.2 \
-e SE_EVENT_BUS_PUBLISH_PORT=4442 \
-e SE_EVENT_BUS_SUBSCRIBE_PORT=4443 \
-v /dev/shm:/dev/shm \
--shm-size="2g" \
selenium/node-chrome
# Parameter explanation:
# -d: Run container in detached (background) mode
# --name: Assign a name to the container
# -p 5900:5900: Map host port 5900 to container port 5900 (VNC)
# -e SE_EVENT_BUS_HOST: Event bus host address (points to Hub IP)
# -e SE_EVENT_BUS_PUBLISH_PORT=4442: Event bus publish port (matches Hub config)
# -e SE_EVENT_BUS_SUBSCRIBE_PORT=4443: Event bus subscribe port (matches Hub config)
# -e SE_NODE_MAX_SESSIONS=5: Limit max sessions per node
# -v /dev/shm:/dev/shm: Mount shared memory volume (prevents browser OOM)
# --shm-size="2g": Set container shared memory to 2 GB (required by Chrome/Firefox)
# --restart unless-stopped: Restart policy (restarts automatically unless manually stopped)Kubernetes Deployment
Reference YAML files: https://github.com/kubernetes/examples/tree/master/staging/selenium
Selenium and Web Scraping
- Captures dynamically loaded data (what you see is what you get)
- Can simulate login flows
# Capture dynamically loaded data
from lxml import etree
bro = webdriver.Chrome(executable_path='./chromedriver.exe')
bro.get('http://scxk.nmpa.gov.cn:81/xk/')
sleep(1)
# page_source returns the full page source including dynamically loaded content
page_text = bro.page_source
page_text_list = [page_text] # Store the first 5 pages of source HTML
for i in range(5):
bro.find_element_by_xpath('//*[@id="pageIto_next"]').click()
sleep(1)
page_text_list.append(bro.page_source)
for page_text in page_text_list:
tree = etree.HTML(page_text)
li_list = tree.xpath('//*[@id="gzlist"]/li')
for li in li_list:
title = li.xpath('./dl/@title')[0]
print(title)
bro.quit()Action Chains encapsulate sequences of continuous mouse/keyboard actions:
from selenium.webdriver import ActionChainsIf you try to locate an element inside an <iframe>, you will get an error. Use switch_to to enter the frame first:
bro.switch_to.frame('iframe-id')
bro = webdriver.Chrome(executable_path='./chromedriver.exe')
bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
sleep(1)
bro.switch_to.frame('iframeResult')
div_tag = bro.find_element_by_xpath('//*[@id="draggable"]')
# 1. Instantiate an action chain and associate it with the current browser
action = ActionChains(bro)
# 2. Define the actions
action.click_and_hold(div_tag) # Click and hold
for i in range(5):
action.move_by_offset(7, 5).perform() # perform() executes the action chain immediately
sleep(0.5)
sleep(2)
bro.quit()Handling cookies:
browser = webdriver.Chrome(executable_path='./chromedriver.exe')
browser.get('https://www.zhihu.com/explore')
print(browser.get_cookies())
browser.add_cookie({'name': 'name', 'domain': 'www.zhihu.com', 'value': 'germey'})
print(browser.get_cookies())
browser.delete_all_cookies()
print(browser.get_cookies())Headless browser (Chrome without a visible UI):
from selenium.webdriver.chrome.options import Options
# Create an options object to run Chrome in headless mode
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
browser = webdriver.Chrome(executable_path='./chromedriver.exe', chrome_options=chrome_options)
browser.get('https://www.zhihu.com/explore')
print(browser.page_source)
browser.save_screenshot('./zhihu.jpg') # Take a screenshot
browser.quit()Case Study: Simulating 12306 Login
#!/usr/bin/env python
# coding:utf-8
import requests
from hashlib import md5
class Chaojiying_Client(object):
def __init__(self, username, password, soft_id):
self.username = username
password = password.encode('utf8')
self.password = md5(password).hexdigest()
self.soft_id = soft_id
self.base_params = {
'user': self.username,
'pass2': self.password,
'softid': self.soft_id,
}
self.headers = {
'Connection': 'Keep-Alive',
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
}
def PostPic(self, im, codetype):
"""
im: image bytes
codetype: CAPTCHA type, see http://www.chaojiying.com/price.html
"""
params = {
'codetype': codetype,
}
params.update(self.base_params)
files = {'userfile': ('ccc.jpg', im)}
r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers)
return r.json()
def ReportError(self, im_id):
"""
im_id: image ID of the incorrectly solved CAPTCHA
"""
params = {
'id': im_id,
}
params.update(self.base_params)
r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
return r.json()
# Function that calls the CAPTCHA-solving API
def getCode_text(imgPath, imgType):
chaojiying = Chaojiying_Client('227851369', '123456', ' 911685')
im = open(imgPath, 'rb').read()
return chaojiying.PostPic(im, imgType)['pic_str']
from selenium import webdriver
from time import sleep
from selenium.webdriver import ActionChains
# pip install PIL (Pillow)
from PIL import Image
# Crop the CAPTCHA image — ensure your OS display scaling is set to 100%
bro = webdriver.Chrome(executable_path='./chromedriver.exe')
bro.get('https://kyfw.12306.cn/otn/login/init')
sleep(1)
# Take a screenshot of the full page
bro.save_screenshot('main.png')
# Get the CAPTCHA image element and its position/size
img_tag = bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img')
# Top-left corner coordinates
location = img_tag.location
# CAPTCHA image dimensions
size = img_tag.size
# Define the crop region
rangle = (int(location['x']), int(location['y']), int(location['x']+size['width']), int(location['y']+size['height']))
i = Image.open('./main.png')
frame = i.crop(rangle) # Crop to the defined region
frame.save('code.png')
# Solve the CAPTCHA and get click coordinates
result = getCode_text('code.png', 9004)
print(result) # Format: x1,y1|x2,y2|x3,y3
# Parse result into [[x1,y1],[x2,y2]]
all_list = []
if '|' in result:
list_1 = result.split('|')
count_1 = len(list_1)
for i in range(count_1):
xy_list = []
x = int(list_1[i].split(',')[0])
y = int(list_1[i].split(',')[1])
xy_list.append(x)
xy_list.append(y)
all_list.append(xy_list)
else:
x = int(result.split(',')[0])
y = int(result.split(',')[1])
xy_list = []
xy_list.append(x)
xy_list.append(y)
all_list.append(xy_list)
for loc in all_list:
x = loc[0]
y = loc[1]
ActionChains(bro).move_to_element_with_offset(img_tag, x, y).click().perform()
sleep(1)
bro.find_element_by_id('username').send_keys('1234567890')
sleep(1)
bro.find_element_by_id('password').send_keys('0000000000')
sleep(1)
bro.find_element_by_id('loginSub').click()
sleep(3)
bro.quit()- PhantomJS: A headless browser (now deprecated; use Chrome headless instead)
- Appium: A mobile app automation framework