Selenium 是一个 Web 应用的自动化框架。
通过它,我们可以写出自动化程序,像人一样在浏览器里操作web界面。 比如点击界面按钮,在文本框中输入文字 等操作,而且还能从web界面获取信息。
从上图可以看出:
编写的自动化程序 需要使用 客户端库,程序的自动化请求都是通过这个库里面的编程接口发送给浏览器。比如,要模拟用户点击界面按钮, 自动化程序里面就应该 调用客户端库相应的函数, 就会发送 点击元素 的请求给 下方的 浏览器驱动。 然后,浏览器驱动再转发这个请求给浏览器(自动化程序发送给浏览器驱动的请求 是HTTP请求)
浏览器驱动 也是一个独立的程序,是由浏览器厂商提供的, 不同的浏览器需要不同的浏览器驱动。 比如 Chrome浏览器和 火狐浏览器有 各自不同的驱动程序。
浏览器驱动接收到我们的自动化程序发送的界面操作请求后,会转发请求给浏览器, 让浏览器去执行对应的自动化操作。浏览器执行完操作后,会将自动化的结果返回给浏览器驱动, 浏览器驱动再通过HTTP响应的消息返回给我们的自动化程序的客户端库。自动化程序的客户端库 接收到响应后,将结果转化为 数据对象 返回给 我们的代码。
总而言之,selenium 自动化流程如下:
pip install selenium
浏览器驱动 是和 浏览器对应的。 不同的浏览器 需要选择不同的浏览器驱动,以谷歌浏览器为例:
Chrome 浏览器驱动下载地址:https://chromedriver.storage.googleapis.com/index.html
from selenium import webdriver
# 创建 WebDriver 对象,指明使用chrome浏览器驱动
wd = webdriver.Chrome(r'd:\webdrivers\chromedriver.exe')
# 调用WebDriver 对象的get方法 可以让浏览器打开指定网址
wd.get('https://www.baidu.com')
将浏览器驱动目录加入到环境变量Path中:
如上代码所示,创建WebDriver对象时,需要指定浏览器驱动路径,但如果将浏览器驱动 所在目录加入环境变量Path,再写代码的时候,就可以无需指定浏览器驱动路径,如:
wd = webdriver.Chrome()
因为,Selenium会自动在环境变量 Path 指定的那些目录里查找名为chromedriver.exe 的文件
一定要注意的是, 加入环境变量 Path 的,不是浏览器驱动全路径,比如 d:\webdrivers\chromedriver.exe
而是 浏览器驱动所在目录,比如 d:\webdrivers
执行脚本出现如图所示字样,则证明环境配置成功,可以开始编写代码了
上面是win10系统环境变量配置,如果是Mac,请移步mac 下 selenium无法启动浏览器
id 可以想象成元素的编号, 用来在html中标记该元素的。 根据规范, 如果元素有id属性 ,这个id 必须是当前html中唯一的,所以如果有id,根据id选择元素是最简单的方式,如下图所示,可以看到 百度搜素框 元素的id为 kw
所以如下代码示例:
from selenium import webdriver
# 创建 WebDriver 对象,指明使用chrome浏览器驱动
wd = webdriver.Chrome(r'd:\webdrivers\chromedriver.exe')
# 调用WebDriver 对象的get方法 可以让浏览器打开指定网址
wd.get('https://www.baidu.com')
# 根据id选择元素,返回的就是该元素对应的WebElement对象
element = wd.find_element_by_id('kw')
# 通过该 WebElement对象,就可以对页面元素进行操作了
# 比如输入字符串到 这个 输入框里
element.send_keys('python\n')
【注意】
send_keys 方法按照字面意思 即 可以在对应的元素中 输入字符串
click 方法按照字面意思 即 可以 点击 该元素
就像一个 学生张三 可以定义类型为 中国人 或者 学生一样, 中国人 和 学生 都是 张三 的 类型。元素也有类型, class 属性就用来标志着元素 类型
【注意】
find_elements_by_class_name 方法返回的是找到的符合条件的 所有 元素 (这里有3个元素), 放在一个 列表 中返回,如果没有符合条件的元素, 返回空列表
而如果我们使用 find_element_by_class_name (注意少了一个s) 方法, 就只会返回 第一个 元素,如果没有符合条件的元素,会抛出NoSuchElementException 异常
<body>
<div class="plant"><span>土豆</span></div>
<div class="plant"><span>西红柿</span></div>
<div class="plant"><span>南瓜</span></div>
<div class="animal"><span>大狮子</span></div>
<div class="animal"><span>大老虎</span></div>
<div class="animal"><span>花蝴蝶</span></div>
</body>
以上面所示页面为例,通过WebElement 对象的 text属性 可以获取该元素在网页中的文本内容,如下面代码所示的for循环,会输出所有动物的名称
from selenium import webdriver
# 创建 WebDriver 实例对象,指明使用chrome浏览器驱动
wd = webdriver.Chrome(r'd:\webdrivers\chromedriver.exe')
# WebDriver 实例对象的get方法 可以让浏览器打开指定网址
wd.get('http://xxxxx.html')
# 根据 class name 选择元素,返回的是 一个列表
# 里面 都是class 属性值为 animal的元素对应的 WebElement对象
elements = wd.find_elements_by_class_name('animal')
# 取出列表中的每个 WebElement对象,打印出其text属性的值
# text属性就是该 WebElement对象对应的元素在网页中的文本内容
for element in elements:
print(element.text)
【注意】
元素也可以有 多个class类型 ,多个class类型的值之间用 空格 隔开,比如
<span class="chinese student">张三</span>
这里 span元素 有两个class属性,分别 是 chinese 和 student, 而不是一个 名为 chinese student 的属性,如果要用代码选择这个元素,可以指定任意一个class 属性值,都可以选择到这个元素,如下
element = wd.find_elements_by_class_name('chinese')
或者
element = wd.find_elements_by_class_name('student')
而不能这样写
element = wd.find_elements_by_class_name('chinese student')~
可以通过方法 find_elements_by_tag_name ,选择所有的tag名为 div的元素,如下所示
from selenium import webdriver
wd = webdriver.Chrome(r'd:\webdrivers\chromedriver.exe')
wd.get('http://xxxxx.html')
# 根据 tag name 选择元素,返回的是 一个列表
# 里面 都是 tag 名为 div 的元素对应的 WebElement对象
elements = wd.find_elements_by_tag_name('div')
# 取出列表中的每个 WebElement对象,打印出其text属性的值
# text属性就是该 WebElement对象对应的元素在网页中的文本内容
for element in elements:
print(element.text)
from selenium import webdriver
from selenium.webdriver.common.by import By
wd = webdriver.Chrome()
wd.get('http://www.baidu.com')
# 选择百度首页【新闻】链接点击 By.LINK_TEXT
element = wd.find_element(By.LINK_TEXT, '新闻')
element.click()
# 或者 By.PARTIAL_LINK_TEXT 不完全匹配选择新闻链接
element = wd.find_element(By.PARTIAL_LINK_TEXT, '新').click()
WebDriver对象有 选择元素 的方法, WebElement对象 也有选择元素的方法,不同的是WebDriver 对象 选择元素的范围是 整个 web页面;WebElement 对象 选择元素的范围是 该元素的内部
from selenium import webdriver
wd = webdriver.Chrome(r'd:\webdrivers\chromedriver.exe')
wd.get('http://xxxx.html')
element = wd.find_element_by_id('container')
# 限制 选择元素的范围是 id 为 container 元素的内部。
spans = element.find_elements_by_tag_name('span')
for span in spans:
print(span.text)
元素定位不到,可能是因为:
(由于我也是初学者,其他的也不是很了解,也没有遇到,所以这次先解决第一个原因造成的元素定位不到问题,其他后续有时间补充)在进行网页操作的时候, 有的元素内容不是可以立即出现的, 可能会等待一段时间。比如 使用百度搜索一个关键词, 我们点击搜索后, 浏览器需要把这个搜索请求发送给百度服务器, 百度服务器进行处理后,再把搜索结果返回。所以,从点击搜索到得到结果,需要一定的时间;通常来讲,代码的执行速度比百度服务器响应的速度快,所以经常直接运行会出现找不到元素的报错…
解决办法:设置sleep时间,但是时间设置为多少是个问题,假如一个自动化程序里面需要10次等待, 就要花费 200秒。 而可能大部分时间, 服务器返回数据都是很快的,根本不需要等20秒, 这样就造成了大量的时间浪费了;
Selenium 中提供了一种方法,当发现元素没有找到的时候, 并不 立即返回 找不到元素的错误,而是周期性(每隔半秒钟)重新寻找该元素,直到该元素找到,或者超出指定最大等待时长,这时才 抛出异常(如果是 find_elements 之类的方法, 则是返回空列表)
Selenium 的 Webdriver 对象 有个方法叫 implicitly_wait,该方法接受一个参数, 用来指定 最大等待时长。
如下代码所示,后续所有的 find_element 或者 find_elements 之类的方法调用 都会采用上面的策略:如果找不到元素, 每隔 半秒钟 再去界面上查看一次, 直到找到该元素, 或者 过了10秒 最大时长
from selenium import webdriver
wd = webdriver.Chrome()
# 设置最大等待时长为 10秒
wd.implicitly_wait(10)
wd.get('https://www.baidu.com')
element = wd.find_element_by_id('kw')
element.send_keys('python111\n')
element = wd.find_element_by_id('1')
print (element.text)
操控元素通常包括
调用元素WebElement对象的 click方法
element = wd.find_element_by_id("input1") # 找到输入框
element.clear() # 清除输入框已有的字符串
element.send_keys('python') # 输入新字符串
# 通过WebElement对象的 text 属性,可以获取元素 展示在界面上的 文本内容
element = wd.find_element_by_id('animal')
print(element.text)
【注意】 通过WebElement对象的 text 属性,可以获取元素 展示在界面上的 文本内容,但是,有时元素的文本没有展示在界面上,这时候使用text就会出问题,可以尝试使用
element.get_attribute('innerText') ,或者 element.get_attribute('textContent')
# 通过WebElement对象的 get_attribute 方法来获取元素的属性值,
# 比如要获取元素属性class的值,就可以使用 element.get_attribute('class') 如下:
element = wd.find_element_by_id('input_name')
print(element.get_attribute('class'))
要获取整个元素对应的HTML文本内容,可以使用 element.get_attribute(‘outerHTML’)
如果,只是想获取某个元素 内部 的HTML文本内容,可以使用 element.get_attribute(‘innerHTML’)
对于input输入框的元素,要获取里面的输入文本,用text属性是不行的,这时可以使用
element.get_attribute('value')
element = wd.find_element_by_id("input1")
print(element.get_attribute('value')) # 获取输入框中的文本
无论从语法还是性能上,CSS是有优势的,一般情况下定位速度比XPath快,语法比XPath简洁(但是XPath初学者很喜欢,毕竟简单,但是不推荐,后期不好维护)
# 通过 CSS Selector 选择单个元素的方法是
find_element_by_css_selector(CSS Selector参数)
# 选择所有元素的方法是
find_elements_by_css_selector(CSS Selector参数)
要选择 所有的tag名为div的元素:
elements = wd.find_elements_by_css_selector('div')
等价于
elements = wd.find_elements_by_tag_name('div')
根据id属性 选择元素的语法是在id号前面加上一个井号: #id值
# 例如,百度的输入框
<input type="text" class="s_ipt" name="wd" id="kw" />
就可以使用 #kw这样的 CSS Selector 来选择它。比如,我们想在 id 为 kw 的输入框中输入文本 你好 ,完整的Python代码如下
from selenium import webdriver
wd = webdriver.Chrome(r'd:\webdrivers\chromedriver.exe')
wd.get('https://www.baidu.com/')
element = wd.find_element_by_css_selector('#kw')
element.send_keys('你好')
根据class属性 选择元素的语法是在 class 值 前面加上一个点: .class值
要选择所有 class 属性值为 animal的元素 动物 除了这样写
elements = wd.find_elements_by_class_name('animal')
还可以这样写
elements = wd.find_elements_by_css_selector('.animal')
因为是选择 所有符合条件的 ,所以用 find_elements 而不是 find_element
HTML中, 元素 内部可以 包含其他元素, 如下代码所示:
<div id='container'>
<div id='layer1'>
<div id='inner11'>
<span>内层11111111</span>
</div>
<div id='inner12'>
<span>内层12222222</span>
</div>
</div>
<div id='layer2'>
<div id='inner21'>
<span>内层2111111111111111111111111111</span>
</div>
</div>
</div>
id 为 container 的div元素 包含了 id 为 layer1 和 layer2 的两个div元素;这种包含是直接包含, 中间没有其他的层次的元素了。 所以 id 为 layer1 和 layer2 的两个div元素 是 id为 container 的div元素 的 直接子元素;
id 为 layer1 的div元素 又包含了 id 为 inner11 和 inner12 的两个div元素,中间没有其他层次的元素,所以这种包含关系也是 直接子元素 关系;
id 为 layer2 的div元素 又包含了 id 为 inner21 这个div元素, 这种包含关系也是 直接子元素 关系
而对于 id 为 container 的div元素来说, id 为 inner11 、inner12 、inner22 的元素 和 两个 span类型的元素 都不是 它的直接子元素, 因为中间隔了 几层。虽然不是直接子元素, 但是 它们还是在 container 的内部, 可以称之为它 的 后代元素
后代元素也包括了直接子元素, 比如 id 为 layer1 和 layer2 的两个div元素 也可以说 是 id 为 container 的div元素 的 直接子元素,同时也是后代子元素
语法表示:
如果 元素2 是 元素1 的 直接子元素, CSS Selector 选择子元素的语法是这样的
元素1 > 元素2
【注意】最终选择的元素是 元素2, 并且要求这个 元素2 是 元素1 的直接子元素
也支持更多层级的选择, 比如
元素1 > 元素2 > 元素3 > 元素4
就是选择 元素1 里面的子元素 元素2 里面的子元素 元素3 里面的子元素 元素4 , 最终选择的元素是 元素4
如果 元素2 是 元素1 的 后代元素, CSS Selector 选择后代元素的语法是这样的
元素1 元素2
中间是一个或者多个空格隔开, 最终选择的元素是 元素2 , 并且要求这个 元素2 是 元素1 的后代元素。
也支持更多层级的选择, 比如
元素1 元素2 元素3 元素4
最终选择的元素是 元素4
比如
<a href="http://www.baidu.com">苏ICP备111111111111111号</a>
css 选择器支持通过任何属性来选择元素,语法是用一个方括号 [] 。比如要选择上面的a元素,就可以使用 [href=“http://www.baidu.com”] 。这个表达式的意思是,选择 属性href值为 http://www.baidu.com 的元素。
完整代码如下
from selenium import webdriver
wd = webdriver.Chrome(r'e:\chromedriver.exe')
wd.get('http://www.baidu.com')
# 根据属性选择元素
element = wd.find_element_by_css_selector('[href="https://beian.miit.gov.cn"]')
# 打印出元素对应的html
print(element.get_attribute('outerHTML'))
当然,前面可以加上标签名的限制,比如 div[class=‘caicai’] 表示 选择所有 标签名为div,且class属性值为 caicai 的元素。属性值用单、双引号都可以;
根据属性选择,还可以不指定属性值,比如 [href] , 表示选择 所有 具有 属性名 为href 的元素,不管它们的值是什么;
CSS 还可以选择 属性值 包含 某个字符串 的元素,比如, 要选择a节点,里面的href属性包含了 baidu 字符串,如下示例:
a[href*="baidu"]
还可以 选择 属性值 以某个字符串 开头 的元素,比如, 要选择a节点,里面的href属性以 http 开头 ,如下示例:
a[href^="http"]
还可以 选择 属性值 以某个字符串 结尾 的元素,比如, 要选择a节点,里面的href属性以 .com 结尾 ,如下示例:
a[href$=".com"]
如果一个元素具有多个属性
<div class="misc" ctype="gun">沙漠之鹰</div>
CSS 选择器 可以指定 选择的元素要 同时具有多个属性的限制,像这样
div[class=misc][ctype=gun]
【注意】可以在想好表达式之后在页面上验证一下是否选择到了自己想要的元素,具体步骤:按F12 以后,使用“箭头”找到想要的元素,按“Ctel + F” 出现删选框,输入CSS 表达式,高亮显示的即是选中的结果。
比如, 我们要选择 网页 html 中的元素 版权
<div id='bottom'>
<div class='footer1'>
<span class='copyright'>版权</span>
<span class='date'>发布日期:2021-08-11</span>
</div>
<div class='footer2'>
<span>备案号
<a href="http://www.miitbeian.gov.cn">京ICP11111111111号</a>
</span>
</div>
</div>
# CSS selector 表达式 可以这样表示:
div.footer1 > span.copyright
就是 选择 一个class 属性值为 copyright 的 span 节点, 并且要求其 必须是 class 属性值为 footer1 的 div节点 的子节点
也可以更简单:
.footer1 > .copyright
就是 选择 一个class 属性值为copyright 的节点(不限类型), 并且要求其 必须是 class 属性值为 footer1 的节点的 子节点
当然 这样也是可以的:
.footer1 .copyright
因为子元素同时也是后代元素
同时选择所有class 为 plant 和 class 为 animal 的元素,可使用 逗号 , 隔开,称之为 组选择 ,例如:
.plant , .animal
再比如,我们要同时选择所有 tag 名为 div 的元素 和 id 为 python 的元素,就可以像这样写
div,#BYHY
对应的selenium代码如下
elements = wd.find_elements_by_css_selector('div,#python')
for element in elements:
print(element.text)
例如 , 选择所有 id 为 caicai 里面的 span 和 p 元素
我们是不是应该这样写??
#caicai > span,p
非也,这样写的意思是 选择所有 id 为 caicai 里面的 span 和 所有的 p 元素,只能这样写
#t1 > span , #t1 > p
对应的html如下,关键信息如下
<body>
<div id='t1'>
<h3> 唐诗 </h3>
<span>李白</span>
<p>静夜思</p>
<span>杜甫</span>
<p>春夜喜雨</p>
</div>
<div id='t2'>
<h3> 宋词 </h3>
<span>苏轼</span>
<p>赤壁怀古</p>
<p>明月几时有</p>
<p>江城子·乙卯正月二十日夜记梦</p>
<p>蝶恋花·春景</p>
<span>辛弃疾</span>
<p>京口北固亭怀古</p>
<p>青玉案·元夕</p>
<p>西江月·夜行黄沙道中</p>
</div>
</body>
使用 nth-child,可以指定选择的元素 是父元素的第几个子节点
如上代码示例,我们要选择 唐诗 和宋词 的第一个 作者,也就是说 选择的是 第2个子元素,并且是span类型,所以这样可以这样写 span:nth-child(2) ,
如果不加节点类型限制,直接这样写 :nth-child(2),就是选择所有位置为第2个的所有元素,不管是什么类型
使用 nth-last-child, 选择的是父元素的 倒数第几个子节点 ,比如:p:nth-last-child(1) ,代表选择第倒数第1个子元素,并且是p元素
使用 nth-of-type,可以指定选择的元素 是父元素的第几个 某类型的 子节点
比如,要选择 唐诗 和宋词 的第一个 作者,选择的是 第2个子元素,并且是span类型,所以这样可以这样写 span:nth-child(2) ,
也可以这样写 span:nth-of-type(1),即选择的是 第1个span类型 的子元素
使用 nth-last-of-type, 选择父元素的 倒数第几个某类型 的子节点,比如p:nth-last-of-type(2),代表选择倒数第2个子元素,并且是p元素
nth-child(even),选择的是父元素的 偶数节点,比如 p:nth-child(even)
nth-child(odd),选择的是父元素的 奇数节点,比如 p:nth-child(odd)
nth-of-type(even),选择的是父元素的 某类型偶数节点
nth-of-type(odd),选择的是父元素的 某类型奇数节点
假如要选择 唐诗 和宋词 的第一个 作者,即就是选择 h3 后面紧跟着的兄弟节点 span,这就是一种 相邻兄弟 关系,可以这样表示 h3 + span;
如果要选择是 选择 h3 后面所有的兄弟节点 span,可以这样写 h3 ~ span
其他CSS选择器详见 :https://www.w3school.com.cn/cssref/css_selectors.asp
最简单的办法,不用看下面内容,直接选中元素,右键—>copy ----->copy XPath
但是不推荐,毕竟是根据页面节点的位置查找的,一旦页面发生改动,就需要重新更换XPath内容,维护起来很不方便!(强烈建议不推荐)
绝对路径:即从根节点开始的,到某个节点,每层都依次写下来,每层之间用 / 分隔的表达式,
elements = driver.find_elements_by_xpath("/html/body/div")
相对路径:即 xpath需要前面加 // , 表示从当前节点往下寻找所有的后代元素,不管它在什么位置
‘//’ 符号也可以继续加在后面,比如,要选择 所有的 div 元素里面的 所有的 p 元素 ,不管div 在什么位置,也不管p元素在div下面的什么位置,则可以这样写 //div//p
elements = driver.find_elements_by_xpath("//div//p")
如果使用CSS选择器,对应代码如下
elements = driver.find_elements_by_css_selector("div p")
如果,要选择 所有的 div 元素里面的 直接子节点 p , xpath,就应该这样写了 //div/p
如果使用CSS选择器,则为 div > p
如果要选择所有div节点的所有直接子节点,可以使用表达式 //div/, 是一个通配符,对应任意节点名的元素,等价于CSS选择器 div > *,如下代码所示:
elements = driver.find_elements_by_xpath("//div/*")
for element in elements:
print(element.get_attribute('outerHTML'))
根据属性来选择元素 是通过 这种格式来的 [@属性名='属性值']
注意:
属性名注意前面有个@
属性值一定要用引号, 可以是单引号,也可以是双引号
根据ID属性选择:
选择 id 为 west 的元素,可以这样 //*[@id='west']
根据Class 属性选择:
选择所有 select 元素中 class为 single_choice 的元素,可以这样 //select[@class='single_choice']
如果一个元素class 有多个,比如
<p id="beijing" class='capital huge-city'>
北京
</p>
如果要选 它, 对应的 xpath 就应该是 //p[@class="capital huge-city"]
,不能只写一个属性,像这样//p[@class=“capital”]则不行
根据其他唯一的属性选择:
同样的道理,我们也可以利用其它的属性选择,比如选择 具有multiple属性的所有页面元素 ,可以这样
//*[@multiple]
要选择 style属性值 包含 color 字符串的 页面元素 ,可以这样 //[contains(@style,‘color’)]
要选择 style属性值 以 color 字符串 开头 的 页面元素 ,可以这样 //[starts-with(@style,‘color’)]
要选择 style属性值 以 某个 字符串 结尾 的 页面元素 ,大家可以推测是 //*[ends-with(@style,‘color’)]注意:该语法目前浏览器都不支持
xpath也可以根据次序选择元素。 语法比css更简洁,直接在方括号中使用数字表示次序
比如,要选择 p类型第2个的子元素,就是
//p[2]
注意,选择的是 p类型第2个的子元素 , 不是 第2个子元素,并且是p类型 。
比如,要选取父元素为div 中的 p类型 第2个 子元素
//div/p[2]
也可以选择第2个子元素,不管是什么类型,采用通配符
比如 选择父元素为div的第2个子元素,不管是什么类型
//div/*[2]
当然也可以选取倒数第几个子元素,比如:
选取p类型倒数第1个子元素
//p[last()]
选取p类型倒数第2个子元素
//p[last()-1]
选择父元素为div中p类型倒数第3个子元素
//div/p[last()-2]
xpath还可以选择子元素的次序范围。比如,
选取option类型第1到2个子元素
//option[position()<=2]
或者
//option[position()<3]
选择class属性为multi_choice的前3个子元素
//*[@class='multi_choice']/*[position()<=3]
选择class属性为multi_choice的后3个子元素
//*[@class='multi_choice']/*[position()>=last()-2]
因为 last() 本身代表最后一个元素,last()-1 本身代表倒数第2个元素,last()-2 本身代表倒数第3个元素
组选择
css有组选择,可以同时使用多个表达式,多个表达式选择的结果都是要选择的元素,css 组选择,表达式之间用 逗号 隔开;xpath也有组选择, 是用 竖线 隔开多个表达式
比如,要选所有的option元素 和所有的 h4 元素,可以使用
//option | //h4
等同于CSS选择器
option , h4
比如,要选所有的 class 为 single_choice 和 class 为 multi_choice 的元素,可以使用
//*[@class='single_choice'] | //*[@class='multi_choice']
等同于CSS选择器
.single_choice , .multi_choice
选择父节点
xpath可以选择父节点, 这是css做不到的。某个元素的父节点用 /… 表示
比如,要选择 id 为 china 的节点的父节点,可以这样写
//*[@id='china']/..
当某个元素没有特征可以直接选择,但是它有子节点有特征, 就可以采用这种方法,先选择子节点,再指定父节点。
还可以继续找上层父节点,比如
//*[@id='china']/../../..
兄弟节点选择
xpath也可以选择 后续 兄弟节点,用这样的语法 following-sibling::
比如,要选择 class 为 single_choice 的元素的所有后续兄弟节点
//*[@class='single_choice']/following-sibling::*
等同于CSS选择器
.single_choice ~ *
如果,要选择后续节点中的div节点
//*[@class='single_choice']/following-sibling::div
xpath还可以选择 前面的 兄弟节点,用这样的语法
preceding-sibling::
比如,要选择 class 为 single_choice 的元素的所有前面的兄弟节点
//*[@class='single_choice']/preceding-sibling::*
而CSS选择器目前还没有方法选择前面的 兄弟节点
在网页上操作的时候,点击一个链接 或者 按钮,就会打开一个 新窗口 。例如,打开新浪主页,点击军事, 就会弹出一个新窗口,访问新浪的军事相关网址。
如果用Selenium写自动化程序 在新窗口里面 打开一个新网址, 并且去自动化操作新窗口里面的元素,问题在于即使新窗口打开了,WebDriver对象对应的 还是老窗口,自动化操作也还是在老窗口进行,所以需要切换窗口
可以使用Webdriver对象的 switch_to 属性的 window方法,如下所示:
wd.switch_to.window(handle)
其中,参数handle需要传入什么呢?
WebDriver对象有window_handles 属性,是一个列表对象, 里面包括了当前浏览器里面所有的窗口句柄。所谓句柄,大家可以想象成对应网页窗口的一个ID,也就是唯一的属性,我们可以通过唯一找到他!
代码示例:
for handle in wd.window_handles:
# 先切换到该窗口
wd.switch_to.window(handle)
# 得到该窗口的标题栏字符串,判断是不是我们要操作的那个窗口
if '军事' in wd.title:
# 如果是,那么这时候WebDriver对象就是对应的该该窗口,正好,跳出循环,
break
上面代码的用意就是:
我们依次获取 wd.window_handles 里面的所有 句柄 对象, 并且调用 wd.switch_to.window(handle) 方法,切入到每个窗口,
然后检查里面该窗口对象的属性(可以是标题栏,地址栏),判断是不是我们要操作的那个窗口,如果是,就跳出循环。
同样的,如果我们在新窗口 操作结束后, 还要回到原来的窗口,可以仍然使用上面的方法,依次切入窗口,然后根据 标题栏 之类的属性值判断即可;
还有更省事的方法,因为我们一开始就在 原来的窗口里面,我们知道 进入新窗口操作完后,还要回来,可以事先 保存该老窗口的 句柄,使用如下方法
# mainWindow变量保存当前窗口的句柄
mainWindow = wd.current_window_handle
切换到新窗口操作完后,就可以直接像下面这样,将driver对应的对象返回到原来的窗口
#通过前面保存的老窗口的句柄,自己切换到老窗口
wd.switch_to.window(mainWindow)
采用注入【cookies】方法来解决验证码的处理
首先通过自动化形式打开登录页面,手动登录后,打印出来cookies信息,此时就知道所需要的cookies信息了,如下代码:
def Get_Cookie(wd):
cookies = wd.get_cookies()
print(cookies)
cookiesFile = json.dumps(cookies)
with open(r'D:\pydemo\webauto\cookiesFile.json', 'w') as f:
f.write(cookiesFile)
切换到新窗这样之前的cookies信息就会存在cookies 文件中,下次要去使用就直接读取该文件,如下代码:
with open('cookiesFile.json', 'r') as f:
cookiesInfo = json.loads(f.read())
for cookie in range(0, len(cookiesInfo)):
wd.add_cookie(cookiesInfo[cookie])
wd.refresh()
Cookie的有效期,Cookie的maxAge决定着Cookie的有效期,单位为秒(Second)。Cookie中通过getMaxAge()方法与setMaxAge(int maxAge)方法来读写maxAge属性。
如果maxAge属性为正数,则表示该Cookie会在maxAge秒之后自动失效。浏览器会将maxAge为正数的Cookie持久化,即写到对应的Cookie文件中。无论客户关闭了浏览器还是电脑,只要还在maxAge秒之前,登录网站时该Cookie仍然有效。下面代码中的Cookie信息将永远有效。
Cookie cookie = new Cookie("username","helloweenvsfei"); // 新建Cookie
cookie.setMaxAge(Integer.MAX_VALUE); // 设置生命周期为MAX_VALUE
response.addCookie(cookie); // 输出到客户端
写在最后:
便于封装的写法
element = wd.find_element(By.ID, 'kw')
username_loc = (By.XPATH, '//*[@id="TANGRAM__PSP_4__userName"]')
``