直接使用模拟浏览器运行的方式来实现,这样我们就可以做到浏览器看到是什么样,抓取的源码就是什么样,也就是可见即可爬。这样我们就不用再去管网页内部的 JavaScript 用了什么算法渲染页面,不用管网页后台的 Ajax 接口到底有哪些参数,利用模拟浏览器的方式我们都可以直接获取 JavaScript 渲染的最终结果,只要能在浏览器中看到,我们都能抓取
1.环境配置
使用chrome浏览器,下载跟chrome浏览器版本相匹配的chromedriver.exe,放到当前使用的python环境的Scripts目录下
然后在命令行里执行chromedriver,出现如下画面则说明配置成功
2.声明浏览器对象
Selenium 支持非常多的浏览器,如 Chrome、Firefox、Edge 等,还有手机端的浏览器 Android、BlackBerry 等,另外无界面浏览器 PhantomJS 也同样支持。
使用其他的浏览器也需要做相应的环境配置才行。
可以用如下的方式初始化:
from selenium import webdriver browser = webdriver.Chrome()
# browser = webdriver.Firefox()
# browser = webdriver.Edge()
# browser = webdriver.PhantomJS()
# browser = webdriver.Safari()
3.访问页面
弹出Chrome 浏览器,自动访问了淘宝,然后控制台输出了淘宝页面的源代码,随后浏览器关闭
from selenium import webdriver browser = webdriver.Chrome() browser.get('https://www.taobao.com') print(browser.page_source) browser.close()
4.查找节点
Selenium 可以驱动浏览器完成各种操作,比如填充表单、模拟点击等等,但是前提是需要知道节点所在的位置
4.1单个节点
所有获取单个节点的方法:
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
通用find_element() 方法,它需要传入两个参数,一个是查找的方式 By,另一个就是值,实际上它就是 find_element_by_id() 这种方法的通用函数版本,比如 find_element_by_id(id) 就等价于 find_element(By.ID, id)
4.2多个节点
find_elements_by_id
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_elements() 方法来选择,所以也可以这样来写:lis = browser.find_elements(By.CSS_SELECTOR, '.service-bd li')
from selenium import webdriver browser = webdriver.Chrome() browser.get('https://www.taobao.com') lis = browser.find_elements_by_css_selector('.service-bd li') print(lis) browser.close()
5.节点交互
比较常见的用法有:
输入文字用 send_keys() 方法,
清空文字用 clear() 方法,
按钮点击用 click() 方法
官方文档的交互动作介绍:http://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.remote.webelement
import time from selenium import webdriver browser = webdriver.Chrome() try: browser.get('https://www.taobao.com') input = browser.find_element_by_id('q') input.send_keys('iPhone') time.sleep(1) input.clear() input.send_keys('iPad') button = browser.find_element_by_class_name('btn-search') button.click() finally: browser.close()
6.动作链
还有另外一些操作它是没有特定的执行对象的,比如鼠标拖拽、键盘按键等操作。这些动作用另一种方式来执行,那就是动作链
更多的动作链操作可以参考官方文档的动作链介绍:http://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.common.action_chains
from selenium import webdriver from selenium.webdriver import ActionChains browser = webdriver.Chrome() url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable' try: browser.get(url) browser.switch_to.frame('iframeResult') source = browser.find_element_by_css_selector('#draggable') target = browser.find_element_by_css_selector('#droppable') actions = ActionChains(browser) actions.drag_and_drop(source, target) actions.perform() finally: browser.close()
7.执行JavaScript
对于某些操作,Selenium API 是没有提供的,如下拉进度条等,可以直接模拟运行 JavaScript,使用 execute_script() 方法即可实现
from selenium import webdriver browser = webdriver.Chrome() browser.get('https://www.zhihu.com/explore') browser.execute_script('window.scrollTo(0, document.body.scrollHeight)') browser.execute_script('alert("To Bottom")')
利用了 execute_script() 方法将进度条下拉到最底部,然后弹出 alert 提示框
8.获取节点信息
8.1获取属性
使用 get_attribute() 方法来获取节点的属性,那么这个的前提就是先选中这个节点
from selenium import webdriver from selenium.webdriver import ActionChains browser = webdriver.Chrome() url = 'https://www.zhihu.com/explore' browser.get(url) logo = browser.find_element_by_id('zh-top-link-logo') print(logo) print(logo.get_attribute('href'))
8.2获取文本值
每个 WebEelement 节点都有 text 属性,我们可以通过直接调用这个属性就可以得到节点内部的文本信息了,就相当于 BeautifulSoup 的 get_text() 方法、PyQuery 的 text() 方法
from selenium import webdriver browser = webdriver.Chrome() url = 'https://www.zhihu.com/explore' browser.get(url) input = browser.find_element_by_class_name('zu-top-add-question') print(input.text)
8.3.获取ID、位置、标签名、大小
from selenium import webdriver browser = webdriver.Chrome() url = 'https://www.zhihu.com/explore' browser.get(url) input = browser.find_element_by_class_name('zu-top-add-question') print(input.id) print(input.location) print(input.tag_name) print(input.size)
9.切换Frame
在网页中有这样一种节点叫做 iframe,也就是子Frame,相当于页面的子页面,它的结构和外部网页的结构是完全一致的。
Selenium 打开页面后,它默认是在父级Frame 里面操作,而此时如果页面中还有子 Frame,它是不能获取到子 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') try: 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 异常,异常被捕捉之后就会输出 NO LOGO,接下来我们重新切换回父Frame,然后再次重新获取节点,发现就可以成功获取了。
所以说当页面中包含子 Frame 时,如果我们想获取子Frame 中的节点,需要先调用 switch_to.frame() 方法切换到对应的 Frame,然后再进行操作即可
10.延时等待
在 Selenium 中,get() 方法会在网页框架加载结束之后就结束执行,此时如果获取 page_source 可能并不是浏览器完全加载完成的页面,如果某些页面有额外的 Ajax 请求,我们在网页源代码中也不一定能成功获取到。所以这里我们需要延时等待一定时间确保节点已经加载出来。
在这里等待的方式有两种,一种隐式等待,一种显式等待
10.1 隐式等待
如果 Selenium 没有在DOM 中找到节点,将继续等待,超出设定时间后则抛出找不到节点的异常, 默认的时间是 0
用 implicitly_wait() 方法实现了隐式等待
from selenium import webdriver browser = webdriver.Chrome() browser.implicitly_wait(10) browser.get('https://www.zhihu.com/explore') input = browser.find_element_by_class_name('zu-top-add-question') print(input)
10.2 显式等待
指定好要查找的节点,然后指定一个最长等待时间。如果在规定时间内加载出来了这个节点,那就返回查找的节点,如果到了规定时间依然没有加载出该节点,则会抛出超时异常。
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait browser = webdriver.Chrome() browser.get('https://www.taobao.com/') wait = WebDriverWait(browser, 0.01) input = wait.until(EC.presence_of_element_located((By.ID, 'q'))) button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.btn-search'))) print(input, button)
首先引入了 WebDriverWait 这个对象,指定好最长等待时间,然后调用它的 until() 方法,传入要等待条件 expected_conditions,比如在这里我们传入了 presence_of_element_located 这个条件,就代表节点出现的意思,其参数是节点的定位元组,也就是 ID 为 q 的节点搜索框。所以这样可以做到的效果就是,在 10 秒内如果 ID 为 q 的节点即搜索框成功加载出来了,那就返回该节点,如果超过10 秒还没有加载出来,那就抛出异常。
对于按钮,可以更改一下等待条件,比如改为 element_to_be_clickable,也就是可点击,所以查找按钮时是查找 CSS 选择器为 .btn-search 的按钮,如果 10 秒内它是可点击的也就是成功加载出来了,那就返回这个按钮节点,如果超过 10 秒还不可点击,也就是没有加载出来,那就抛出异常。
所有的加载条件:
等待条件 |
含义 |
title_is |
标题是某内容 |
title_contains |
标题包含某内容 |
presence_of_element_located |
节点加载出,传入定位元组,如(By.ID, 'p') |
visibility_of_element_located |
节点可见,传入定位元组 |
visibility_of |
可见,传入节点对象 |
presence_of_all_elements_located |
所有节点加载出 |
text_to_be_present_in_element |
某个节点文本包含某文字 |
text_to_be_present_in_element_value |
某个节点值包含某文字 |
frame_to_be_available_and_switch_to_it frame |
加载并切换 |
invisibility_of_element_located |
节点不可见 |
element_to_be_clickable |
节点可点击 |
staleness_of |
判断一个节点是否仍在DOM,可判断页面是否已经刷新 |
element_to_be_selected |
节点可选择,传节点对象 |
element_located_to_be_selected |
节点可选择,传入定位元组 |
element_selection_state_to_be |
传入节点对象以及状态,相等返回True,否则返回False |
element_located_selection_state_to_be |
传入定位元组以及状态,相等返回True,否则返回False |
alert_is_present |
是否出现Alert |
更多详细的等待条件的参数及用法介绍可以参考官方文档:http://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.support.expected_conditions
11.前进后退
在Selenium中使用 back() 方法可以后退,forward() 方法可以前进
import time from selenium import webdriver browser = webdriver.Chrome() browser.get('https://www.baidu.com/') browser.get('https://www.taobao.com/') browser.get('https://www.python.org/') browser.back() time.sleep(1) browser.forward() browser.close()
连续访问三个页面,然后调用 back() 方法就可以回到第二个页面,接下来再调用 forward() 方法又可以前进到第三个页面
12.cookies
使用 Selenium 还可以方便地对 Cookies 进行操作,例如获取、添加、删除 Cookies 等等
from selenium import webdriver browser = webdriver.Chrome() browser.get('https://www.zhihu.com/explore') print(browser.get_cookies()) browser.add_cookie({'name': 'name', 'domain': 'www.zhihu.com', 'value': 'germey'}) print(browser.get_cookies()) browser.delete_all_cookies() print(browser.get_cookies())
首先我们访问了知乎,然后加载完成之后,浏览器实际上已经生成了 Cookies 了,我们调用 get_cookies() 方法就可以获取所有的 Cookies,然后我们添加一个 Cookie,传入一个字典,有 name、domain、value 等内容。接下来我们再次获取所有的 Cookies,可以发现结果就多了这一项 Cookie。最后我们调用 delete_all_cookies() 方法,删除所有的 Cookies,再重新获取,结果就为空了。
13.选项卡管理
import time from selenium import webdriver browser = webdriver.Chrome() browser.get('https://www.baidu.com') browser.execute_script('window.open()') print(browser.window_handles) browser.switch_to.window(browser.window_handles[1]) browser.get('https://www.taobao.com') time.sleep(1) browser.switch_to.window(browser.window_handles[0]) browser.get('https://python.org')
我们访问了百度,然后调用了 execute_script() 方法,传入 window.open() 的 JavaScript 语句新开启一个选项卡,然后接下来我们想切换到该选项卡,可以调用 window_handles 属性获取当前开启的所有选项卡,返回的是选项卡的代号列表,要想切换选项卡只需要调用 switch_to.window() 方法,传入选项卡的代号即可。在这里我们将第二个选项卡代号传入,即跳转到了第二个选项卡,然后接下来在第二个选项卡下打开一个新的页面,然后切换回第一个选项卡可以重新调用 switch_to.window() 方法,再执行其他操作即可
14.异常处理
在使用 Selenium 过程中,难免会遇到一些异常,例如超时、节点未找到等错误,一旦出现此类错误,程序便不会继续运行了,所以异常处理在程序中是十分重要的
from selenium import webdriver from selenium.common.exceptions import TimeoutException, NoSuchElementException browser = webdriver.Chrome() try: browser.get('https://www.baidu.com') except TimeoutException: print('Time Out') try: browser.find_element_by_id('hello') except NoSuchElementException: print('No Element') finally: browser.close()
更多的异常可以参考官方文档:http://selenium-python.readthedocs.io/api.html#module-selenium.common.exceptions