Skip to content

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.

You also need to download a browser driver:

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.exe

Linux:

echo 'export PATH=$PATH:/path/to/driver' >> ~/.bash_profile
source ~/.bash_profile

Hard-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 browser

Element 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 selector

Selenium Remote Browser (Grid)

  • 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 NameDescription
selenium/baseBase image containing Java and Selenium Server
selenium/hubHub image for Selenium Grid (used with node-xxx images)
selenium/node-baseBase Node image with a virtual desktop for Grid
selenium/node-chromeGrid Node image with Chrome
selenium/node-firefoxGrid Node image with Firefox
selenium/node-edgeGrid Node image with Edge
selenium/node-chromiumGrid Node image with Chromium
selenium/node-chrome-debugGrid Node image with Chrome + VNC server
selenium/node-firefox-debugGrid Node image with Firefox + VNC server
selenium/standalone-chromeStandalone image with Chrome
selenium/standalone-firefoxStandalone image with Firefox
selenium/standalone-edgeStandalone image with Edge
selenium/standalone-chromiumStandalone image with Chromium
selenium/standalone-chrome-debugStandalone image with Chrome + VNC server
selenium/standalone-firefox-debugStandalone 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 standalone

After 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 6666

Docker Deployment

Start the Hub:

docker run -d --name myhub -p 5555:4444 selenium/hub

Link 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-debug

Link a Firefox Node to the Hub:

docker run -d --name node1 -p 5901:5900 --link myhub:hub selenium/node-firefox-debug

Open 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 ActionChains

If 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
Last updated on