Python无头爬虫Selenium系列(01):像手工一样操作浏览器

2021-09-01 14:44:39 浏览数 (1)

自动化爬虫虽然方便,但希望大家能顾及网站服务器的承受能力,不要高频率访问网站。并且千万不要采集敏感数据!!否则很容易"从入门到入狱"

本系列大部分案例同时采用 selenium 与 pyppeteer 库讲解,并且有 Python 和 C# 2门语言的实现文章,详细请到公众号目录中找到。

前言

学习任何一个库,必须先了解这个库的机制与流程,今天先从一个小例子开始我们的学习之旅。

搜索并采集结果的标题

需求如下:

  • 打开百度搜索主页
  • 在输入框输入搜索内容(比如"爬虫")
  • 点击"百度一下"按钮,进行搜索
  • 把结果页面中的第一页的各个结果的主标题抓取下来

Selenium 的麻烦之处

本系列始终围绕一点开展:"用代码操作浏览器",下面看看整个流程:

  • Python 代码通过 selenium 库,控制"浏览器驱动"程序(一个 exe 文件)
  • "浏览器驱动"程序则发送指令操控"浏览器"

但是,市面上存在各种浏览器,而且就算只是一个厂商的浏览器也有不同的版本。怎么能保证我们的代码只需要写一次,就能控制不同的浏览器?

深入一点的流程图如下:

  • 不同厂商不同版本的浏览器,都需要一个对应版本的"浏览器驱动"

"怎么案例都没开始,就在说 selenium 的不是呢?我到底还学不学?"

他有如下优点:

  • selenium 库已经开发很久,相对来说比较稳定
  • selenium 在各个语言的库都是有 google 开发维护,因此不会出有些问题只在 Python 版本出现
  • selenium 相比 pyppeteer 容易学一些
  • 有一个 selenium-ide 工具,能够把手工操作过程直接转换为 selenium 的代码

缺点:

  • 不同版本不同厂商的浏览器需要不同的驱动程序
  • 无法精细控制请求过程的各种处理,如下
  • 无法在执行网站 js 代码之前,执行自己的 js 代码
  • 无法在登录阶段控制浏览器让人工登录获得 cookies,后续直接请求获得数据

如果你认为无法接受 selenium 的缺点,可以查看 pyppeteer 的相关文章(公众号:数据大宇宙 > py爬虫 > pyppeteer)


获得驱动

现在让我们来开始使用 selenium 解决我们的需求。

首先,使用 pip 安装 selenium

代码语言:javascript复制
!pip install selenium
  • 你可以在 jupyter notebook 的 cell 中执行 "!pip install selenium"
  • 也可以在 cmd 中执行 "pip install selenium"

由于我本机安装了 Google Chrome 浏览器,打开浏览器,看看浏览器的版本:

  • 版本为 78.0.3904.70

接着到相关网站(公众号发送"爬虫")下载对应的驱动:

  • 点击进入浏览器版本号对应的目录
  • 下载 win32 版本压缩包
  • 解压后,里面有一个 chromedriver.exe ,这个就是"浏览器驱动"

万事俱备

看过我的相关教学文章的小伙伴都知道,我很喜欢从语义角度去理解学习一个库。

selenium 本质上是控制浏览器,因此当我们使用它的时候,代码的语义应该与手工操作浏览器的过程大同小异才合理。

首先导入一些包:

代码语言:javascript复制
from selenium import webdriver
import selenium.webdriver.support.wait as WA

下面来看看怎么用代码来描述我们的手工操作。


打开浏览器:

代码语言:javascript复制
wd = webdriver.Chrome()
  • 行1:webdriver.Chrome() ,实例化一个 Chrome 对象,如果你是其他浏览器,那么就要实例化对应浏览器的对象
  • 这代码相当于我们手工启动浏览器一样
  • 但是,代码报错了。他的意思是,他找不到"浏览器驱动"

的确,刚刚我们把驱动下载下来,但是 Python 怎么可能会知道去哪里找到那个驱动程序呢。

我们可以在实例化浏览器对象时,传入一个文件路径,告诉他程序的具体位置:

  • 注意,要传入完整的文件路径
  • 我们也可以直接把"驱动程序"放置在代码所在目录
  • 此时可以看到浏览器被启动,默认开启一个空白页面,并且下方出现一行文字说,"此浏览器被控制"

输入百度搜索的网址

代码语言:javascript复制
wd = webdriver.Chrome()
wd.get('https://www.baidu.com/')
  • 行2:wd.get() ,传入网址即可
  • 注意,每次重复执行 webdriver.Chrome() 都会启动一个新的浏览器

鼠标移到输入框,点击一下,然后输入内容"爬虫"

这里的问题是,怎么用代码表达"鼠标移到输入框,点击一下"?

事实上,selenium 真可以模拟鼠标移动等操作(有些网站的登录验证码需要用鼠标拉动拼图都可以模拟),但是现在的情况我们不应该模拟鼠标,而是根据 html 标签定位即可。

此时我们使用浏览器的"开发者功能",进行定位即可。

由于篇幅关系,本文不详细讲解"开发者功能"的所有操作,详细讲解将放在公众号目录:数据大宇宙 > 爬虫工具 > 系列文章

  • 也可以按快捷键 F12 启动此功能(大部分浏览器都可以)

下面用一个动态图展示操作过程:

  • 点击功能区(右区)的左上角的小标签,开启定位模式
  • 此时鼠标移到页面区(左区),鼠标移到的地方,右区会显示此元素在 html 的位置
  • 我们看到,输入框是一个 input 标签,我们要在代码中告诉 selenium 找到这个 input 标签即可
  • 那么用啥"暗号"表示这个 input 标签呢?有2种常见的方式,css 选择器 或者 xpath
  • selenium 文档中强烈推荐你使用 css 选择器
  • 我们选用 css 选择器,因此,在右区的 input 标签上,按鼠标右键,选 "copy" ,然后选择"copy selector" ,此时已经把"暗号"复制到剪切板上

看看代码:

代码语言:javascript复制
wd = webdriver.Chrome()
wd.get('https://www.baidu.com/')
input_box = wd.find_element_by_css_selector('#kw')
  • 行3:wd.find_element_by_css_selector ,使用 css 选择器找到元素,方法中传入刚刚复制的"暗号"(按 ctor v ,粘贴即可)。注意是字符串,因此要用单引号包围
  • 此时,变量 input_box 则表示输入框

接着,输入内容"爬虫"

代码语言:javascript复制
wd = webdriver.Chrome()
wd.get('https://www.baidu.com/')
input_box = wd.find_element_by_css_selector('#kw')
input_box.send_keys('爬虫')
  • 行4:input_box.send_keys ,往该元素发送按键,这个方法不仅仅能发送键盘的按键,还能往可输入的元素发送文本
  • 此时可以看到,浏览器已经输入了内容"爬虫",并且还可以看到下方已经出现搜索结果(这是因为现在的搜索引擎都提供这种边输入边查询的功能)

我们继续模拟点击输入框右边的"百度一下"这个按钮。

同样用"开发者功能",定位该元素,并复制 css 选择器表达字符串:

代码语言:javascript复制
wd = webdriver.Chrome()
wd.get('https://www.baidu.com/')
# 输入框
input_box = wd.find_element_by_css_selector('#kw')
input_box.send_keys('爬虫')
#百度一下按钮
act_btn = wd.find_element_by_css_selector('#su')
act_btn.click()
  • 行7:用 css 选择器找到按钮
  • 行8:act_btn.click() 方法,对元素模拟点击
  • 现在浏览器显示的页面,就有我们需要的所有的内容

所有结果的主标题:

这个可能对初学者有点难度,因为我们这次需要一次选择多个元素(多个搜索结果的主标题),看看定位到的标签:

  • 每个搜索结果,都是一个 div标签(上图右区下方红框)
  • 而所有的搜索结果的 div,都被包在一个 id='content_left' 的 div 标签里面(上图右区上方红框)

进一步看看我们需要的主标题在哪里:

  • 我们要的数据都在一个 a 标签下
  • 并且这个 a 标签被放在一个 h3 标签里面

那么,现在我们要用 css 选择器表达以下语义:在一个div(id=content_left)里面,h3 标签里面的 a 标签的文本。div 与 h3 之间可能嵌套了多层

得到的选择器表达式如下:

  • div[id=content_left] 表示 div 标签,他的 id 属性为 content_left
  • div 与 h3 之间用空格分开,表示他们是祖孙关系,就是 div 与 h3 之间有其他任意多的其他标签嵌套
  • h3 与 a 之间,用">" 分开,表示父子关系,就是 a 标签就是在 h3 标签包围

调用代码如下:

代码语言:javascript复制
wd = webdriver.Chrome()
wd.get('https://www.baidu.com/')
# 输入框
input_box = wd.find_element_by_css_selector('#kw')
input_box.send_keys('爬虫')
#百度一下按钮
act_btn = wd.find_element_by_css_selector('#su')
act_btn.click()
#
titles = wd.find_elements_by_css_selector('div[id=content_left] h3 > a')
titles = [t.text for t in titles]
titles
  • 行10:wd.find_elements_by_css_selector ,查找符合选择器的多个元素,注意方法名字的单词 elements 是复数的,与 行4 和 行7 的方法是不一样
  • 此时,titles 其实是一个列表,里面全是符合条件的 a 标签,但是我们的目标是 a 标签里面的文本
  • 行11:调用 a 标签的文本属性,获得其文本
  • 但是,你会发现结果啥也没有!!!

代码执行太快了

上面的代码之所以拿不到任何结果,是因为当执行到第10行的代码时,页面上还没有加载任何的结果。

如果是一个人在操作浏览器,那么你应该跟他说:嘿,一直到你看到那些结果,你再去提取主标题啊。

怎么表达"一直到你看到那些结果"?,selenium 有专门用于等待元素出现的机制,代码如下:

代码语言:javascript复制
wd = webdriver.Chrome()
wd.get('https://www.baidu.com/')
# 输入框
input_box = wd.find_element_by_css_selector('#kw')
input_box.send_keys('爬虫')
#百度一下按钮
act_btn = wd.find_element_by_css_selector('#su')
act_btn.click()
# 等待对象
wait = WA.WebDriverWait(wd, timeout=3)
titles =wait.until(lambda w:w.find_elements_by_css_selector('div[id=content_left] h3 > a')) 
titles = [t.text for t in titles]
titles
  • 行10:实例化一个 WebDriverWait 对象,注意在一开始导入包的时候,我们导入了 import selenium.webdriver.support.wait as WA
  • 行11:调用 wait.until 方法,传入 lambda ,selenium 会定时执行里面的方法,直到里面的方法有返回对象
  • 此时可以见到,我们得到了结果
  • 关于更多等待机制的知识点,请关系本系列后续的文章

加上关闭浏览器的控制,完整代码如下:

代码语言:javascript复制
with webdriver.Chrome() as wd:
    wd.get('https://www.baidu.com/')
    # 输入框
    input_box = wd.find_element_by_css_selector('#kw')
    input_box.send_keys('爬虫')
    #百度一下按钮
    act_btn = wd.find_element_by_css_selector('#su')
    act_btn.click()
    # 等待对象
    wait = WA.WebDriverWait(wd, timeout=3)
    titles =wait.until(lambda w:w.find_elements_by_css_selector('div[id=content_left] h3 > a')) 
    titles = [t.text for t in titles]
titles

总结

用代码控制 selenium 基本与人工操作一致,一般的流程:

  • 启动浏览器
  • 定位元素(必要时要等元素出现)
  • 操作元素(点击或其他)
  • 不断进行定位与操作过程,直到出现目标页面,爬取数据即可

0 人点赞