自动化爬虫虽然方便,但希望大家能顾及网站服务器的承受能力,不要高频率访问网站。并且千万不要采集敏感数据!!否则很容易"从入门到入狱"
本系列大部分案例同时采用 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 基本与人工操作一致,一般的流程:
- 启动浏览器
- 定位元素(必要时要等元素出现)
- 操作元素(点击或其他)
- 不断进行定位与操作过程,直到出现目标页面,爬取数据即可