【Python】爬虫篇 Selenium运用你熟练到了什么程度?
利用selenium这个库去做爬虫其目的是模仿人的鼠标和键盘操作,爬虫中主要用来解决JavaScript渲染问题。
比如有些代码是用动态的JS写的,传统的爬虫是很难爬取到数据的,而使用selenium去模拟人的鼠标和键盘操作则可以避免这个问题。
但也不算十全十美,问题就是爬虫的速度并不能算很高效。
1、支持的浏览器
Edge、Firefox、Safari,Google、Chrome等等;
2、实际简要的执行过程
Selenium模仿真正用户的操作,如打开浏览器,跳转指定url,查看指定数据等;
① 驱动浏览器
② 根据代码中设定的url,进行跳转并加载内容
③ 当然可以插入点击,输入查询内容等操作
④ 爬取并分析数据
3、安装
用python写爬虫的时候,主要用的是selenium的Webdriver;
#安装selenium库
pip install selenium
#安装对应浏览器驱动
# 我们可以通过下面的方式先看看Selenium.Webdriver支持哪些浏览器
from selenium import webdriver
print(help(webdriver))
适用浏览器:
PACKAGE CONTENTS
android (package) blackberry (package) chrome (package)
common (package) edge (package) firefox (package)
ie (package) opera (package) phantomjs (package)
remote (package) safari (package) support (package) webkitgtk (package)
#这里要说一下比较重要的PhantomJS
#PhantomJS是一个而基于WebKit的服务端JavaScript API
#支持Web而不需要浏览器支持
#其快速、原生支持各种Web标准:Dom处理,CSS选择器,JSON等等
#PhantomJS可以用用于页面自动化、网络监测、网页截屏,以及无界面测试
注意下载版本号一致的WebDriver,否则出现异常,chrome地址栏输入chrome://version/ 查看自己的Chrome版本;
示例:
from selenium import webdriver
# #声明浏览器对象
browser1 = webdriver.Chrome()
browser2 = webdriver.Firefox()
# #访问页面
browser1.get("百度一下,你就知道")
print(browser1.page_source)
#关闭当前窗口
browser1.close()
下方也有分享学习Python常用到的安装资源以及一些语法学习、爬虫项目练习资源,自学Python的朋友有需要的可以下载↓↓
<Python基础学习资料 安装资源+项目练习>
4、Selenium常用操作
(1)请求
调用WebDriver 的 get 方法打开一个链接,WebDriver 将等待,直到页面完全加载完毕(onload 方法执行完毕), 然后才会继续执行get 方法后面的代码。
值得注意的是,如果你的页面使用了大量的Ajax加载, WebDriver可能不知道什么时候页面才会完成加载。
如果你想确保这些页面被完全加载,可以使用Waits操作。
from selenium import webdriver
driver = webdriver.Chrome() # 打开谷歌浏览器
driver.get("https://github.com") # 请求一个页面
driver.close() # 关闭浏览器
这里用 get 方法访问百度,示例:
import time
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.baidu.com')
print(browser.page_source)
time.sleep(10)
browser.close()
运行代码后会弹出 Chrome 浏览器并且自动访问百度,然后控制台会输出百度页面的源代码,10秒后浏览器关闭。
(2)查找元素
Selenium提供了以下方法用来定位一个元素:
- find_element_by_id
- find_element_by_name
- find_element_by_xpath
- find_element_by_link_text
- find_element_by_partial_link_text
- find_element_by_tag_name
- find_element_by_class_name
- find_element_by_css_selector
下列方法用来一次查找多个元素 (返回一个list列表):
- find_elements_by_name
- find_elements_by_xpath
- find_elements_by_link_text
- find_elements_by_partial_link_text
- find_elements_by_tag_name
- find_elements_by_class_name
- find_elements_by_css_selector
除了上面的公共方法,还有两个私有方法 find_element 和 find_elements:
from selenium.webdriver.common.by import By
driver.find_element(By.XPATH, '//button[text()="Some text"]')
driver.find_elements(By.XPATH, '//button')
By 类的一些可用属性:
ID = "id"
XPATH = "xpath"
LINK_TEXT = "link text"
PARTIAL_LINK_TEXT = "partial link text"
NAME = "name"
TAG_NAME = "tag name"
CLASS_NAME = "class name"
CSS_SELECTOR = "css selector"
1)单个元素查找
from selenium import webdriver
browser = webdriver.Chrome()
browser.get("http://www.taobao.com")
input_first = browser.find_element_by_id("q")
input_second = browser.find_element_by_css_selector("#q")
input_third = browser.find_element_by_xpath('//*[@id="q"]')
print(input_first)
print(input_second)
print(input_third)
browser.close()
这里我们通过三种不同的方式去获取响应的元素,第一种是通过id的方式,第二个中是CSS选择器,第三种是xpath选择器。
结果都是相同的:
2)多个元素查找
多个元素和单个元素的区别,find_elements,单个元素是find_element,其他使用上没什么区别;
通过其中的一个例子演示:
from selenium import webdriver
browser = webdriver.Chrome()
browser.get("http://www.taobao.com")
lis = browser.find_elements_by_css_selector('.service-bd li')
print(lis)
browser.close()
这样获得就是一个列表:
上面的方式也是可以通过导入from selenium.webdriver.common.by import By 这种方式实现;
(3)切换 Frame
在很多网页中都有Frame标签,我们爬取数据的时候就涉及到切入到frame中以及切出来的问题;
Selenium 打开页面后,默认是在父级 Frame 里面操作,而此时如果页面中还有子 Frame,Selenium 是不能获取到子 Frame 里面的节点的。
这时需要使用 switch_to.frame 方法来切换 Frame:
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
browser = webdriver.Chrome()
url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
browser.get(url)
browser.switch_to.frame('iframeResult')
logo = browser.find_element_by_class_name('logo')
except NoSuchElementException:
print('NO LOGO')
browser.switch_to.parent_frame()
logo = browser.find_element_by_class_name('logo')
print(logo)
print(logo.text)
首先通过 switch_to.frame 方法切换到子 Frame 里面,然后尝试获取子 Frame 里的 logo 节点(实际上是不能找到的),若找不到,就会抛出 NoSuchElementException 异常。
接下来切换回父级 Frame,然后重新获取节点,则可以成功获取。
NO LOGO
<selenium.webdriver.remote.webelement.WebElement (session="70eb5ef47851fceefb1b7cd9a0c5026e", element="a4000bc2-1bb7-4e88-960d-a61a7b69eb7c")>
RUNOOB.COM
当页面中包含子 Frame 时,如果想获取子 Frame 中的节点,需要先调用 switch_to.frame 方法切换到对应的 Frame,然后再进行操作。
在使用selenium模块进行数据爬取时,会遇到爬取iframe中的内容,可能会因为定位的作用域问题爬取不到数据。
菜鸟教程的运行例子:
会以文本块生成xpath为/html/body/text(),代码如下:
#!/user/bin/
# -*- coding:UTF-8 -*-
# Author:Master
from selenium import webdriver
import time
driver = webdriver.Chrome(executable_path="./chromedriver")
driver.get('https://www.runoob.com/try/runcode.php?filename=HelloWorld&type=python3')
time.sleep(2)
text = driver.find_element_by_xpath('/html/body').text
print(text)
time.sleep(5)
driver.quit()
执行结果
很明显这并不是想要的结果;
当我们打开抓包工具定位到Hello, World!文本的时候会发现,该文本是在一个iframe中。
这样的话我们xpath所定位到的内容则是大的html中的路径,我们需要的内容则是在iframe中的小的html中。
想要解决问题的实质就是改变作用域,通过switch_to.frame(‘id’)方法来改变作用域就可以了。
#!/user/bin/
# -*- coding:UTF-8 -*-
# Author:Master
from selenium import webdriver
import time
driver = webdriver.Chrome(executable_path="./chromedriver")
driver.get('https://www.runoob.com/try/runcode.php?filename=HelloWorld&type=python3')
time.sleep(2)
driver.switch_to.frame('iframeResult')
text = driver.find_element_by_xpath('/html/body').text
print(text)
time.sleep(5)
driver.quit()
运行结果:
(4)在不同的窗口和框架之间移动
对于现在的web应用来说,没有任何frames或者只包含一个window窗口是比较罕见的。
WebDriver 支持在不同的窗口之间移动,只需要调用switch_to_window方法即可:
driver.switch_to.window("windowName")
所有的 driver 将会指向当前窗口,但是怎么知道当前窗口的名字呢,查看打开它的javascript或者连接代码:
<a href="somewhere.html" target="windowName">Click here to open a new window</a>
还可以在switch_to_window()中使用窗口句柄来打开它,也可以使用窗口句柄迭代所有已经打开的窗口:
for handle in driver.window_handles:
driver.switch_to.window(handle)
WebDriver也支持在不同的frame中切换,比如
driver.switch_to.frame("frameName")
driver.switch_to.frame("frameName.0.child") # 切换到frameName的第一个子frame中名称为child的frame
driver.switch_to.default_content() # 返回父frame
element = driver.switch_to.active_element
alert = driver.switch_to.alert # 切换到弹出对话框
driver.switch_to.parent_frame()
(5)等待方式
现在的大多数的Web应用程序都使用Ajax技术;
当一个页面被加载到浏览器时, 该页面内的元素可以在不同的时间点被加载,使得定位元素变得困难。
如果元素不在页面之中,会抛出 ElementNotVisibleException 异常,使用 Waits, 我们可以解决这个问题。
Waits提供了一些操作之间的时间间隔- 主要是定位元素或针对该元素的任何其他操作;
Selenium Webdriver 提供两种类型的Waits - 隐式和显式;
① 显式等待
显式等待是一种条件触发式等待;
直到设置的某一条件达成时才会继续执行,可以设置超时时间,如果超过超时时间元素依然没被加载,就会抛出异常。
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
drive = webdriver.Chrome()
url = 'http://www.baidu.com/'
drive.get(url)
WebDriverWait(self.driver,10).until(EC.presence_of_element_located(By.ID,"LoginForm[username]")) #显示等待
except:
print('%s页面未找到元素'% loc)
以上代码加载 http://www. baidu.com/ ,并定位id为"LoginForm[username]"的元素;
设置超时时间10秒,webDriverWait默认会500ms检测一下元素是否存在。
selenium提供了一些内置的用于显示等待的方法,位于expected_conditions类中:
② 隐式等待
隐式等待是在尝试定位某个元素时,如果没能立刻发现,就等待固定时长。
类似于socket超时,默认设置是0秒,即相当于最长等待时长;
在浏览器界面直观感受是:等待直到网页加载完成(地址栏这个地方不是× 变成如下)时继续执行,网页加载超过设置等待时长才报错。
方法示例:
from selenium import webdriver
drive = webdriver.Chrome()
url = 'http://www.baidu.com/'
#设置最大等待时长 10秒
drive.implicitly_wait(10)
drive.get(url)
user = drive.find_element_by_name("LoginForm[username]")
(6)异常处理
在使用 Selenium 的过程中,难免会遇到一些异常,例如超时、节点未找到等错误,一旦出现此类错误,程序便不会继续运行了。
这里我们可以使用 try except 语句来捕获各种异常;
示例节点未找到的异常:
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.baidu.com/')
browser.find_element_by_id('hello')
输出
NoSuchElementException: no such element: Unable to locate element: {"method":"css selector","selector":"[id="hello"]"}
(Session info: chrome=83.0.4103.116)
此处抛出了 NoSuchElementException 异常,代表节点未找到。
为了防止程序遇到异常而中断,我们需要捕获异常:
from selenium import webdriver
from selenium.common.exceptions import TimeoutException, NoSuchElementException
browser = webdriver.Chrome()
browser.get('https://www.baidu.com/')
except TimeoutException:
print('Time Out')
browser.find_element_by_id('hello')
except NoSuchElementException:
print('No Element')
finally:
browser.close()
输出
No Element
(7)操作Cookies
Selenium 可以方便地对 Cookies 进行操作,例如获取、添加、删除 Cookies 等;
# 打开一个页面
driver.get("http://www.example.com")
# 现在设置Cookies,这个cookie在域名根目录下(”/”)生效
cookie = {"name" : "foo", "value" : "bar"}
driver.add_cookie(cookie)
# 现在获取所有当前URL下可获得的Cookies
driver.get_cookies()
(8)使用selenium爬取动态网页信息
示例:获取网易云音乐
from selenium import webdriver
#打开浏览器
brower = webdriver.Chrome()
url='https://music.163.com/#/discover/toplist'
brower.get(url)
#寻找logo文字
#logo = brower.find_elements_by_class_name('logo')[0]
#print(logo.text)
#一般情况下动态加载的内容都可以找到
#有一种情况就没有
#就是网页内存在网页框架iframe
#需要切换网页的层级
#语法:brower.switch_to.frame(iframe的id或者你提前获取这个对象,放入此处)
#方法一:id
#brower.switch_to.frame('g_iframe')
#方法二:name
#brower.switch_to.frame('contentFrame')
#方法三:提前用变量存iframe
iframe = brower.find_element_by_id('g_iframe')
brower.switch_to.frame(iframe)
#寻找大容器
toplist = brower.find_element_by_id('toplist')
#寻找tbody 通过标签名
tbody = toplist.find_elements_by_tag_name('tbody')[0]
#寻找所有tr
trs = tbody.find_elements_by_tag_name('tr')
dataList = []
for each in trs:
rank = each.find_elements_by_tag_name('td')[0].find_elements_by_class_name('num')[0].text
musicName = each.find_elements_by_tag_name('td')[1].find_elements_by_class_name('txt')[0].\
find_element_by_tag_name('b').get_attribute('title')
#print(musicName)
singer = each.find_elements_by_tag_name('td')[3].find_elements_by_class_name('text')[0].\
get_attribute('title')
#print(singer)
dataList.append([rank,musicName,singer])
#print(dataList)
from openpyxl import Workbook
wb = Workbook()
ws = wb.active