Skip to content

Latest commit

 

History

History
280 lines (219 loc) · 14.8 KB

File metadata and controls

280 lines (219 loc) · 14.8 KB

Using Selenium to Capture Dynamic Web Content

According to the global Internet accessibility audit report released by authoritative organizations, about three quarters of websites in the world have content or part of their content generated dynamically through JavaScript. This means that when we use "View Page Source" in the browser window, we cannot find these contents in the HTML code, and the way we used before to get data cannot work normally anymore. There are basically two solutions to this problem. One is to get the data interface that provides the dynamic content. This way is also suitable for getting data from mobile apps. The other is to run a browser through the automated testing tool Selenium and get the rendered dynamic content. For the first solution, we can use the browser's Developer Tools or more professional packet-capture tools, such as Charles, Fiddler, or Wireshark, to get the data interface. The later operations are the same as the way talked about in the previous lesson for getting the data of the 360 Image website, so we will not repeat them here. In this chapter, we mainly explain how to use the automated testing tool Selenium to get the dynamic content of websites.

Introduction to Selenium

Selenium is an automated testing tool. By using it, we can drive the browser to carry out specific actions, and finally help crawler developers get the dynamic content of web pages. Simply speaking, as long as we can see the content in the browser window, Selenium can get it. For those websites that use JavaScript dynamic rendering technology, Selenium is an important choice. Below, we still use the Chrome browser as an example to explain the use of Selenium. Everyone needs to first install Chrome and download its driver. Chrome's driver program can be downloaded from the ChromeDriver official website. The version of the driver needs to match the version of the browser. If there is no exactly matching version, choose the one whose version number is closest.

Using Selenium

We can first install Selenium through pip, and the command is shown below.

pip install selenium

Loading Pages

Next, we use the code below to drive Chrome to open Baidu.

from selenium import webdriver

# Create a Chrome browser object
browser = webdriver.Chrome()
# Load the specified page
browser.get('https://www.baidu.com/')

If you do not want to use Chrome, you can also change the code above to control other browsers. You only need to create the matching browser object, such as Firefox or Safari. If you run the program above and see the error message shown below, it means we have not yet added the Chrome browser driver to the PATH environment variable, and we also did not specify the location of the Chrome browser driver in the program.

selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executable needs to be in PATH. Please see https://sites.google.com/a/chromium.org/chromedriver/home

There are three ways to solve this problem:

  1. Put the downloaded ChromeDriver into a path that is already under the PATH environment variable. It is recommended to directly put it in the same directory as the Python interpreter, because when Python was installed before, we already put the path of the Python interpreter into the PATH environment variable.

  2. Put ChromeDriver into the bin folder under the project virtual environment, and the matching directory on Windows is Scripts. In this way, ChromeDriver is in the same place as the Python interpreter under the virtual environment, and it can definitely be found.

  3. Modify the code above. When creating the Chrome object, configure a Service object through the service parameter, and specify the location of ChromeDriver through the executable_path parameter when creating the Service object, as shown below.

    from selenium import webdriver
    from selenium.webdriver.chrome.service import Service
    
    browser = webdriver.Chrome(service=Service(executable_path='venv/bin/chromedriver'))
    browser.get('https://www.baidu.com/')

Finding Elements and Simulating User Behavior

Next, we can try to simulate the user typing a search keyword into the text box on the Baidu home page and clicking the "Baidu Search" button. After the page is loaded, we can get page elements through the find_element and find_elements methods of the Chrome object. Selenium supports many ways of getting elements, including CSS selectors, XPath, element name, meaning tag name, element ID, class name, and so on. The former gets a single page element, a WebElement object, and the latter gets a list made up of multiple page elements. After getting a WebElement object, we can simulate user input behavior through send_keys, and simulate user click operations through click. The code is shown below.

from selenium import webdriver
from selenium.webdriver.common.by import By

browser = webdriver.Chrome()
browser.get('https://www.baidu.com/')
# Get an element through its ID
kw_input = browser.find_element(By.ID, 'kw')
# Simulate user input behavior
kw_input.send_keys('Python')
# Get an element through a CSS selector
su_button = browser.find_element(By.CSS_SELECTOR, '#su')
# Simulate user click behavior
su_button.click()

If you need to perform a series of actions, such as simulating drag-and-drop, you can create an ActionChains object. Interested readers can study it by themselves.

Implicit Wait and Explicit Wait

There is one more detail everyone needs to know here. Elements on a page may be generated dynamically. When we use the find_element or find_elements methods to get them, rendering may not be finished yet, and then a NoSuchElementException error is raised. To solve this problem, we can use implicit wait by setting a waiting time so the browser can finish rendering page elements. Besides that, we can also use explicit wait. By creating a WebDriverWait object and setting the waiting time and conditions, if the conditions are not met, we can wait first and then try later operations. The specific code is shown below.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait

browser = webdriver.Chrome()
# Set browser window size
browser.set_window_size(1200, 800)
browser.get('https://www.baidu.com/')
# Set the implicit waiting time to 10 seconds
browser.implicitly_wait(10)
kw_input = browser.find_element(By.ID, 'kw')
kw_input.send_keys('Python')
su_button = browser.find_element(By.CSS_SELECTOR, '#su')
su_button.click()
# Create an explicit wait object
wait_obj = WebDriverWait(browser, 10)
# Set the waiting condition, wait for the search result div to appear
wait_obj.until(
    expected_conditions.presence_of_element_located(
        (By.CSS_SELECTOR, '#content_left')
    )
)
# Take a screenshot
browser.get_screenshot_as_file('python_result.png')

The waiting condition set above, presence_of_element_located, means waiting for the specified element to appear. The table below lists common waiting conditions and their meanings.

Waiting Condition Meaning
title_is / title_contains The title is the specified content / the title contains the specified content
visibility_of The element is visible
presence_of_element_located The located element has finished loading
visibility_of_element_located The located element becomes visible
invisibility_of_element_located The located element becomes invisible
presence_of_all_elements_located All located elements have finished loading
text_to_be_present_in_element The element contains the specified content
text_to_be_present_in_element_value The value attribute of the element contains the specified content
frame_to_be_available_and_switch_to_it Load and switch to the specified inner window
element_to_be_clickable The element can be clicked
element_to_be_selected The element is selected
element_located_to_be_selected The located element is selected
alert_is_present An alert popup appears

Running JavaScript Code

For a page that uses waterfall loading, if you want to load more content in the browser window, you can do it by running JavaScript code through the execute_scripts method of the browser object. Similar operations may also be needed in some more advanced crawling actions. If your crawler code needs JavaScript support, it is recommended to learn JavaScript properly first, especially the BOM and DOM operations in JavaScript. If we add the code below before taking the screenshot in the code above, then JavaScript can be used to scroll the page to the very bottom.

# Run JavaScript code
browser.execute_script('document.documentElement.scrollTop = document.documentElement.scrollHeight')

Cracking Selenium Anti-Crawler Checks

Some websites set anti-crawler measures specifically for Selenium, because in a browser driven by Selenium, the value of the webdriver property shown in the console is true, as shown below. If you want to bypass this check, you can first change it to undefined by running JavaScript code before loading the page.

On the other hand, we can also hide the text "Chrome is being controlled by automated test software" on the browser window. The full code is shown below.

# Create a Chrome options object
options = webdriver.ChromeOptions()
# Add experimental parameters
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_experimental_option('useAutomationExtension', False)
# Create a Chrome browser object and pass in the parameters
browser = webdriver.Chrome(options=options)
# Run a Chrome DevTools Protocol command
# execute the specified JavaScript code when the page is loaded
browser.execute_cdp_cmd(
    'Page.addScriptToEvaluateOnNewDocument',
    {'source': 'Object.defineProperty(navigator, "webdriver", {get: () => undefined})'}
)
browser.set_window_size(1200, 800)
browser.get('https://www.baidu.com/')

Headless Browsers

Many times, when we are collecting data, we do not need to see the browser window. As long as there is Chrome and its matching driver program, our crawler can run. If you do not want to see the browser window, we can set a headless browser in the way below.

options = webdriver.ChromeOptions()
options.add_argument('--headless')
browser = webdriver.Chrome(options=options)

API Reference

There is still much more Selenium knowledge, and we will not repeat it one by one here. Below, we list some common properties and methods of browser objects and WebElement objects. For the exact content, everyone can also refer to the Chinese translation of the Selenium official documentation.

Browser Objects

Table 1. Common properties

Property Name Description
current_url URL of the current page
current_window_handle Handle, meaning reference, of the current window
name Name of the browser
orientation Direction of the current device, landscape or portrait
page_source Source code of the current page, including dynamic content
title Title of the current page
window_handles Handles of all windows opened by the browser

Table 2. Common methods

Method Name Description
back / forward Go backward / forward in browser history
close / quit Close the current browser window / exit the browser instance
get Load the page of the specified URL into the browser
maximize_window Maximize the browser window
refresh Refresh the current page
set_page_load_timeout Set the page loading timeout
set_script_timeout Set the JavaScript execution timeout
implicit_wait Set waiting for elements to be found or target actions to finish
get_cookie / get_cookies Get the specified cookie / get all cookies
add_cookie Add cookie information
delete_cookie / delete_all_cookies Delete the specified cookie / delete all cookies
find_element / find_elements Find a single element / find a series of elements

WebElement Objects

Table 1. Common properties of WebElement

Property Name Description
location Position of the element
size Size of the element
text Text content of the element
id ID of the element
tag_name Tag name of the element

Table 2. Common methods

Method Name Description
clear Clear the content in a text box or text area
click Click the element
get_attribute Get the attribute value of the element
is_displayed Judge whether the element is visible to the user
is_enabled Judge whether the element is usable
is_selected Judge whether the element, meaning a radio button or checkbox, is selected
send_keys Simulate text input
submit Submit a form
value_of_css_property Get the specified CSS property value
find_element / find_elements Get one child element / get a series of child elements
screenshot Generate a snapshot for the element

Simple Example

The example below shows how to use Selenium to search and download images from the 360 Image website.

import os
import time
from concurrent.futures import ThreadPoolExecutor

import requests
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

DOWNLOAD_PATH = 'images/'


def download_picture(picture_url: str):
    """
    Download and save the picture
    :param picture_url: URL of the picture
    """
    filename = picture_url[picture_url.rfind('/') + 1:]
    resp = requests.get(picture_url)
    with open(os.path.join(DOWNLOAD_PATH, filename), 'wb') as file:
        file.write(resp.content)


if not os.path.exists(DOWNLOAD_PATH):
    os.makedirs(DOWNLOAD_PATH)
browser = webdriver.Chrome()
browser.get('https://image.so.com/z?ch=beauty')
browser.implicitly_wait(10)
kw_input = browser.find_element(By.CSS_SELECTOR, 'input[name=q]')
kw_input.send_keys('Cang Laoshi')
kw_input.send_keys(Keys.ENTER)
for _ in range(10):
    browser.execute_script(
        'document.documentElement.scrollTop = document.documentElement.scrollHeight'
    )
    time.sleep(1)
imgs = browser.find_elements(By.CSS_SELECTOR, 'div.waterfall img')
with ThreadPoolExecutor(max_workers=32) as pool:
    for img in imgs:
        pic_url = img.get_attribute('src')
        pool.submit(download_picture, pic_url)

Run the code above, and check whether the images found by the keyword search have been downloaded under the specified directory.