现代网络变得越来越复杂并依赖于 Javascript,这使得传统的网络抓取变得困难。Python 中的传统网络抓取工具无法执行 javascript,这意味着它们难以处理动态网页,而这正是Selenium(一种浏览器自动化工具包)派上用场的地方!
浏览器自动化经常用于网络抓取,以利用浏览器渲染能力来访问动态内容。此外,它通常用于避免网络抓取器阻塞,因为真实的浏览器往往比原始 HTTP 请求更容易融入人群。
我们已经在我们的概述文章如何使用无头 Web 浏览器抓取动态网站中简要介绍了 3 个可用工具 Playwright、Puppeteer 和 Selenium ,在这篇文章中,我们将更深入地了解 Selenium——最流行的浏览器自动化工具包在那里。
在这个 Selenium with Python 教程中,我们将了解什么是 Selenium;其常用功能用于网络抓取动态页面和网络应用程序。我们将介绍一些一般的提示和技巧以及常见的挑战,并通过抓取twitch.tv将其与示例项目一起总结
什么是Selenium?
Selenium最初是一种用于测试网站行为的工具,但它很快成为用于网络抓取和其他自动化任务的通用网络浏览器自动化工具。
这个工具非常普遍,能够通过名为 Selenium webdriver 的中间件控制来自动化不同的浏览器,如 Chrome、Firefox、Opera 甚至 Internet Explorer。
Webdriver 是 W3C 组织设计的第一个浏览器自动化协议,它本质上是一个位于客户端和浏览器之间的中间件协议服务,将客户端命令转换为 Web 浏览器操作。
目前,它是 Web 浏览器自动化的两个可用协议之一(另一个是Chrome Devtools Protocol),虽然它是一个较旧的协议,但它仍然能够并且完全适用于网络抓取 – 让我们来看看它能做什么!
安装Selenium
可以通过命令安装用于 python 的 Selenium webdriver pip
:
$ pip install selenium
然而,我们还需要支持 webdriver 的浏览器。我们推荐 Firefox 和 Chrome 浏览器:
- 用于 Chrome 驱动程序的ChromeDriver
- Geckodriver for Firefox 驱动程序
更多安装说明,参见官方Selenium安装说明
导航、等待和检索
当谈到网页抓取时,我们本质上需要 Selenium API 的一些基本功能:导航到网页、等待元素加载和按钮单击/页面滚动。
探索这些基本功能的最简单方法是在交互式 REPL(例如ipython)
. 请参阅这个快速演示:
为了进一步了解 selenium,让我们从示例项目开始。
我们将从https://www.twitch.tv/艺术部分抓取当前流,用户可以在其中流式传输他们的艺术创作过程。我们将收集动态数据,例如流名称、观看人数和作者。
我们当前的任务是:
- 启动 Chrome 网络浏览器
- 前往https://www.twitch.tv/directory/game/Art
- 等待页面加载完成
- 获取当前浏览器实例的 HTML 内容
- 从 HTML 内容中解析数据
在我们开始之前,让我们安装 Selenium 本身:
$ pip install selenium $ pip show selenium Version: 3.141.0
从我们的爬虫代码开始,让我们创建一个 selenium webdriver 对象并启动 Chrome 浏览器:
from selenium import webdriver driver = webdriver.Chrome() driver.get("https://www.twitch.tv/directory/game/Art")
如果我们运行这个脚本,我们会看到一个浏览器窗口打开,并为我们提供我们的 twitch URL。然而,通常在网络抓取时我们不希望我们的屏幕被所有 GUI 元素占用,为此我们可以使用一种称为无头模式的东西,它会剥离浏览器的所有 GUI 元素并让它在后台静默运行. 在 Selenium 中,我们可以通过关键字参数启用它options
:
from selenium import webdriver from selenium.webdriver.chrome.options import Options ... # configure webdriver options = Options() options.headless = True # hide GUI options.add_argument("--window-size=1920,1080") # set window size to native GUI size options.add_argument("start-maximized") # ensure window is full-screen ... driver = webdriver.Chrome(options=options) # ^^^^^^^^^^^^^^^ driver.get("https://www.twitch.tv/directory/game/Art")
此外,当网络抓取时我们不需要渲染图像,这是一个缓慢而密集的过程。在 Selenium 中,我们可以通过关键字参数指示 Chrome 浏览器跳过图像渲染chrome_options
:
from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.support.ui import WebDriverWait # configure webdriver options = Options() options.headless = True # hide GUI options.add_argument("--window-size=1920,1080") # set window size to native GUI size options.add_argument("start-maximized") # ensure window is full-screen ... # configure chrome browser to not load images and javascript chrome_options = webdriver.ChromeOptions() chrome_options.add_experimental_option( # this will disable image loading "prefs", {"profile.managed_default_content_settings.images": 2} ) ... driver = webdriver.Chrome(options=options, chrome_options=chrome_options) # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ driver.get("https://www.twitch.tv/directory/game/Art") driver.quit()
如果我们将设置options.headless
设置回False
我们会看到加载的所有页面都没有任何媒体图像。它们仍然存在,但没有被下载并嵌入到我们的视口中——为我们节省了大量的资源和时间!
最后,我们可以检索一个完全呈现的页面并开始解析数据。我们的驱动程序能够通过driver.page_source
属性向我们提供当前浏览器窗口的内容(称为页面源),但如果我们过早调用它,我们将得到一个几乎空的页面,因为尚未加载任何内容!
幸运的是,Selenium 有很多检查页面是否加载的方法,但是最可靠的方法是通过 CSS 选择器检查页面中是否存在元素:
from parsel import Selector from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.chrome.options import Options from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time # configure webdriver options = Options() options.headless = True # hide GUI options.add_argument("--window-size=1920,1080") # set window size to native GUI size options.add_argument("start-maximized") # ensure window is full-screen # configure chrome browser to not load images and javascript chrome_options = webdriver.ChromeOptions() chrome_options.add_experimental_option( "prefs", {"profile.managed_default_content_settings.images": 2} ) driver = webdriver.Chrome(options=options, chrome_options=chrome_options) driver.get("https://www.twitch.tv/directory/game/Art") # wait for page to load element = WebDriverWait(driver=driver, timeout=5).until( EC.presence_of_element_located((By.CSS_SELECTOR, 'div[data-target=directory-first-item]')) ) print(driver.page_source)
在这里,我们使用一个特殊的WebDriverWait
对象来阻止我们的程序,直到满足特定条件。在这种情况下,我们的条件是存在我们通过 CSS 选择器选择的元素。
解析动态数据
我们已经启动了一个浏览器,告诉它转到 twitch.tv 并等待页面加载并检索页面内容。有了这些内容,我们就可以完成我们的项目并解析相关的动态数据:
from parsel import Selector sel = Selector(text=driver.page_source) parsed = [] for item in sel.xpath("//div[contains(@class,'tw-tower')]/div[@data-target]"): parsed.append({ 'title': item.css('h3::text').get(), 'url': item.css('.tw-link::attr(href)').get(), 'username': item.css('.tw-link::text').get(), 'tags': item.css('.tw-tag ::text').getall(), 'viewers': ''.join(item.css('.tw-media-card-stat::text').re(r'(\d+)')), })
虽然 selenium 提供了自己的解析功能,但它们低于 python 生态系统中可用的功能。获取呈现页面的 HTML 源代码并使用parsel或beautifulsoup包以更高效和 pythonic 的方式解析此内容的效率要高得多。在此示例中,我们使用parsel通过 XPATH 和 CSS 选择器提取内容。
在本节中,我们介绍了第一个基本的基于 Selenium 的网络抓取工具。我们已经启动了浏览器的优化实例,告诉它转到我们的网页,等待内容加载并返回给我们呈现的文档!
这些基本功能将使您在网络抓取方面走得更远,但是一些边缘情况可能需要更高级的自动化功能,例如元素按钮单击、文本输入和自定义 javascript 执行 – 让我们来看看这些。
高级Selenium函数
Selenium 是一个非常强大的自动化库,其功能远远超过我们通过 twitch.tv 示例发现的功能。
对于初学者来说,有时我们可能需要单击按钮并将文本输入表单以访问我们想要网络抓取的内容。为此,让我们看看如何使用 Twitch.tv 搜索栏。我们将为搜索框和搜索按钮找到 HTML 元素,并将我们的输入发送到那里:
from selenium import webdriver from selenium.webdriver.common.keys import Keys driver = webdriver.Chrome() driver.get("https://www.twitch.tv/") search_box = driver.find_element_by_css_selector('input[aria-label="Search Input"]') search_box.send_keys( 'fast painting' ) # either press the enter key search_box.send_keys(Keys.ENTER) # or click search button search_button = driver.find_element_by_css_selector('button[icon="NavSearch"]') search_button.click()
在上面的示例中,我们使用了 CSS 选择器来查找我们的搜索框并输入一些键。然后,要提交我们的搜索,我们可以选择发送文字 ENTER 键或查找搜索按钮并单击它以提交我们的搜索表单。
最后,网络抓取中使用的最后一个重要特征是javascript 执行。Selenium 本质上为我们提供了一个完整的、运行中的 Javascript 解释器,它允许我们完全控制页面文档和浏览器本身的一大块!
为了说明这一点,让我们看一下滚动。
由于 Twitch 使用所谓的“无限分页”从第二页获取结果,我们必须指示浏览器滚动到底部以触发下一页的加载:
from selenium import webdriver driver = webdriver.Chrome() driver.get("https://www.twitch.tv/directory/game/Art") # find last item and scroll to it driver.execute_script(""" let items=document.querySelectorAll('.tw-tower>div'); items[items.length-1].scrollIntoView(); """)
在此示例中,我们使用 javascript 执行来查找页面中代表视频的所有 Web 元素,然后将视图滚动到最后一个元素,这会告诉页面生成第二页结果。
在 Selenium 控制的 Web 浏览器中滚动内容的方法有很多种,但使用该scrollIntoView()
方法是浏览浏览器视口的最可靠方法之一。
在本节中,我们介绍了用于网络抓取的主要高级 Selenium 功能:键盘输入、按钮单击和 javascript 执行。有了这些完整的知识,我们就可以抓取复杂的 javascript 驱动的网站,例如 twitch.tv!
常问问题
我们在这篇文章中学到了很多东西,让我们将其中的一些内容消化成一个简洁的常见问题列表:
错误:Geckodriver 可执行文件需要在 PATH 中
这个错误通常意味着 geckodriver – Firefox 的渲染引擎 – 没有安装在机器上。您可以查看官方发布页面以获取下载说明或者,我们可以通过更改webdriver 启动中的参数
来使用任何其他 Firefox 实例,例如:executable_path
webdriver.Firefox(executable_path=r'your\path\geckodriver.exe')
如何禁用图像加载?
为了减少使用 Selenium 进行抓取时的带宽使用,我们可以通过首选项禁用图像加载:
chrome_options = webdriver.ChromeOptions() chrome_options.add_experimental_option( # this will disable image loading "prefs", {"profile.managed_default_content_settings.images": 2} )
如何在 Selenium 中截取屏幕截图?
要截取屏幕截图,我们可以使用 webdriver 命令:webdriver.save_screenshot()
和webdriver.get_screenshot_as_file()
. 屏幕截图对于调试无头浏览器工作流程非常有用。
如何在 Selenium 中键入特定的键盘键?
要发送非字符键盘键,我们可以在from selenium.webdriver.common.keys import Keys
常量中使用定义的常量。例如Keys.ENTER
将发送回车键。
如何在 Selenium 中选择下拉值?
要选择下拉值,我们可以利用 Selenium 的 UI 实用程序。from selenium.webdriver.support.ui import Select
对象允许我们选择值并执行各种操作:
from selenium.webdriver.support.ui import Select select = Select(driver.find_element_by_id('dropdown-box')) # select by visible text select.select_by_visible_text('first option') # or by value select.select_by_value('1')
如何将 Selenium 浏览器滚动到特定对象?
可靠地滚动浏览动态页面的最佳方式是使用 javascript 代码执行。例如,要滚动到最后一个产品项目,我们将使用scrollIntoView()
javascript 函数:
driver.execute_script(""" let items=document.querySelectorAll('.product-box .product'); items[items.length-1].scrollIntoView(); """)
如何在 Selenium 中捕获 HTTP 请求?
当 Web 浏览器连接到网页时,它会执行从文档本身到图像和数据请求的许多 HTTP 请求。对于这个selenium-wire python 包,可以使用它通过请求/响应捕获功能扩展 Selenium:
driver.get('https://www.google.com') for request in driver.requests: if request.response: print( request.url, request.response.status_code, request.response.headers['Content-Type'] )
Selenium 可以和 Scrapy 一起使用吗?
Scrapy 是 Python 中流行的网络抓取框架,但是由于不同的体系结构,使 scrapy 和 selenium 一起工作很困难。查看这些开源尝试scrapy-selenium和scrapy-headless。
总结和进一步阅读
在这个带有 Selenium 的简短 Python 教程中,我们了解了如何使用这个 Web 浏览器自动化包进行 Web 抓取。
我们回顾了抓取中使用的大部分常用功能,例如导航、单击按钮、文本输入、等待内容和自定义 javascript 执行。
我们还回顾了一些常见的性能习惯用法,例如无头浏览和禁用图像加载。
这些知识应该可以帮助您开始使用 Selenium 网络抓取。此外,我们建议查看避免机器人检测:如何使用 Javascript 来阻止 Web 抓取工具?