url = 'http://www.baidu.com' # 这里不要用https
resp = requests.get(url) # 请求数据
resp = requests.post(url=url, data=data) # post方式请求数据(只需要加个字典类型的data即可)
resp.encoding = 'utf-8' # 根据html的charset来设定编码方式,可以避免中文乱码
print(resp) # -- 是一个Request对象,包含响应头、html之类的
# ★ 拿到网页源代码(html)
html = resp.text # 拿到网页源代码(html)、数据 --- str类型
html = resp.content # 拿到网页源代码(html)、数据 --- bytes类型 (下载图片可以如此)
参数
url: 获取页面的url链接
params:url中的额外参数,字典或者字节流等形式,可以选择
# params(GET参数)是指 url中的额外参数,字典或者字节流格式,是可选参数。可以让url不那么冗长,使代码看起来更加整齐
params = {
'version'='0.12',
'language'='en',
...
}
resp = requests.get(url, params) / requests.post(url, data ,params)
data:POST参数 --- ⚠ post方法才有
headers:请求头(反爬)
cookies:Cookies
**kwargs:12个控制访问的参数
...
对响应内容的操作
resp = requests.get(url) # 请求数据
resp = requests.post(url) # 请求数据
resp.encoding = 'utf-8' # 设置数据的编码
resp.text # 返回响应的内容(拿到页面源代码(html)、数据)
resp.json() # 如果返回的是json格式,可以直接resp.json()可以正常显示中文
resp.content # 返回响应的内容(拿到页面源代码(html)、数据)(bytes形式)
resp.request.headers # 拿到请求头
resp.headers # 拿到响应头
resp.status_code # 响应代码,200表示成功
为什么要用 Session?
Session代表服务器与浏览器的一次会话过程,Session对象存储了特定用户会话所需的信息
例如:一定时间内记录账号密码 (自动登录)
可以加快 requests请求速度
import requests
url = 'http://www.baidu.com'
resp = requests.Session().request(method='get', url=url)
resp.encoding = 'utf-8'
html = resp.text
print(html)
需要客户端登录的网站
客户端登录 --> 服务器返回 cookie 给客户端
客户端带着 cookie 再去请求数据 --> 服务器知道你是谁
# 需要登录的网站一般可以这样进行登录
resp = requests.post(url,data={
'loginName':' xxx',
'password':' xxx',
})
# 或
resp = requests.post(url,headers={
'Cookie':'xxxxxxxx' # 抓包复制来的
})
print(resp.text)
# 这两种方法 缺点是每次都得传入cookie信息
# ★ 使用 Session 可以记录 cookie 信息
url = 'https://h5.17k.com/'
# 登录信息
data = {
'loginName':'xxx',
'password':'xxx',
}
# 创建会话
session = requests.Session()
# 1. 登录
resp = session.post(url=url,data=data,)
# print(resp.text)
print(resp.cookies)
# 2. 拿我的书架上的数据
# 刚才的那个Session中是有cookie的,还是使用刚才的Session
resp2 = session.get('https://user.17k.com/ck/author/shelf?platform=4&appKey=1351550300')
print(resp2.text)
【★注意】很多 页面源代码 和 开发者工具的Elements页面代码 是存在偏差的
页面源代码是不会改变的
Elements页面代码是实时的网页效果的源代码,很多内容可能是Js渲染出来的内容,在网页源代码中是找不到的!
'''
防盗链
Referer验证 --- 溯源
找当前请求的上一级(上一个页面)是谁,如果找不到,就认为有问题,不会返回需要的相响应内容
'''
import requests
url = 'https://www.pearvideo.com/video_1756772'
contId = url.split('_')[1]
# 抓包,请求视频html相关内容(不在原代码中)
videoStatusUrl = f'https://www.pearvideo.com/videoStatus.jsp?contId={contId}&mrd=0.7303948894191317'
headers={
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36',
# 防盗链:溯源
'Referer':url
}
# 请求视频详情页数据
resp = requests.get(videoStatusUrl,headers=headers)
# print(resp.json()) # 因为是json形式的str,直接转为json好处理
dict = resp.json()
srcUrl = dict['videoInfo']['videos']['srcUrl']
systemTime = dict['systemTime']
# 对视频链接处理(替换、拼接成完整的视频地址)
srcUrl = srcUrl.replace(systemTime,f'cont-{contId}')
# print(srcUrl)
resp2 = requests.get(srcUrl)
# 下载视频
with open('myVidoe.mp4','wb') as f:
f.write(resp2.content)
# 原理:通过第三方的一个机器去发送请求(借用他人ip)
#当需要采集大量数据时,或者有的网站对访问速度特别严格的时候,有的网站就采取封ip,这样就需要使用代理ip。就像马蜂窝一样,,自从被曝数据造假之后,就不好爬了,python使用代理ip的小demo为:
import requests
# ★ 代理ip的使用
# 检测IP地址的网站
url = 'http://httpbin.org/ip'
# 设置代理ip
proxy = {
'http':'http://218.238.83.182:80', # 根据类型选择 http 或 https
'https':'https://172.157.83.198:80',
}
res = requests.get(url,proxies=proxy)
print(res.text)
'''
{
"origin": "218.238.83.182"
}
'''
# ★ 检测代理ip是否能用
ips = [('http://218.238.83.182:80'),('http://219.146.125.162:9091'),('http://112.80.248.73:80'),('http://125.123.121.223:766'),('http://114.100.3.14:3617'),('http://47.92.113.71')]
url = 'http://httpbin.org/ip'
for i in ips:
try:
res = requests.get(url,proxies={'http':i},timeout=1)
print(res.text)
except Exception as e:
print('出现异常',e)
urllib
import urllib
url = 'https://www.baidu.com'
# 一、简单获取一个get请求 (不修改headers)
resp = urllib.request.urlopen(url)
# 二、简单获取一个post请求 (不修改headers)
# Post请求需要向服务器发送表单内容,用urlopen中的data参数
# data 是 bytes 字节型数据,需要用 bytes() 转换成字节型
# 如果data被赋值,则请求的方式就会由get转为post,而post需要提供一些待处理的数据data
# 这些待处理的数据需要一定的格式,因此就需要 urllib.parse.urlencode() 编码
# 【注】不能对string编码,只能对dict类型编码
import urllib.parse
data = bytes(urllib.parse.urlencode({'name': 'Syy'}), encoding='utf-8')
resp = urllib.request.urlopen(url=url, data=data)
# ★ 拿到网页源代码(html)
# 拿到网页源代码(html)、数据 --- str类型
html = resp.read().decode('utf-8') # 以utf-8的方式对获取到的网页源码进行解码,可以更好的解析网页,解析换行符、中文等
html = resp.read() # 拿到网页源代码(html)、数据 --- bytes类型
# 三、拿到请求头的一些属性值
print(resp.status) # 200 (状态码)
# 查看所有属性值(有s)
print(resp.getheaders())
# 查看所有属性值(无s)
print(resp.getheader('Server'))
# 四、伪装成浏览器(将User-Agent由爬虫改为浏览器的信息)
# ① Post 请求
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
}
data = bytes(urllib.parse.urlencode({'name': 'Eric'}), encoding='utf-8')
# ⭕构建(封装)一个请求对象
req = urllib.request.Request(url=url, headers=headers, data=data, method='POST')
resp = urllib.request.urlopen(req)
# ② Get请求
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
}
# 构建(封装)一个请求对象
req = urllib.request.Request(url=url, headers=headers)
resp = urllib.request.urlopen(req)
urllib3
import urllib3
url = 'http://www.baidu.com'
http = urllib3.PoolManager(n) # 创建连接池管理对象,参数可填几个,可以实现向多个服务器发送请求
resp = http.request(method='get',url=url)
resp = http.urlopen(method='get',url=url) # urlopen 跟 request方法效果一样,但无法直接添加headers,因此用的少
print(resp) # 是一个响应对象
# ★ 拿到网页源代码(html)
html = resp.data.decode('utf-8') # 拿到网页源代码(html)、数据 --- str类型
html = resp.data # 拿到网页源代码(html)、数据 --- bytes类型 (下载图片可以如此)
from selenium import webdriver
# (注意:驱动需要与浏览器版本兼容)
# 配置Chrome启动时属性的类ChromeOptions
options = webdriver.ChromeOptions()
options.add_argument('--headless') # 设置 无界面浏览 # 还可以设置UA、IP代理等等
driver = webdriver.Chrome(options=options) # 创建了 Chrome 实例
driver.get('http://www.baidu.com') # 发送请求,请求数据
html = driver.page_source # 拿到网页源代码(html)、数据 --- str类型
正则表达式
'''
正则:用来匹配字符串的一门表达式语言
测试的方法:https://tool.oschina.net/regex/
1.正则支持所有普通字符
2.元字符,就一个符号来匹配一堆内容
\d --能够匹配一个数字(0-9)
\D --匹配除了数字(0-9)的内容
\w --能够匹配数字、字母、下划线(0-9,a-z,A-Z,_)
\W --除了数字、字母、下划线(0-9,a-z,A-Z,_)以外的内容
[abc] -- 匹配字母 a 或 b 或 c 的内容
[^abc] -- 匹配除了字母 a 或 b 或 c 的内容
. -- 匹配 除了换行符以外 的所有内容
3.量词,控制前面元字符出现的频次
+ -- 前面的元字符出现 1 次或多次
* -- 前面的元字符出现 0 次或多次,贪婪匹配的,尽可能多的拿到数据
? -- 前面的元字符出现 0 次或 1 次
4.惰性匹配 : .*?
例如: 玩儿吃鸡游戏,晚上一起打游戏,干嘛呢?打游戏啊!
正则:玩儿.*游戏 .* -- 找最远的"游戏" (即 -- 中间尽可能多)
结果:玩儿吃鸡游戏,晚上一起打游戏,干嘛呢?打游戏
正则:玩儿.*?游戏 .*? -- 找最近的"游戏" (即 -- 中间尽可能少)
结果:玩儿吃鸡游戏
😊.*? : 惰性匹配 xxx.*?xx-- 匹配xxx 到最近的xx为止内容,😊
'''
'''
re.findall(r'',str) # 匹配正则表达式匹配的内容 r''--原始字符串,避免转义字符的影响 返回一个装有匹配结果的列表
re.search(r'',str) # 只拿到第一个结果就返回
re.finditer(r'',str) # 匹配正则表达式匹配的内容,返回的是一个迭代器对象
# findall 和 finditer 的结果效果相同,选择哪个主要看获取的内容大小,如果太大,选择finditer比较合适,占用内存少
# 结合写法:
re.findall(re.compile(r''),str)
# 预加载
# 提前准备好一个正则对象
# re.compile 用于编译正则表达式,生成一个 Pattern 对象
# 此对象可以使用 findall、finditer、search等等各种re方法
obj = re.compile(r'')
obj.search(str)
obj.finditer(str)
obj.findall(str)
(?P<组名>正则表达式) -- ?P表示要分组,<>内为组名
item.group() # 打印当前匹配到的内容(只能用于 re.search和 re.finditer 的结果内容的遍历)
item.group('组名') # 打印当前匹配到的指定组的内容 # 必须在设定了组名的时候才能使用
item.groupdict() # 必须在设定了组名的时候才能使用
'''
# 一、findall()
result = re.findall(r'\d+', '今天我有100块,买了2块蛋糕') # 匹配正则表达式匹配的内容 r''--原始字符串,避免转义字符的影响 返回一个装有匹配结果的列表
print(result) # ['100', '2']
'''
注意:search 和 finditer中,()括起来的内容是分组内容,是要输出的内容,不用括号括起来的内容至会匹配不会输出!
'''
# 二、search()
result = re.search(r'(?P\d+)' , '今天我有100块,买了2块蛋糕') # 只拿到第一个结果就返回
print(result.group()) # 输出所有分组内容,即()括起来的内容
print(result.group('text')) # 分组起名后可以按名称查询
# 三、finditer()
result = re.finditer(r'\d+', '今天我有100块,买了2块蛋糕') # 匹配正则表达式匹配的内容,返回的是一个迭代器对象
for item in result:
print(item.group())
# result = re.finditer(r'(?P\d+)', '今天我有100块,买了2块蛋糕') # 匹配正则表达式匹配的内容,返回的是一个迭代器对象
print(item.group('text')) # 分组起名后可以按名称查询
print(item.groupdict()) # 字典格式打印所有匹配的组
# 四、sub()
# re.sub() 利用正则来实现字符替换
print(re.sub('a','A', 'abcdefghijklmn')) # 找到a用A替换
# 建议在正则表达式中,在被比较的字符串前面加上r,这样不用担心转义字符的问题
print(re.sub('a','A', r'\abcdefgh\ijklmn\l'))
# ★ 例子2:预加载
# 提前准备好一个正则对象
# re.compile 用于编译正则表达式,生成一个 Pattern 对象
obj = re.compile(r'\d+') # 此对象可以使用 findall、finditer、search等等各种re方法
result = obj.findall('今天我有100块,买了2块蛋糕')
obj.search('今天我有100块,买了2块蛋糕')
obj.finditer('今天我有100块,买了2块蛋糕')
print(result)
# 测试正则表达式用于爬虫
html = requests.get(url).text
html = '''
'''
# ★ finditer 方法
obj = re.compile(r'')
result = obj.finditer(html)
for item in result:
print(item.group('url'), end=' ')
print(item.group('text'))
print(item.groupdict()) # 字典格式打印所有匹配的组
'''
baidu.com 我是百度
qq.com 我是腾讯
162.com 我是网易
{'url': '162.com', 'text': '我是网易'}
'''
# ★ findall 方法
obj = re.compile(r'')
result = obj.findall(html) # result是一个列表
print(result[0]) # 可以直接输出
# findall 和 finditer 的结果效果相同,选择哪个主要看获取的内容大小,如果太大,选择finditer比较合适,占用内存少
# ★ search 方法
obj = re.compile(r'')
result = obj.search(html)
print(item.group('url'), end=' ')
print(item.group('text'))
print(item.groupdict()) # 字典格式打印所有匹配的组
漂亮的汤
'''
BeautifulSoup4将复杂html文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:
- Tag
- NavigableString
- BeautifulSoup
- Comment
'''
from bs4 import BeautifulSoup
html = requests.get(url).text
# 解析内容:html,用的解析器:html.parser
bs = BeautifulSoup(html, 'html.parser')
# 1.拿到整个文档(html)
print(bs)
print(type(bs)) #
# 2.Tag --标签及其内容:找到的第一个标签及其内容
# bs.标签名
# 找到第一个出现的标签及其所有内容
print(bs.title) # 百度一下,你就知道
print(bs.a)
# print(bs.div)
print(type(bs.title)) #
# 3.NavigableString --标签里的内容(字符串)
# bs.标签名.string
print(bs.title.string) # 百度一下,你就知道
# 4.拿到一个标签的所有属性
# bs.标签名.attrs
print(bs.a.attrs)
# 5. Comment --注释类型,是一种特殊的NavigatableString,输出的内容不包含注释符号
print(bs.a.string) # 新闻
print(type(bs.a.string)) #
# 解析内容:html,用的解析器:html.parser
bs = BeautifulSoup(html, 'html.parser')
# 【节点获取】
# 一、文档的遍历
# contents -- 返回一个列表,里面装的是标签中的内容
print(bs.div.contents)
print(bs.div.contents[0])
print(bs.div.contents[1])
print(type(bs.div.contents[1])) #
# 二、 文档的搜索 (更重要)
# 1.find(标签,属性=属性值) --查询第一个符合条件的节点
a = bs.find('a',class_='bri')
aa = bs.find('a',attrs={'class':'bri'}) # 同上
print(a.text) # 更多产品
# 拿到某个属性值
print(a.get('href'))
# 2.find_all() --查询所有指定节点,返回的是列表
# ① 字符串过滤:会查找所有与字符串完全匹配的节点内容
t_list = bs.find_all('a') # ★ 查找所有a标签
print(t_list)
# ② 正则表达式
# 正则表达式搜索,使用search()方法来匹配内容
t_list = bs.find_all(re.compile('a')) # ★ 查找所有带有'a'字符的节点
print(t_list)
# ③ 方法:传入一个函数:根据函数的要求来搜索
def name_is_exists(tag):
# 标签中有name属性才会筛选出来(拿到)
return tag.has_attr('name')
t_list = bs.find_all(name_is_exists) # 这里只需要写函数名
print(t_list)
# 3.kwargs 参数
# 指定一些参数搜索:class_、id...
t_list = bs.find_all(id='head')
# html的class属性要加个下划线_,为了和Python的内部class区分开来!
t_list = bs.find_all(class_=True) # 这里是指有class属性的标签,而不是class=True的标签
t_list = bs.find_all(class_="mnav") # 这里是指class="mnav"的标签
t_list = bs.find_all(href="http://news.baidu.com")
for item in t_list:
print(item)
# 4.text 参数
# 查找文本
t_list = bs.find_all(text='hao123')
t_list = bs.find_all(text=['hao123', '百度', '贴吧'])
t_list = bs.find_all(text=re.compile('\d')) # 应用正则表达式查找含有特定文本的内容(标签里的字符串) # ['hao123']
for item in t_list:
print(item)
# 5.limit 参数
t_list = bs.find_all('a', limit=3) # 查找前 3 个 a标签
print(t_list)
# 6.指定属性查找
# ★ 既是某个标签,又有某种属性
t_list = bs.find_all('a', class_='mnav')
t_list = bs.find_all('a', {'class': 'mnav', 'id': '', 'name': 'tj_trnews'}) # 获取带有这些属性的标签
print(t_list)
# ★【css选择器】
t_list = bs.select('title') # 通过标签来查找
t_list = bs.select('.mnav') # 通过标class来查找
t_list = bs.select('#u1') # 通过id来查找
t_list = bs.select('a[class="bri"]') # 通过属性值来查找
t_list = bs.select('head > title') # 通过子代选择器来查找
t_list = bs.select('.mnav ~ .bri') # 兄弟选择器
print(t_list[0].get_text()) # 获取文本
# 2.逐一解析数据
soup = BeautifulSoup(html, "html.parser")
# soup.find_all('div', class_='item') # 查找指定class='item'的div节点
for item in soup.find_all('div', class_='item'): # 带有 class = item 的div
# print(item) # 测试查看item(筛选后的html内容)
print(type(item)) #
item = str(item) # 转换成字符串str,方便后面用正则表达式
正则表达式 re模块 适合一长段凌乱的字符串的匹配
xpah lxml-->etree 适合层次鲜明的节点的层层获取
'''
xpath解析
xpath 是针对 xml 创建的一种表达式语言,可以从 xml 中直接提取数据
html 是 xml 的子集,因此 xpath 也可以处理 html
'''
'''
from lxml import etree 从包中导入模块
# etree.HTML 用来解析字符串格式的HTML文档对象,将传进去的字符串转变成_Element对象。
# 作为_Element对象,可以方便的使用getparent()、remove()、xpath()等方法。
et = etree.HTML(html) -- 加载数据,返回element对象
et.xpath('') -- 匹配节点
/html/body/div/span/text() -- text() : 拿到标签内的文本
/html/body/div/span/@属性 -- @属性 : 拿到某个属性值
/html/*/span/@属性 -- * 通配符,任意的字符,啥都行
et.xpath('//a/@href') -- // 表示从任意位置开始找
et.xpath('//span[@class="title"]/text()') -- [@属性名xxx=属性值xx] 表示属性上的限定
href = item.xpath('./a/@href')[0] -- ./表示当前这个元素
补充:
① 标签同时具有两个属性值,使用and连接即可
'//div[@class="icon" and @id="next"]/@href'
同一个属性有两个值,可以用contains(@属性名=属性值)来限定
.xpath('//sdiv[@id="a" and contains(@class,"b") and contains(@class,"c")]/text()
② XPath常用规则
/ (直接)子节点
// 所有子孙节点 (后代)
. 选取当前节点
.. 表示父节点
* 表示任意节点
[1] 表示第一个某元素 -- //div[1] 第一个div元素 , ⚠ 不是从0开始计数,从1开始计数!⚠
[last()] 表示最后一个 -- //div[last()] 最后一个dv | //div[last() - 1] 倒数第二个dv
[position() < 3] 前两个某元素 -- //div[position() < 5] 前4个div
contains(@属性名,属性值) 某元素的某个属性中包含某个值 -- div[contains(@class,'abc')]class中含有'abc'的div
starts-with(@属性名,属性值) 选择以 某个属性名=xxx 开头的元素
'''
from lxml import etree
# ① 加载准备解析的数据 (爬取数据)
f = open('text.html', mode='r', encoding='utf-8')
html = f.read()
print(html) # 拿到了html
# ② 加载数据,返回element对象
# et = etree.parse('',etree.HTMLParser()) # 解析内容,解析方式
# etree.HTML 用来解析字符串格式的HTML文档对象,将传进去的字符串转变成_Element对象。
# 作为_Element对象,可以方便的使用getparent()、remove()、xpath()等方法。
et = etree.HTML(html) # et是一个element对象
# xpath的语法
# 不用//,就必须从/html开始
result = et.xpath('/html') # /html表示根节点
result = et.xpath('/html/body') # 表达式中间的/表示一层html节点
result = et.xpath('/html/body/div/span')
result = et.xpath('/html/body/div/span/text()') # text()表示提取标签中的文本信息
result = et.xpath('/html/body/div/*/li/a/text()') # * 通配符,任意的字符,啥都行
result = et.xpath('/html/body/div/*/li/a/@href') # @表示属性
print(type(result)) #
print(type(result[0])) #
result = et.xpath('//a/@href') # // 表示从任意位置开始找 -- 任意位置开始的 a标签的 href属性值
result = et.xpath('//span[@class="title"]/text()') # [@属性名xxx=属性值xx] 表示属性上的限定
# 满足包含多个属性的元素
result = et.xpath('//div[contains(@class,"pic") and contains(@id,"next")]/span/text()')
# 包含 @class="pic"并且 @id="next" 的 div节点
# 选择以 某个属性名=xxx 开头的元素
result = et.xpath('//li[starts-with(@name,"navIndex")]/ul/li/a')
# 成对出现(匹配到多个节点)
# 用循环实现
result = et.xpath('/html/body/div[@class="item"]/ul/li')
for item in result:
href = item.xpath('./a/@href')[0] # ./表示当前这个元素
text = item.xpath('./a/text()')[0]
print(href, text)
# 查找一次完毕,基于查找到的节点,继续往下查找(相对某个节点查找)
divs = et.xpath('/html/body/div[@class="item"]')
a = divs[0].xpath('./ul/li/a') # ./ 表示从当前节点的下一层开始找
print(a[0].text)
更多内容: https://blog.csdn.net/qq_50854790/article/details/123610184
html = requests.get(url)
# ② 加载数据,返回element对象
et = etree.HTML(html) # et是一个element对象
res1 = et.xpath('/html/body/div/span/text()') # text()表示提取标签中的文本信息
print(res1)
result = et.xpath('//div[@class="title"]/span/text()') # [@属性名xxx=属性值xx] 表示属性上的限定
print(res2)
res3 = et.xpath('//div[contains(@class,"pic") and contains(@id,"next")]/div/ul/li')
# 包含 @class="pic"并且 @id="next" 的 div节点
print(res3)
res4 = res3[0].xpath('./text()') # 拿到的节点还能继续使用 xpath 进一步获得节点
print(res4)
# 成对出现(匹配到多个节点)
# 用循环实现
result = et.xpath('/html/body/div[@class="item"]/ul/li')
for item in result:
href = item.xpath('./a/@href')[0] # ./表示当前这个元素
text = item.xpath('./a/text()')[0]
print(href, text)
'''
多线程
让程序能同时执行多个任务
1.导包 from threading import Thread
2.创建任务 def func()...
3.创建线程 t = Thread(target=func,args=(xxx,xxx)) # target=目标函数 , args=(参数1,参数2...)
4.启动线程 t.start()
'''
from threading import Thread
# 定义函数,用于添加到线程中
def func(name):
for i in range(0, 100):
print("子线程_{}".format(name))
if __name__ == '__main__': # 主线程
# 创建一个子线程
t1 = Thread(target=func, args=('张三丰',)) # 容器类型哪怕里面只有一个元素,建议用元组形式,要用逗号隔开!
t1.start() # 启动线程,这里只是设置了多线程状态,为可以开始工作状态,具体的执行时间由CPU决定
t2 = Thread(target=func, args=('李四',))
t2.start()
for i in range(0, 100):
print("主线程%d" % i)
# 会发现t1、t2和主线程会有交叉,说明是多线程并发
处理一些庞大的、比较慢的程序时适合用多线程,如果是秒完成的程序,单单针对速度的话,用多线程提速并不明显,甚至还会变慢
处理一些请求服务器的操作一般用多线程都比较快
'''
线程池
自动管理线程
若指定管理 10 个线程,则在一个线程结束后,自动关闭这个线程,创建下一个线程,保证一直都是 10 个线程在进行
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程
# 把任务提交给线程池,线程任务的调度交给线程池来完成
t.submit(func,f'线程{i}',18) # 参数1:执行的函数,参数2-n:函数的传参
'''
from concurrent.futures import ThreadPoolExecutor
# 定义函数,用于添加到线程中
def func(name,age):
for i in range(0, 100):
print("线程_{}__{}".format(name,age))
if __name__ == '__main__':
# 创建线程池
with ThreadPoolExecutor(10) as t: # 里面可以有10个线程
# 10个任务
# 把任务交给线程池
for i in range(1000):
t.submit(func,f'线程{i}',18)
# 用 with 会等待线程池内的任务全部执行完毕,才会向下执行(守护)
from concurrent.futures import ThreadPoolExecutor
def func(name, age,url):
html = requests.get(url).text
print(html,name,age)
if __name__ == '__main__':
with ThreadPoolExecutor(100) as t: # 里面可以有100个线程
# 100000个任务
# 把任务交给线程池
for i in range(100000):
t.submit(func, f'线程{i}', 18,'http://www.baidu.com')
# 😊测试线程池性能
# 创建线程池
st1 = time.time()
for i in range(100):
func(f'线程{i}', 18)
ed1 = time.time()
print(ed1 - st1)
# 创建线程池
st2 = time.time()
with ThreadPoolExecutor(10) as t: # 里面可以有10个线程
# 10个任务
# 把任务交给线程池
for i in range(100):
t.submit(func, f'线程{i}', 18)
# func(f'线程{i}', 18)
ed2 = time.time()
print(ed2 - st2)
# 创建线程池
st3 = time.time()
for i in range(1000):
func(f'线程{i}', 18)
ed3 = time.time()
print(ed3 - st3)
# 创建线程池
st4 = time.time()
with ThreadPoolExecutor(100) as t: # 里面可以有10个线程
# 10个任务
# 把任务交给线程池
for i in range(1000):
t.submit(func, f'线程{i}', 18)
# func(f'线程{i}', 18)
ed4 = time.time()
print(ed4 - st4)
# 创建线程池
st5 = time.time()
for i in range(100000):
func(f'线程{i}', 18)
ed5 = time.time()
print(ed5 - st5)
# 创建线程池
st6 = time.time()
with ThreadPoolExecutor(1000) as t: # 里面可以有10个线程
# 10个任务
# 把任务交给线程池
for i in range(100000):
t.submit(func, f'线程{i}', 18)
# func(f'线程{i}', 18)
ed6 = time.time()
print(ed6 - st6)
print(ed1 - st1) # 单线程, 任务100 0.11509108543395996
print(ed2 - st2) # 多线程,线程池10, 任务100 0.4698059558868408
print(ed3 - st3) # 单线程, 任务1000 1.6373044967651367
print(ed4 - st4) # 多线程,线程池100, 任务1000 3.6064316749572754
print(ed5 - st5) # 单线程, 任务100000 208.70198273658752
print(ed6 - st6) # 多线程,线程池1000,任务100000 132.52682662010193
协程 当线程遇见了IO操作的时候,可以选择性地切换到其他任务上(提高CPU利用率) (多任务异步操作) 在单线程条件下 在微观上是一个任务一个任务进行切换,切换条件一般就是IO操作 在宏观上,我们能看到的其实是多个任务一起在执行
- 1
- 2
- 3
- 4
- 5
- 6
线程中的不足
def func():
print('我爱周杰伦')
time.sleep(3) # 让当前线程处于阻塞状态,CPU不为此线程工作
# input() # 程序也是处于阻塞状态 # 一般的IO操作都会使线程处于阻塞状态
# requests.get(url) # 在网络请求返回数据之前,也是处于阻塞状态
print('我真的爱周杰伦')
if __name__ == '__main__':
func()
import asyncio
import time
async def func2():
print('nihao!')
if __name__ == '__main__':
g = func2() # 此时的函数是异步协程函数,此时函数调用得到的 g 是一个协程对象
print(g) #
asyncio.run(g) # 协程程序运行需要asyncio模块的支持
# 协程案例:
async def func3():
print('你好,我是武大郎')
# time.sleep(3) # time.sleep(3) 是同步操作,当出现了同步操作的时候,异步就会中断了
await asyncio.sleep(3) # 异步操作的 sleep,需要用 await 把当前的 sleep 挂起
print('你好,我是武大郎')
async def func4():
print('你好,我是黑旋风')
# time.sleep(2)
await asyncio.sleep(2)
print('你好,我是黑旋风')
async def func5():
print('你好,我是及时雨')
# time.sleep(4)
await asyncio.sleep(4)
print('你好,我是及时雨')
async def main(): # 协程主函数
# # 第一种写法(不推荐)
# f3 = func3()
# await f3 # await 必须在 async 函数中 ,一般 await 挂起操作放在协程对象前面
# 第二种(推荐)
task = [
asyncio.create_task(func3()),
asyncio.create_task(func4()),
asyncio.create_task(func5())
]
await asyncio.wait(task)
if __name__ == '__main__':
st = time.time()
# 同时执行多个异步操作需要用 wait 管理,然后用 run 调用
# asyncio.run(main()) # 这样写会时常报错
loop = asyncio.get_event_loop() # 创建事件循环,事件循环:去检索一个任务列表的所有任务,并执行所有未执行任务,直至所有任务执行完成。
loop.run_until_complete(main()) # 将内部代码交给事件循环执行(添加任务,直至所有任务执行完成)
ed = time.time()
print(ed - st) # 3.991 - 4.005
# 一般的单线程痛不需要 9s+
# 报错记录: RuntimeError: There is no current event loop in thread 'MainThread'.
# 此时我有两个协程
# 执行这个协程之前还有一个协程调用,下面的 loop = asyncio.get_event_loop() 并没有获取到事件循环,所以导致报错:在主线程没有事件循环。简单说,就是在同一线程中,第二个协程受到第一个的干扰。
import asyncio # io 库 import aiohttp # 类似 requests,用于发送请求 import aiofiles # 异步 files 管理库
- 1
- 2
- 3
async def main():
urls = []
# 准备异步协程对象列表
tasks = []
for url in urls:
d = asyncio.create_task(aio_download(url))
tasks.append(d)
# tasks = [asyncio.create_task(aio_download(url)) for url in urls] # 这么干也行哦~
# 一次性把所有任务都执行
await asyncio.wait(tasks)
if __name__ == '__main__':
loop = asyncio.get_event_loop() # 创建事件循环,事件循环:去检索一个任务列表的所有任务,并执行所有未执行任务,直至所有任务执行完成。
loop.run_until_complete(main()) # 将内部代码交给事件循环执行(添加任务,直至所有任务执行完成)
async with aiohttp.ClientSession() as session: # aiohttp.ClientSession <==> requests
async with session.get(url) as resp:
# resp.content.read() <==> resp.content
# resp.text() <==> resp.text
# resp.json() <==> resp.json()
async with aiofiles.open(file=f'img/{name}', mode='wb') as f:
await f.write(await resp.content.read()) # 读取内容试异步的,需要 await 挂起
# 在爬虫领域的应用
import time
import asyncio
import aiohttp
import aiofiles
async def aio_download(url):
# 使用 with 可以自动管理上下文,不用手动关闭打开的资源,异步程序中使用 with 前面必须加 async,这是规定
async with aiohttp.ClientSession() as session: # aiohttp.ClientSession <==> requests
async with session.get(url) as resp:
name = url.rsplit('/', 1)[-1] # 从右边切,切1个,选最后一个
# resp.content.read() <==> resp.content
# resp.text() <==> resp.text
# resp.json() <==> resp.json()
print(f"准备开始下载 {name}")
# aiofiles是一个用于处理asyncio应用程序中的本地磁盘文件。爬虫过程中用它来进行文件的异步操作。
async with aiofiles.open(file=f'img/{name}', mode='wb') as f:
await f.write(await resp.content.read()) # 读取内容是异步的,需要 await 挂起
# with open(file=f'img/{name}', mode='wb') as f:
# f.write(await resp.content.read()) # 读取内容是异步的,需要 await 挂起
print(f"{name} 下载完成!")
async def main():
urls = [ # 三张图片地址
'http://kr.shanghai-jiuxin.com/file/2022/0729/fe832be7e35c8a593e0ce90ab56361d3.jpg',
"http://kr.shanghai-jiuxin.com/file/2022/0729/efe2fc9725df8915b530fd84ae18c39b.jpg",
"http://kr.shanghai-jiuxin.com/file/mm/20211130/hvnxp0z2jlc.jpg"
]
# 准备异步协程对象列表
tasks = []
for url in urls:
d = asyncio.create_task(aio_download(url))
tasks.append(d)
# tasks = [asyncio.create_task(aio_download(url)) for url in urls] # 这么干也行哦~
# 一次性把所有任务都执行
await asyncio.wait(tasks)
if __name__ == '__main__':
st = time.time()
# asyncio.run()会自动关闭循环,并且调用_ProactorBasePipeTransport.__del__报错, 而asyncio.run_until_complete()不会.
# asyncio.run(main()) # 这样写会时常报错
loop = asyncio.get_event_loop() # 创建事件循环,事件循环:去检索一个任务列表的所有任务,并执行所有未执行任务,直至所有任务执行完成。
loop.run_until_complete(main()) # 将内部代码交给事件循环执行(添加任务,直至所有任务执行完成)
ed = time.time()
print(ed - st)
语法
import xlwt
# 创建 Workbook 对象 (工作簿)
workbook = xlwt.Workbook(encoding='utf-8') # 创建Workbook对象
# 创建工作表1
worksheet = workbook.add_sheet('sheet1')
# 在工作表中写入数据,第一个参数表示行,第二个参数表示列,第三个参数表示内容
worksheet.write(0,0,'hello')
# 保存数据表
workbook.save('student.xls')
实战
# 保存数据到excel
def saveData_excel(dataList, savePath):
# dataList是一个二维数据,每一维是一部电影的的8条数据
print('saving...')
# 创建Workbook对象
workbook = xlwt.Workbook(encoding='utf-8', style_compression=0) # 创建Workbook对象
worksheet = workbook.add_sheet('sheet1', cell_overwrite_ok=True) # 创建工作表1
col = ("电影详情链接", "图片链接", "影片中文名", "影片外文名", "评分", "评价人数", "概述", "其他相关信息")
# 写入表头
for i in range(0, 8):
# worksheet.write(0,0,'hello') # 写入数据,第一个参数表示行,第二个参数表示列,第三个参数表示内容
worksheet.write(0, i, col[i]) # 在第0行写入写入列名
# 写入内容
for i in range(0, 250):
print('第%d条数据' % (i + 1))
data = dataList[i]
for j in range(0, 8):
worksheet.write(i + 1, j, data[j]) # 写入每行数据(一部电影信息)
workbook.save(savePath) # 保存数据表
语法
import sqlite3
# 1.连接数据库
# 若不存在会自动创建一个数据库文件
conn = sqlite3.connect('test.db') # 打开或创建一个数据库文件
print('成功打开数据库!')
# 2.创建一张数据表
# 数据库游标
cursor = conn.cursor() # 数据库游标
sql = '''
create table company
(id int primary key not null,
name text not null,
age int not null,
address char(50),
salary real)
'''
cursor.execute(sql) # 执行sql语句
conn.commit() # 提交数据库操作
# conn.close() # 关闭数据库连接
print('成功建表!')
# 3.插入数据
cursor = conn.cursor() # 数据库游标
# sql1 = '''
# insert into company(id,name,age,address,salary)
# values(1,'张三',20,'成都',8000)
# '''
# sql2 = '''
# insert into company(id,name,age,address,salary)
# values(2,'李四',30,'重庆',1000),
# '''
# sql3 = '''
# insert into company(id,name,age,address,salary)
# values(3,'王武',25,'杭州',16000)
# '''
# SQLite插入多行数据写法
sql1 = '''
insert into company(id,name,age,address,salary)
select 1,'张三',20,'成都',8000
union all
select 2,'李四',30,'重庆',1000
union all
select 3,'王武',25,'杭州',16000
'''
cursor.execute(sql1) # 执行sql语句
# driver.execute(sql2)
# driver.execute(sql3)
conn.commit() # 提交数据库操作
# conn.close() # 关闭数据库连接
print('插入成功!')
# 4.查询数据
cursor = conn.cursor() # 数据库游标
sql1 = "select id,name,address,salary from company"
res = cursor.execute(sql1) # 执行sql语句 # res 是
for row in res:
print('id = ', row[0])
print('name = ', row[1])
print('address = ', row[2])
print('salary = ', row[3], '\n')
conn.commit() # 提交数据库操作
conn.close() # 关闭数据库连接
print('查询完毕!')
实战
# 创建初始化数据库
def init_db(db_path):
sql = '''
create table movie250
(
id integer primary key autoincrement,
info_link text,
imgSrc_link text,
c_name varchar,
f_name varchar,
rating numeric,
judgeNum numeric,
inq text,
bd text
)
'''
# 创建数据表
conn = sqlite3.connect(db_path)
cursor = conn.cursor() # 游标
cursor.execute(sql) # 执行sql语句
conn.commit() # 提交数据库操作
conn.close() # 关闭数据库连接
# 保存数据到数据库db
def saveData_db(dataList, db_path):
init_db(db_path) # 创建、初始化数据库
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
for data in dataList: # 每条数据(每部电影)
# 处理数据
for index in range(len(data)): # 每部电影的内容(8个指标)
if index == 4 or index == 5:
continue # 索引是4和5的不用变为字符串类型
data[index] = '"{}"'.format(data[index]) # 把每个内容都变成带有""的字符串 -- 这一步是必须的,因为sql语句带引号,里面要插入的值如果是字符串必须再在字符串外面套一层引号
# 插入数据(保存数据)
sql = '''
insert into movie250(
info_link,imgSrc_link,c_name,f_name,rating,judgeNum,inq,bd)
values({})'''.format(','.join(data))
print(sql)
cursor.execute(sql)
conn.commit()
cursor.close()
conn.close()
# 用 requests 请求图片数据:拿到 😊字节数据😊 方法: resp.content
resp_detail_img = requests.get(url=res_img_url)
with open(f'D:/img/{img_name}.{img_format}',mode='wb') as f:
f.write(resp_detail_img.content) # 保存图片(写入bytes数据)
拿到网页源代码时,script中有一大串 类似 json格式的字符串,可以这样转换成json: json.loads(img_arr_dict)
import json
img_arr_dict = res.group('dict_img') # 此时是一个很像json的字符串
# 用json模块把字符串转换为json,转换完后里面的一些转义符也会消去
img_json = json.loads(img_arr_dict)
# print(img_json)
在爬取网页数据的时候,有时候会出现获取数据为空的情况(在路径、代码没问题前提下),
这种就很有可能是爬取的数据为动态加载的数据
有很多网页内容是js渲染或者其他手段弄出来的,这些在网页源代码中都是找不到的,
之前的方案数都是抓包获取这些后来数据的网页链接,再拿到这些数据,
但有些数据经过加密等一系列复杂操作,我们解密、加密操作起来十分复杂!
现在想能不能让程序连接到浏览器,让浏览器来完成各种复杂的操作,把那些脚本都跑完了
(即浏览器拿到这些数据,加载完毕后),
我们直接拿到最后的结果数据 ?
可以,那就是使用 selenium !
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
selenium : 本是一种自动化测试工具
selenium 可以打开浏览器,然后像人一样去操作浏览器
★ 程序员可以通过selenium,直接提取网页上的各种信息
因为对于 selenum 来说,各种网页信息都是透明的
selenium 它本身也是浏览器,加密、反爬的东西无论如何也得让浏览器正常运行
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
自动化测试框架 selenium 解决了 Ajax异步加载的页面获取问题
chromedriver下载地址: http://npm.taobao.org/mirrors/chromedriver/
from selenium import webdriver
😊【第一部分:创建driver并访问(打开)网页】
# (注意:驱动需要与浏览器版本兼容)
# 配置Chrome启动时属性的类ChromeOptions
options = webdriver.ChromeOptions()
options.add_argument('--headless') # 设置 无界面浏览 # options.add_argument('') # 可以设置UA、IP代理等等
options.add_argument('--disable-gpu') # 禁用GPU加速
driver = webdriver.Chrome(options=options) # 创建了 Chrome 实例
driver.get('http://www.baidu.com') # 请求页面(打开此页面)
html = driver.page_source # 网页源代码 str类型
Chrome 实例常用的配置
选项的添加无非就是分为以下这几种,如下:
options.set_headless() # 设置启动无界面化 options.binary_location(value) # 设置chrome二进制文件位置 options.add_argument(arg) # 添加启动参数 options.add_extension(path) # 添加指定路径下的扩展应用 options.add_encoded_extension(base64) # 添加经过Base64编码的扩展应用 options.add_experimental_option(name,value) # 添加实验性质的选项 options.debugger_address(value) # 设置调试器地址 options.to_capabilities() # 获取当前浏览器的所有信息ptions
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
虽然选项很多,但是我们真正能用到的不多,一般就是无痕模式或者禁用JavaScript和图片来快速获取到相关信息。虽然我们上面使用的是Options方法,但是在实际应用中建议大家使用的ChromeOptions方法。
# 利用 options 配置 Chrome 实例 driver
options = webdriver.ChromeOptions()
# 😊添加启动参数 (add_argument)
# 设置UA
options.add_argument('user-agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36"')
# 设置ip和端口
options.add_argument('--proxy-server=http://ip:port')
options.add_argument("--proxy-server=http://200.130.123.43:3456") # 代理服务器访问
options.add_argument('--headless') # 浏览器不提供可视化页面
options.add_argument('--user-agent=""') # 设置请求头的User-Agent
options.add_argument('--window-size=1280x1024') # 设置浏览器分辨率(窗口大小)
options.add_argument('--start-maximized') # 最大化运行(全屏窗口),不设置,取元素会报错
# 去掉chrome正受到自动测试软件的控制的提示
options.add_argument('--disable-infobars') # 禁用浏览器正在被自动化程序控制的提示
options.add_argument('--incognito') # 隐身模式(无痕模式)
options.add_argument('--hide-scrollbars') # 隐藏滚动条, 应对一些特殊页面
options.add_argument('--disable-javascript') # 禁用javascript
options.add_argument('--blink-settings=imagesEnabled=false') # 不加载图片, 提升速度
options.add_argument('--ignore-certificate-errors') # 禁用扩展插件并实现窗口最大化
options.add_argument('--disable-gpu') # 禁用GPU加速
options.add_argument('–disable-software-rasterizer')
options.add_argument('--disable-extensions') # 禁用扩展
options.add_argument('lang=zh_CN.UTF-8') # 设置默认编码为utf-8
# 设置加载策略
options.page_load_strategy = 'normal'
options.page_load_strategy = 'eager'
options.page_load_strategy = 'none'
# 通过URL导航到新页面时, 默认情况下, Selenium 将等待页面完全加载后再进行响应. 但是在加载大量第三方资源的页面上可能会导致较长的等待时间. 在这种情况下, 使用非默认策略可以使测试的执行速度更快, 但是也可能导致不稳定, 即页面上的元素随元素加载和大小变化而改变位置
'''
策略 准备完成的状态 备注
normal complete 默认情况下使用, 等待所有资源下载完成
eager interactive DOM访问已准备就绪, 但其他资源 (如图像) 可能仍在加载中
none Any 完全不阻塞WebDriver
normal:等待整个页面加载完毕再开始执行操作
eager:等待整个dom树加载完成,即DOMContentLoaded这个事件完成,也就是只要 HTML 完全加载和解析完毕就开始执行操作。放 弃等待图片、样式、子帧的加载。
none:等待html下载完成,哪怕还没开始解析就开始执行操作。
'''
# 😊添加扩展应用 (add_extension, add_encoded_extension)
extension_path = '插件路径'
options.add_extension(extension_path)
# 😊添加实验性质的设置参数 (add_experimental_option)
# 禁止图片加载
prefs = {"profile.managed_default_content_settings.images": 2}
options.add_experimental_option("prefs", prefs)
# 禁用保存密码
prefs = {"": ""}
prefs["credentials_enable_service"] = False
prefs["profile.password_manager_enabled"] = False
options.add_experimental_option ("prefs", prefs)
# 禁用浏览器弹窗
prefs = {
'profile.default_content_setting_values' : {
'notifications' : 2
}
}
options.add_experimental_option('prefs',prefs)
# 创建 Chrome 实例对象
# executable_path为chromedriver的路径,如果 python 环境变量下或项目中有,就不用配置
dirver = webdriver.Chrome() # 默认启动
dirver = webdriver.Chrome(executable_path=r'./chromedriver.exe') # 获取chrome浏览器的驱动,并启动Chrome浏览器
#设置隐式等待
driver.implicitly_wait(30)
# 注:隐式等待的好处是不用像固定等待方法一样死等时间N秒,可以在一定程度上提升测试用例的执行效率。不过这种方法也存在一定的弊端,那就是程序会一直等待整个页面加载完成,也就是说浏览器窗口标签栏中不再出现转动的小圆圈,才会继续执行下一步
#设置窗口最大化
driver.maximize_window()
# 设置cookie
driver.delete_all_cookies() # 删除cookie
driver.add_cookie({'name':'ABC','value':'DEF'}) # 携带cookie
driver.get_cookies() # 获取cookie
😊【第二部分:匹配节点及其内容】
# ★此后获取节点啥的和以往一样,可以用 driver.page_source 这个str类型的网页源码去匹配内容
# from lxml import etree
# et = etree.HTML(driver.page_source) # 把这个网页源码字符串解析成了html element对象 -- et是一个element对象
# self_b = et.xpath('//input[contains(@id,"su")]/@value')
# print(self_b)
# ★也可以使用selenium自带的查找节点的方法
'''
from selenium.webdriver.common.by import By
driver.find_element(By.XPATH,'XPATH')
driver.find_element(By.ID,'ID')
driver.find_element(By.CLASS_NAME,'CLASS_NAME')
driver.find_element(By.CSS_SELECTOR,'CSS_SELECTOR')
driver.find_element(By.LINK_TEXT,'LINK_TEXT')
driver.find_element(By.PARTIAL_LINK_TEXT,'PARTIAL_LINK_TEXT')
driver.find_element(By.TAG_NAME,'TAG_NAME')
'''
from selenium.webdriver.common.by import By
res = driver.find_elements(By.CLASS_NAME,'nav_bg') # 查找class='nav_bg'的节点
print(res) # 是一个节点 []
for item in res:
print(item.get_attribute('style')) # item 的获取style属性值
# res = driver.find_element(By.XPATH,'//li[@name="navIndex"]/a/text()') # 错误写法,selenium的xpath只能匹配元素
res = driver.find_elements(By.XPATH,'//li[starts-with(@name,"navIndex")]/ul/li/a')
print(res) # 是一个节点列表
for item in res:
print(item.get_property('text')) # 这样能拿到文本(?不明白为什么这里text不行?)
# 获得属性
print(item.get_attribute('title')) # NBA直播
res = driver.find_elements(By.XPATH,'//a')
for item in res:
print(item.text) # 这样text能拿到一些文本
注意:如果某些元素是ajax或者其他方式加载出来的,在selenium匹配节点的时候,很可能因为某些操作(如:点击某按钮后,页面会局部刷新,加载出来一些信息)后,导致节点还没加载出来,还没获取到,代码就已经往下执行了(因为局部刷新需要时间,而代码执行太快),此时最简单的方法就是time.sleep() 让程序睡一会儿,再往下执行,或者采用下文提到的延时等待方法。
😊【第三部分:可以执行js操作浏览器】
# 可以让 dricer 执行 js代码,来操作BOM,操作页面
js = '''
console.log('hello,world!')
'''
driver.execute_script(js) # 可以让 dricer 执行 js代码,来操作BOM,操作页面
driver.close() # 关闭当前页面(如果浏览器只剩下一页,则关闭整个dirver)
driver.quit() # 关闭dirver
① 窗口下滑
# 窗口下滑
js = '''
let height = 0
let interval = setInterval(() => {
window.scrollTo({
top: height,
behavior: "smooth"
});
height += 500
}, 500);
setTimeout(() => {
clearInterval(interval)
}, 7000);
'''
driver.execute_script(js)
# 时间必须大于等于js的时间,否则会并发执行,混乱
time.sleep(8) # 给js足够的时间执行完,最好比js中定时器的总时间多一点
② 清理Chrome(driver)缓存
from selenium import webdriver
driver = webdriver.Chrome()
# 设置隐式等待
driver.implicitly_wait(10)
# 清除缓存提示框
driver.get('chrome://settings/clearBrowserData')
# 2S 等待时间
time.sleep(2)
clearButton = driver.execute_script("return document.querySelector('settings-ui').shadowRoot.querySelector('settings-main').shadowRoot.querySelector('settings-basic-page').shadowRoot.querySelector('settings-section > settings-privacy-page').shadowRoot.querySelector('settings-clear-browsing-data-dialog').shadowRoot.querySelector('#clearBrowsingDataDialog').querySelector('#clearBrowsingDataConfirm')")
clearButton.click()
# driver.quit()
driver.current_url
driver.log_types # 获取当前日志类型
driver.get_log('browser')# 浏览器操作日志
driver.get_log('driver') # 设备日志
driver.get_log('client') # 客户端日志
driver.get_log('server') # 服务端日志
driver.maximize_window() # 最大化
driver.fullscreen_window() # 全屏
driver.minimize_window() # 最小化
driver.get_window_position() # 获取窗口的坐标
driver.get_window_rect() # 获取窗口的大小和坐标
driver.get_window_size() # 获取窗口的大小
driver.set_window_position(100,200) # 设置窗口的坐标
driver.set_window_rect(100,200,32,50) # 设置窗口的大小和坐标
driver.set_window_size(400,600) # 设置窗口的大小
driver.current_window_handle # 返回当前窗口的句柄
driver.window_handles #返回当前会话中的所有窗口的句柄
driver.set_script_timeout(5) # 设置脚本延时五秒后执行
driver.set_page_load_timeout(5) # 设置页面读取时间延时五秒
driver.close() #关闭当前标签页
driver.quit() #关闭浏览器并关闭驱动
driver.page_source
driver.save_screenshot('1.png')#截图,只支持PNG格式
driver.get_screenshot_as_png() #获取当前窗口的截图作为二进制数据
driver.get_screenshot_as_base64() #获取当前窗口的截图作为base64编码的字符串
driver.forward() # 页面前进
driver.back() # 页面后退
driver.refresh() # 页面刷新
在Selenium中也可以自定义JS代码并带到当前页面中去执行,如下:
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
driver = webdriver.Chrome(executable_path=r'./chromedriver.exe')
driver.get('https://www.baidu.com')
kw1 = driver.find_element(By.ID,'kw')
driver.execute_script("alert('hello')") # 执行js代码
time.sleep(3)
driver.quit()
driver.get_cookie('BAIDUID') #获取指定键的Cookies
driver.get_cookies() #获取所有的Cookies
for y in driver.get_cookies():
x=y
if x.get('expiry'):
x.pop('expiry')
driver.add_cookie(x) #添加Cookies
driver.delete_cookie('BAIDUID') #删除指定键的Cookies内容
driver.delete_all_cookies() #删除所有cookies
driver.title
driver.name
driver.implicitly_wait(5)
对我们找到的元素进行二次操作,不仅可以再次选择子元素还可以进行其它操作。如下:
kw1.click() # 点击元素
kw1.text # 内容,如果是表单元素则无法获取
kw1.get_property('background') # 获取元素的属性的值
kw1.get_attribute('id') # 获取元素的属性的值
kw1.send_keys('') # 向元素内输入值
kw1.id # Selenium所使用的内部ID
kw1.clear() # 清除元素的值
kw1.location # 不滚动获取元素的坐标
kw1.location_once_scrolled_into_view # 不滚动且底部对齐并获取元素的坐标
kw1.parent # 父元素
kw1.size # 大小
kw1.submit # 提交
kw1.screenshot('2.png') # 截取元素形状并保存为图片
kw1.tag_name # 标签名
kw1.is_selected() # 判断元素是否被选中
kw1.is_enabled() # 判断元素是否可编辑
kw1.is_displayed # 判断元素是否显示
kw1.value_of_css_property('color') # 获取元素属性的值
kw1._upload('2.png') # 上传文件
动作API是网上资料比较少的,因为之前的查找元素,调用click等已经可以解决很多的问题了,在翻看官方文档时,发现selenium还支持动作API来模拟动作。
动作API分为四个部分,分别是键盘、鼠标、笔、滚轮。
一些交互动作都是针对某个节点执行的,比如:对于输入框,我们就调用它的输入文字和清空文字方法;对于按钮,就调用它的点击方法,还有另外一些操作,他们没有特定的对象,比如鼠标的拖拽,键盘的按键等,这些动作用另一种方式来执行,那就是动作链
比如:现在实现一个节点的拖拽操作,将某个节点从一处拖拽到另外一处
from selenium.webdriver.common.action_chains import ActionChains
ActionsChains是如何模拟鼠标操作?
1.首先把当前的driver对象赋予给ActionsChains类,让ActionsChains知道是哪个driver实例在操作鼠标,
2.其次再传入需要被定位元素位置,让鼠标对此元素执行click操作,
3.最后借助perform执行上面规划好的动作链
例如:ActionChains(driver).context_click(right_click).perform()
from selenium.webdriver import ActionChains
clickable = driver.find_element(By.ID, "clickable")
find_element(By.ID,'su').click() # 点击
# 复杂的操作,使用动作链
# 鼠标单击长按id为clickable的元素
ActionChains(driver).click_and_hold(clickable).perform()
# 也可以分开写
ac = ActionChains(driver)
ac.key_down(Keys.SHIFT)
ac.send_keys("abc")
ac.click(on_element=btn)
ac.perform()
## ★常用鼠标操作★ ##
.click(on_element=None) # 鼠标单击
.click_and_hold(on_element=None) # 鼠标单击长按
.context_click(on_element=None) # 右击
.double_click(on_element=None) # 双击
.drag_and_drop(source,target) # 拖拽到某个元素然后松开
.drag_and_drop_by_offset(source, xoffset, yoffset) # 拖拽到某个坐标然后松开
source: The element to mouse down.
xoffset: X offset to move to.
yoffset: Y offset to move to.
.move_by_offset(xoffset, yoffset) # 鼠标从当前位置移动到某个坐标
.move_to_element(to_element) # 鼠标移动到某个元素
.move_to_element_with_offset(to_element, xoffset, yoffset) # 移动到距某个元素(左上角坐标)多少距离的位置
.release(on_element=None) # 在某个元素位置松开鼠标左键
鼠标操作实例
鼠标定义的5种按键
鼠标双击
clickable = driver.find_element(By.ID, "clickable")
ActionChains(driver)\
.double_click(clickable)\
.perform()
鼠标右击
clickable = driver.find_element(By.ID, "clickable")
ActionChains(driver)\
.context_click(clickable)\
.perform()
按下鼠标3键
动作行为构造类ActionBuilder,将行为绑定到WebDriver对象上
selenium 中的鼠标操作类 -- ActionChains类,其操作鼠标行为都是使用的 ActionBuilder类。
action = ActionBuilder(driver)
action.pointer_action.pointer_down(MouseButton.BACK)
action.pointer_action.pointer_up(MouseButton.BACK)
action.perform()
按下鼠标4键
action = ActionBuilder(driver)
action.pointer_action.pointer_down(MouseButton.FORWARD)
action.pointer_action.pointer_up(MouseButton.FORWARD)
action.perform()
鼠标移动到元素上
hoverable = driver.find_element(By.ID, "hover")
ActionChains(driver)\
.move_to_element(hoverable)\
.perform()
鼠标位移
就是通过像素点来进行位移操作。
从元素左顶边进行位移
mouse_tracker = driver.find_element(By.ID, "mouse-tracker")
ActionChains(driver)\
.move_to_element_with_offset(mouse_tracker, 8, 11)\
.perform()
从当前窗口左上角位移
action = ActionBuilder(driver)
action.pointer_action.move_to_location(8, 12)
action.perform()
从当前鼠标位置位移
ActionChains(driver)\
.move_by_offset( 13, 15)\
.perform()
拖拽元素
该方法首先单击并按住源元素,移动到目标元素的位置,然后释放鼠标。
draggable = driver.find_element(By.ID, "draggable")
droppable = driver.find_element(By.ID, "droppable")
ActionChains(driver)\
.drag_and_drop(draggable, droppable)\
.perform()
通过位移拖拽
draggable = driver.find_element(By.ID, "draggable")
start = draggable.location
finish = driver.find_element(By.ID, "droppable").location
ActionChains(driver)\
.drag_and_drop_by_offset(draggable, finish['x'] - start['x'], finish['y'] - start['y'])\
.perform()
滚轮(只有谷歌内核浏览器生效)
滚动到某元素位置
iframe = driver.find_element(By.TAG_NAME, "iframe")
ActionChains(driver)\
.scroll_to_element(iframe)\
.perform()
定量滚动
footer = driver.find_element(By.TAG_NAME, "footer")
delta_y = footer.rect['y']
ActionChains(driver)\
.scroll_by_amount(0, delta_y)\
.perform()
从一个元素滚动指定量
iframe = driver.find_element(By.TAG_NAME, "iframe")
scroll_origin = ScrollOrigin.from_element(iframe)
ActionChains(driver)\
.scroll_from_origin(scroll_origin, 0, 200)\
.perform()
从一个元素滚动,并指定位移
footer = driver.find_element(By.TAG_NAME, "footer")
scroll_origin = ScrollOrigin.from_element(footer, 0, -50)
ActionChains(driver)\
.scroll_from_origin(scroll_origin, 0, 200)\
.perform()
从一个元素的原点位移
ActionChains(driver)\
.scroll_from_origin(scroll_origin, 0, 200)\
.perform()
from selenium.webdriver import ActionChains
from selenium.webdriver.common.keys import Keys
driver.find_element(By.ID,'kw').send_keys('python') # 输入'python'
driver.find_element(By.ID,'kw').send_keys(Keys.ENTER) # 回车键
# 复杂的操作,使用动作链
# 输入'ABC'
ActionChains(driver).key_down(Keys.SHIFT).send_keys("abc").perform()
## ★常用键盘操作★ ##
.key_down(value,element=None) # 按下某个键
.key_up(value,element=None) # 放开某键
- value: The modifier key to send. Values are defined in `Keys` class.
- element: The element to send keys. If None, sends a key to current focused element.
.send_keys(*keys_to_send) # 发送某个键或者输入文本到当前焦点的元素
.send_keys_to_element(element, *keys_to_send) # 发送某个键到指定元素
组合
.send_keys(Keys.CONTROL,'a') # 全选(Ctrl+A)
.send_keys(Keys.CONTROL,'c') # 复制(Ctrl+C)
.send_keys(Keys.CONTROL,'x') # 剪切(Ctrl+X)
.send_keys(Keys.CONTROL,'v') # 粘贴(Ctrl+V)
非组合
回车键:Keys.ENTER
删除键:Keys.BACK_SPACE
空格键:Keys.SPACE
制表键:Keys.TAB
回退键:Keys.ESCAPE
刷新键:Keys.F5
键盘操作实例
按下某键,以输入shift+abc为例
ActionChains(driver)\
.key_down(Keys.SHIFT)\
.send_keys("abc")\
.perform()
弹起某键,以输入shift+a和shift+b为例
ActionChains(driver)\
.key_down(Keys.SHIFT)\
.send_keys("a")\
.key_up(Keys.SHIFT)\
.send_keys("b")\
.perform()
浏览器输入某串字符(不指定元素)
ActionChains(driver)\
.send_keys("abc")\
.perform()
指定元素输入字符串
text_input = driver.find_element(By.ID, "textInput")
ActionChains(driver)\
.send_keys_to_element(text_input, "abc")\
.perform()
复制和粘贴
cmd_ctrl = Keys.COMMAND if sys.platform == 'darwin' else Keys.CONTROL
ActionChains(driver)\
.send_keys("Selenium!")\
.send_keys(Keys.ARROW_LEFT)\
.key_down(Keys.SHIFT)\
.send_keys(Keys.ARROW_UP)\
.key_up(Keys.SHIFT)\
.key_down(cmd_ctrl)\
.send_keys("xvv")\
.key_up(cmd_ctrl)\
.perform()
perform() # 执行所有操作
pause(seconds) # 暂停所有输入(指定持续时间以秒为单位)
reset_actions() # 结束已经存在的操作并重置
实例1:复制粘贴
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
driver = webdriver.Chrome(executable_path=r'./chromedriver.exe')
driver.get('https://www.baidu.com')
a = ActionChains(driver)
kw1 = driver.find_element(By.ID,'kw')
tj = driver.find_element(By.ID,'su')
tj.send_keys(Keys.CONTROL,'c') # 复制
a.drag_and_drop(kw1,tj).perform() # 从输入框拖动到搜索按钮
kw1.send_keys(Keys.CONTROL,'v') # 粘贴
tj.send_keys(Keys.ENTER)
time.sleep(3)
driver.close()
driver.quit()
暂停(pause)
光标移动,滚轮滚动期间,会有一些时间空隙,这里可以使用暂停来实现,这里是支持链式调用的,这里贴出官方给出的例子,
clickable = driver.find_element(By.ID, "clickable")
ActionChains(driver)\
.move_to_element(clickable)\
.pause(1)\
.click_and_hold()\
.pause(1)\
.send_keys("abc")\
.perform()
释放所有动作
当前有动作执行时,可以使用以下方法停止这些动作,
ActionBuilder(driver).clear_actions()
通过执行js命令: window.open()
实现新开选项卡(新窗口)
不同的选项卡是存在列表 driver.window_handles 里
通过driver.window_handles[0]
就可以操作第一个选项卡
通过driver.window_handles[-1]
就可以操作最后一个(最新)选项卡
注意:在 selenium 的浏览器驱动中,新窗口默认是不切换过来的,需要用上述代码控制切换窗口,否则不切换,也拿不到相应网页的内容
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://www.baidu.com')
driver.execute_script('window.open()')
print(driver.window_handles)
driver.switch_to_window(driver.window_handles[1]) # 切换到第二个选项卡
driver.get('https://www.taobao.com')
time.sleep(1)
driver.close() # 关闭当前选项卡(第二个选项卡) # 注意此时即使被关闭了,selenium 视角还是在第二个选项卡那里,需要手动切换回去
driver.switch_to_window(driver.window_handles[0]) # 切换到第一个选项卡(回到第一个选项卡)
driver.get('https://python.org')
网页中有一种节点叫作iframe,也就是子Frame,相当于页面的子页面,它的结构和外部网页的结构完全一致。
Selenium打开页面后,它默认是在父级别Frame里面操作,而此时如果页面中还有子Frame,它是不能获取到子Frame里面的节点的,这时就需要使用switch_to.frame() 方法来切换Frame
import time
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver import ActionChains
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
from selenium.webdriver.support.wait import WebDriverWait
driver = webdriver.Chrome()
driver.get("https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable")
driver.switch_to.frame("iframeResult")
try:
logo = driver.find_element_by_class_name("logo")
except NoSuchElementException:
print("NO LOGO")
# driver.switch_to.default_content() # 切换回默认页面、原页面(即整个窗口)
driver.switch_to.parent_frame() # 切换回上一层的frame,对于层层嵌套的frame很有用
logo = driver.find_element_by_class_name("logo")
print(logo)
print(logo.text)
结果:
NO LOGO
<selenium.webdriver.remote.webelement.WebElement (session="97f699231561624df577fc75ece0866e", element="16ea4a07-35f5-426b-aab8-980a86a7d739")>
在弹窗处理中,我们会遇到三种情况,如下:
浏览器弹出框
新窗口弹出框
人为弹出框
首先说说浏览器弹出框,想必大家对JavaScript中的Alert,Confirm,Prompt应该不是很陌生,就是弹出框,确认框,输入框;基本方法如下:
from selenium.webdriver.common.alert import Alert
driver = webdriver.Chrome(executable_path=r'./chromedriver.exe')
driver.implicitly_wait(10)
driver.get('https://www.baidu.com')
a1 = Alert(driver)
a1.accept() # 确定
a1.dismiss() # 取消
a1.authenticate(username,password) # 用户身份验证
a1.send_keys('') # 输入文本或按键
a1.text # 获取弹窗内容
这里我们应对每种情况它上面的方法的对应位置都是会有所变化的,所以我们需要根据具体情况来进行操作,而且还可以使用另一种方法,如下:
driver = webdriver.Chrome(executable_path=r'./chromedriver.exe')
driver.implicitly_wait(10)
driver.get('https://www.baidu.com')
a1 = driver.switch_to_alert()
a1.accept() # 确定
a1.dismiss() # 取消
a1.authenticate(username,password) # 用户身份验证
a1.send_keys('') # 输入文本或按键
a1.text # 获取弹窗内容
注:该类方法必须在有弹框的情况下才有作用,如没有会报错。
上面就是浏览器弹出框的处理方法了,如果是 新窗口弹出 的话那么就不一样了,我们需要通过句柄来定位
具体用法如下:
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
driver = webdriver.Chrome(executable_path=r'./chromedriver.exe')
driver.implicitly_wait(10)
driver.get('https://www.baidu.com')
kw1 = driver.find_element(By.ID,'kw')
tj = driver.find_element(By.ID,'su')
hwnd = driver.window_handles # 所有窗口句柄
for h in hwnd:
if h != driver.current_window_handle: # 如果句柄不是当前窗口句柄则切换
driver.switch_to_window(h) # 切换窗口
else:
print('无需切换窗口')
time.sleep(3)
driver.close()
driver.quit()
注:如果有多个窗口,当你关闭了当前窗口想切换到另一个窗口,你需要把没关闭的窗口切换成当前活动窗口,因为Selenium是不会为你做这件事的。
这类弹出框是我们自己开发的,一般都是使用 div 包裹一些其它的元素标签然后形成一个整体,当我们触发某个事件的时候就会出现,否则消失。这种弹出框使用我们的众多find前缀的方法就能遍历到,很方便,这里不一一细说。
在Selenium中我们在做自动化测试时常无法判断一个元素是否真的显示出来了,因此会各种报错,接下来我们对这些操作进行判断,如果显示出了我们预期的值,那么就进行下一步操作,否则就关闭或者暂停几秒然后再判断,这里我要跟大家说Selenium中的一个模块-----Expected_Conditions,简称为EC,如下所示:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
import time
driver = webdriver.Chrome(executable_path=r'./chromedriver.exe')
driver.implicitly_wait(10)
driver.get('https://baidu.com')
t = Edriver.title_is('百度一下,你就知道') # 判断 title 是不是 '百度一下,你就知道'
print(t(driver)) # True
time.sleep(3)
driver.close()
driver.quit()
常用的 Expected_Conditions方法
EC.title_contains('')(driver) # 判断页面标题是否包含给定的字符串
EC.presence_of_element_located('')(driver) # 判断某个元素是否加载到dom树里,该元素不一定可见
EC.url_contains('')(driver) # 判断当前url是否包含给定的字符串
EC.url_matches('')(driver) # 匹配URL
EC.url_to_be('')(driver) # 精确匹配
EC.url_changes('')(driver) # 不完全匹配
EC.visibility_of_element_located('')(driver) # 判断某个元素是否可见,可见代表元素非隐藏元素
EC.visibility_of('')(driver) # 跟上面一样,不过是直接传定位到的element
EC.presence_of_all_elements_located('')(driver) # 判断是否至少有1个元素存在于dom树中
EC.visibility_of_any_elements_located('')(driver) # 判断是否至少一个元素可见,返回列表
EC.visibility_of_all_elements_located('')(driver) # 判断是否所有元素可见,返回列表
EC.text_to_be_present_in_element('')(driver) # 判断元素中的text是否包含了预期的字符串
EC.text_to_be_present_in_element_value('')(driver) # 判断元素中value属性是否包含预期的字符串
EC.frame_to_be_available_and_switch_to_it('')(driver) # 判断该frame是否可以switch进去
EC.invisibility_of_element_located('')(driver) # 判断某个元素是否不存在于dom树或不可见
EC.element_to_be_clickable('')(driver) # 判断某个元素中是否可见并且可点击
EC.staleness_of('')(driver) # 等某个元素从dom树中移除
EC.element_to_be_selected('')(driver) # 判断某个元素是否被选中了,一般用在下拉列表
EC.element_located_to_be_selected('')(driver) # 判断元组中的元素是否被选中
EC.element_selection_state_to_be('')(driver) # 判断某个元素的选中状态是否符合预期
EC.element_located_selection_state_to_be('')(driver) # 跟上面一样,只不过是传入located
EC.number_of_windows_to_be('')(driver) # 判断窗口中的数字是否符合预期
EC.new_window_is_opened('')(driver) # 判断新的窗口是否打开
EC.alert_is_present('')(driver) # 判断页面上是否存在alert
selenium的下拉选择框。我们通常会遇到两种下拉框,一种使用的是html的标签select,另一种是使用input标签做的假下拉框。
后者我们通常的处理方式与其他的元素类似,点击或使用JS等。
而对于前者,selenium给了有力的支持,就是Select类。
进行测试的网站:http://sahitest.com/demo/selectTest.htm
语法
from selenium.webdriver.support.select import Select
from selenium.webdriver.common.by import By
element = driver.find_element(By.ID,'ls_fastloginfield')
s = Select(element) # 实例化
res = s.all_selected_options # 全部选中子项
res1 = s.options # 全部子项
print(res)
print(res1)
下拉选择控件最常用的2种选择方式:Select
方式一:通过下标选择值,下标开始值:0
from selenium.webdriver.support.select import Select
# 单位-默认选择第一个
# 这里的get_visible_element是我封装的函数,类似于 driver.find_element(By.ID,id)
ele = get_visibile_element(driver,'id','inputItemKind.prpCitemKind.targetPriceUnit')
s = Select(ele)
s.select_by_index(0) # 通过下标选择值
方式二:通过文本内容来选择值
from selenium.webdriver.support.select import Select
# 单位-默认选择第一个
ele = get_visibile_element(driver,"id","opinionType")
s = Select(ele)
s.select_by_visible_text(val) # 通过文本内容来选择值
所有Select操作
# 选择选项
s.select_by_index(index) # 根据索引选择
s.select_by_value(value) # 根据值来选择
s.select_by_visible_text(text) # 根据选项可见文本
# 取消选择(反选)
s.deselect_by_index(index) # 取消选择对应index索引的选项
s.deselect_by_value(value) # 取消选择对应value值的选项
s.deselect_by_visible_text(text) # 取消选择对应可见文本的选项
s.deselect_all() # 取消所有选择
# 返回选项
s.first_selected_option # 返回第一个选中的子项,也是下拉框的默认值
s.all_selected_options # 返回全部选中的子项
s.options # 返回全部子项
实战
实例1
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.select import Select
from selenium.webdriver.common.by import By
import time
driver = webdriver.Chrome()
path = r'E:\web\select.html'
driver.get(path)
# 通过显示等待的方法判断元素是否出现
WebDriverWait(driver,10).until(EC.visibility_of_element_located((By.NAME,"anjing")))
anjing = driver.find_element(By.NAME,'anjing')
s = Select(select)
# 根据下标进行选择,从0开始
s.select_by_index(1)
time.sleep(2)
# 根据value的值选择anjing
s.select_by_value('daily')
time.sleep(2)
# 根基text选择
s.select_by_visible_text('关注了吗?')
time.sleep(2)
# 判断选择是否预期
WebDriverWait(driver,20).until(EC.element_located_to_be_selected((By.XPATH,'//*[contains(text(),"关注了")]')))
实例2
import unittest
import time
from selenium import webdriver
from selenium.webdriver.support.ui import Select
class SelectStudy(unittest.TestCase):
def setUp(self):
# 创建一个Chrome WebDriver的实例
self.driver = webdriver.Chrome()
# 选择页面第一个下拉框,依次选择值O1-O3
def test_selectO1ToO3(self):
driver = self.driver
driver.get('http://sahitest.com/demo/selectTest.htm')
# 实例化Select
s1 = Select(driver.find_element(By.ID,'s1Id'))
# 查看选择框的默认值
print s1.first_selected_option.text
# 选择第二个选项o1
s1.select_by_index(1)
time.sleep(3)
# 为了方便查看效果,可以加上等待时间
time.sleep(3)
# 选择value="o2"的项,value是option标签的一个属性值,并不是显示在下拉框中的值
s1.select_by_value("o2")
# 查看选中选择框的默认值
print s1.first_selected_option.text
time.sleep(3)
# 选择text="o3"的值,即在下拉时我们可以看到的文本,visible_text是在option标签中间的值,是显示在下拉框的值
s1.select_by_visible_text("o3")
time.sleep(3)
# 反选操作,包括取消某个值和全部取消
def test_cancel_select(self):
driver = self.driver
driver.get('http://sahitest.com/demo/selectTest.htm')
s4 = Select(driver.find_element(By.ID,'s4Id'))
# 全选
for option in s4.options:
if not option.is_selected():
print option.text
s4.select_by_visible_text(option.text)
time.sleep(3)
# 根据index取消选中
s4.deselect_by_index(0)
time.sleep(3)
# 根据value取消选中
s4.deselect_by_value("o1val")
time.sleep(5)
# 根据标签文本选中
s4.deselect_by_visible_text("o2")
time.sleep(5)
# 全选
for option in s4.options:
if not option.is_selected():
s4.select_by_visible_text(option.text)
time.sleep(3)
# 取消选中所有选项
s4.deselect_all()
# 查看选中项目
"""
输出结果为:
o1
o2
With spaces
With nbsp
"""
def test_view_selection(self):
driver = self.driver
driver.get('http://sahitest.com/demo/selectTest.htm')
s4 = Select(driver.find_element(By.ID,'s4Id'))
# 查看选择框的默认值
s4.select_by_index(1)
s4.select_by_value("o2val")
s4.select_by_visible_text("With spaces")
s4.select_by_value("o4val")
for select in s4.all_selected_options:
print select.text
def tearDown(self):
self.driver.close()
if __name__ == "__main__":
unittest.main()
在Selenium中,get() 方法会在网页框架加载结束后结束执行,此时如果获取page_source,可能并不是浏览器完全加载完成的页面,如果某些页面有额外的Ajax请求,我们在网页源代码中也不一定能成功获取到,这里就需要延时等待一定时间,确保节点已经加载处理好。
显式等待 和 隐式等待
首先指定一个等待条件,并且再指定一个最长等待时间,然后在这个时间段内进行判断是否满足等待条件,如果成立就会立即返回,如果不成立,就会一直等待,直到等待你指定的最长等待时间,如果还是不满足,就会抛出异常,如果满足了就会正常返回
隐式等待 则是我们设置时间,然后程序去找元素,期间会不断刷新页面(轮询查看页面/元素是否加载完成),超时仍没找到就抛异常。
使用隐式等待执行测试的时候,如果 Selenium 没有在DOM中找到节点,将继续等待,超出设定时间后,则抛出找不到节点的异常, 换句话说,当查找节点而节点并没有立即出现的时候,隐式等待将等待一段时间再查找DOM ,默认的时间是0
本质上是driver的设置项,设置一次全局生效
强制等待 是用 time.sleep()
强制让浏览器等待,不管当前操作是否完成
这里有个常用的模块专门用来实现显示等待和隐式等待的,它就是”wait“
😊 显式等待使用方法:
WebDriverWait(driver, 超时时间, 调用频率, 要忽略的异常).until(要执行的方法, 超时时返回的错误信息)
WebDriverWait 结合 ExpectedCondition 是实现显式等待的一种方式
from selenium.webdriver.support.ui import WebDriverWait
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
driver = webdriver.Chrome(executable_path=r'C:\Users\Administrator\AppData\Local\Google\Chrome\Application\chromedriver.exe')
driver.get('https://www.baidu.com/')
# 显示等待
wait = WebDriverWait(driver, timeout=True, poll_frequency=10)
input = wait.until(EC.presence_of_element_located((By.ID, 'q'))) # 确认元素是否已经出现了
button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.btn-search'))) # 确认元素是否是可点击
time.sleep(3)
driver.close()
driver.quit()
引入WebDriverWait这个对象,指定最长等待时间,然后调用until() 方法,(可以选择传入要等待条件excpeted_conditions比如:这里传入presence_of_element_located这个条件,代表节点出现的意思,其参数就是定位节点元组)也可以自定义函数
解释:
上述的例子中的条件:
EC.presence_of_element_located()是确认元素是否已经出现了
EC.element_to_be_clickable()是确认元素是否是可点击的
WebDriverWait()类
until()方法:条件成立为真
until_not()方法:条件不成立为真也就是没有指定的元素为真
expected_conditions()类
element_located_selection_state_to_be():判断一个元素的状态是否是给定的选择状态
element_selection_state_to_be():判断给定的元素是否被选中
element_located_to_be_selected():期望某个元素处于被选中状态
element_to_be_selected():期望某个元素处于选中状态
element_to_be_clickable():判断元素是否可见并且能被单击,条件满足返回页面元素对象,否则返回Flase
frame_to_be_available_and_switch_to_it():判断frame是否可用
invisibility_of_element_located():希望某个元素不可见或者不存在DOM中,满足条件返回True,否则返回定位到的元素对象
visibility_of_element_located():希望某个元素出现在DOM中并且可见,满足条件返回该元素的页面元素对象
visibility_of():希望某个元素出现在页面的DOM中,并且可见,满足条件返回该元素的页面元素对象
visibility_of_any_elements_located():希望某个元素出现在DOM中并且可见
presence_of_all_elements_located():判断页面至少有一个如果元素出现,如果满足条件,返回所有满足定位表达式的压面元素
presence_of_element_located(locator):判断某个元素是否存在DOM中,不一定可见,存在返回该元素对象
staleness_of(webelement):判断一个元素是否仍在DOM中,如果在规定时间内已经移除返回True,否则返回Flase
text_to_be_present_in_element():判断文本内容test是否出现在某个元素中,判断的是元素的text
text_to_be_present_in_element_value():判断text是否出现在元素的value属性值中
title_contains():判断页面title标签的内容包含partial_title,只需要部分匹配即可,包含返回True,不包含返回Flase
title_is():判断页面title内容是与传入的title_text内容完全匹配,匹配返回True,否则返回Flase
😊 隐式等待使用方法
隐式等待很简单,就一行代码,如下:
# 隐式等待
driver.implicitly_wait(10)
它的等待时间适用于全局的环境,也就是任何地方找不到某个元素,它都将发挥作用,如果找得到,则不会产生作用。
😊 强制等待使用方法
import time
time.sleep(3)