动态渲染页面不止Ajax一种,有些页面是直接由Javascript计算后生成的,并不包含Ajax请求。还有的页面虽然是通过Ajax获取的数据,但是Ajax接口含有很多加密参数,我们很难直接找出其中的规律将数据抓取下来。为解决这个问题,就直接使用模拟浏览器运行的方式,只要浏览器中能够显示出来的都能够抓取到。
selenum相当于一个机器人,可以执行点击、下拉等操作。下面通过它来打开百度页面:
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://baidu.com')
还可以获取网页源代码:
print(driver.page_source)
close()方法和quit()方法分别可以关闭当前页面和整个浏览器:
from selenium import webdriver
import time
driver = webdriver.Chrome()
driver.get('https://baidu.com')
driver.get('https://www.taobao.com')
time.sleep(1)
driver.close()#关闭当前页面
time.sleep(1)
driver.quit()#关闭整个浏览器
然后再来看一个例子:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
browser = webdriver.Chrome()
try:
browser.get('https://www.baidu.com')
input = browser.find_element_by_id('kw')
input.send_keys('Python')#搜索关键字为Python
input.send_keys(Keys.ENTER)
wait = WebDriverWait(browser,10)
wait.until(EC.presence_of_all_elements_located((By.ID,'content_left')))
print(browser.current_url)
print('*'*300)
print(browser.get_cookies())
print('*' * 300)
print(browser.page_source)
finally:
browser.close()
输出结果是当前的url,Cookies和网页源代码,拿到的都是浏览器中呈现的,因此Selenium可以直接拿到Javascript的渲染结果。
通过以下方法可以定位我们想要的元素:
find_element_by_id:根据id查找某个元素
find_element_by_class_name:根据类名查找元素
find_element_by_tag_name:根据标签名查找元素
find_element_by_xpath:根据xpath语法来获取元素
find_element_by_css_selector:根据CSS选择器选择元素
下面来看一个例子:
现在我们想得到搜索框,可以通过id、name等方式:
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://baidu.com')
inputTag = driver.find_element_by_id('kw')#获取搜索框
inputTag.send_keys('python')#向搜索框发送python
inputTag = driver.find_element_by_name('wd')
inputTag = driver.find_element_by_class_name('s_ipt')
inputTag = driver.find_element_by_xpath("//input[@id='kw']")
获取网页源代码之后就可以用xpath进行解析:
from selenium import webdriver
from lxml import etree
from selenium.webdriver.common.keys import Keys
driver = webdriver.Chrome()
driver.get('https://baidu.com')
inputTag = driver.find_element_by_id('kw')#获取搜索框
inputTag.send_keys('python')#向搜索框发送python
inputTag.send_keys(Keys.ENTER)
source =driver.page_source
html = etree.HTML(source)
print(etree.tostring(html,encoding='utf-8').decode('utf-8'))
操作表单元素:
表单元素:input标签:看type='text'或'password'或者'email'、'number'就表示文本框。
button input[type='submit']表示按钮。
checkbox:input[type='checkbox']
select:下拉列表
使用clear()方法可以清除输入框中的内容:inputTag.clear()
操作checkbox:因为要选中checkbox标签,在网页中是通过鼠标点击的,因此想要选中checkbox标签就要先选中这个标签,然后执行click事件:Tag.click()
查找结点
Selenium可以驱动浏览器完成像填充表单、模拟点击等各种操作,比如我们想要完成向某个输入框输入文字的操作,总需要知道这个输入框在哪里吧?而Selenium提供了一系列查找节点的方法,我们可以用这些方法来获得想要的节点,然后可以执行一些当作或者获取某些信息。
单个节点
比如我们想要从淘宝页面中提取搜索框这个节点,先来查看它的源代码查找它的搜索框:
发现它的id和name都是q,因此我们就通过这种方式获取它,通过find_element()方法,传入查找方式By和值也可以,比如find_element(By.ID,id)就相当于find_element_id(id):
from selenium import webdriver
from selenium.webdriver.common.by import By
browser = webdriver.Chrome()
browser.get('https://www.taobao.com')
input_first = browser.find_element(By.ID,'q')
print(input_first)
多个节点
有时候我们需要查找满足条件的所有节点,就需要用到find_elements()方法,比如要查找淘宝页面中主题市场中的所有条目:
from selenium import webdriver
from selenium.webdriver.common.by import By
browser = webdriver.Chrome()
browser.get('https://www.taobao.com')
lis = browser.find_elements(By.CSS_SELECTOR,'.service-bd li')
for li in lis:
print(li)
browser.close()
它将所有满足条件的li标签返回,结果如下:
<selenium.webdriver.remote.webelement.WebElement (session="59fc32ac4d359eb362126e3cffca7852", element="0.4544509519611133-1")>
<selenium.webdriver.remote.webelement.WebElement (session="59fc32ac4d359eb362126e3cffca7852", element="0.4544509519611133-2")>
...
<selenium.webdriver.remote.webelement.WebElement (session="59fc32ac4d359eb362126e3cffca7852", element="0.4544509519611133-1")>
每个节点都是Element类型的。
同样find_elements_by_id,find_elements_by_class_name,find_elements_by_tag_name,find_elements_by_xpath,find_elements_by_css_selector也能够匹配多个节点。
节点交互
Selenium可以让浏览器执行一些动作,比如输入文字可以用:send_keys()方法,清空文字可以用clear()方法,点击按钮可以用click()方法。先来举个例子:
from selenium import webdriver
import time
browser = webdriver.Chrome()
browser.get('https://www.taobao.com')
input = browser.find_element_by_id('q')
input.send_keys('书包')
time.sleep(1)
input.clear()
input.send_keys('笔记本')
button = browser.find_element_by_class_name('btn-search')
button.click()
动作链
有时候需要做一连串的动作,这时候就需要用到动作连,先来看一个例子:
from selenium import webdriver
from selenium.webdriver import ActionChains
browser = webdriver.Chrome()
url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
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)#声明ActionChains对象
actions.drag_and_drop(source,target)#执行拖曳操作
actions.perform()
它还可以模拟执行JavaScript,使用excute_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('alter("To Bottom")')#将进度条下拉到最底部,然后弹出alter提示框
使用page_source属性可以获取网页源代码接着就可以对其进行解析了,但是Selenium也提供了一些方法和属性来直接提取节点信息。使用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('class'))
每个WebElement节点都有text属性,直接调用这个属性就可以得到节点内部的文本信息:
from selenium import webdriver
from selenium.webdriver import ActionChains
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)#将其文本值打印出来
WebElement节点还有一些其它属性,比如id属性可以获取节点id,location属性可以获取该节点在页面中的相对位置,tag_name属性可以获取标签名称,size属性可以获取节点大小:
from selenium import webdriver
from selenium.webdriver import ActionChains
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)
Selenium打开页面以后默认是在父级Frame里面操作的,而此时如果页面中还有子Frame,它是不能获取到子Frame里面的节点的,需要用switch_to.fram()方法来切换Frame:
from selenium import webdriver
import time
from selenium.common.exceptions import NoSuchElementException
browser = webdriver.Chrome()
url = 'https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
browser.get(url)
browser.switch_to.frame('iframeResult')#切换到子frame里面
#尝试获取父级frame里的logo节点
try:
logo = browser.find_element_by_class_name('logo')
except NoSuchElementException:
print('no logo')
browser.switch_to.parent_frame()#切换到父级frame,再次重新获取节点
logo = browser.find_element_by_class_name('logo')
print(logo)
print(logo.text)
Selenium中的get()方法会在网页框架加载结束后结束执行,此时如果获取page_source,可能并不是浏览器完全加载完成的页面,如果某些页面有额外的Ajax请求,我们在网页源代码中也不能成功获取到,所以需要延时等待一定时间,确保节点加载完毕,有隐式等待和显示等待两种等待方式。
当使用隐式等待执行测试的时候,如果Selenium没有在DOM中找到节点,将继续等待,超出设定时间后,则抛出找不到节点的异常,也就是说当查找结点而节点并没有立即出现的时候就等待一段3时间再查找DOM:
from selenium import webdriver
browser = webdriver.Chrome()
browser.implicitly_wait(10)#隐式等待10秒
browser.get('https://www.zhihu.com/explore')
input = browser.find_element_by_class_name('zu-top-add-question')
print(input)
隐式等待的效果并不好,因为我们只规定了一个固定时间,而页面的加载时间会受到网络条件的影响,而显示等待的方法只需要指定要查找的节点,然后指定一个最长等待时间,如果在规定时间内加载出了这个节点就返回查找的节点,否则抛出异常:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
browser = webdriver.Chrome()
browser.get('https://www.taobao.com/')
wait = WebDriverWait(browser,10)#指定最长等待时间
#传入等待条件presence_of_element_located,代表节点出现,参数是节点的定位元组ID为q的搜索框
#10秒内如果ID为q的节点成功加载出来就返回该节点
input = wait.until(EC.presence_of_element_located((By.ID,'q')))
#对于按钮,则将等待条件更改为element_to_be_clickable表示可点击,如果10秒内可点击就返回这个按钮节点
button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,'.btn-search')))
print(input,button)
使用forward()方法和back()方法可以实现前进和后退:
from selenium import webdriver
import time
browser = webdriver.Chrome()
browser.get('https://www.taobao.com/')
browser.get('https://www.baidu.com')
browser.get('https://www.python.org')
browser.back()
time.sleep(1)
browser.forward()
browser.close()