• Python爬虫|基础知识点详细汇总(requests、urllib、re、bs4、xpath、多线程、协程、数据保存、selenium)


    爬虫总结

    一、静态页面 html 代码的获取

    1. 请求数据

    requests

    (1) 基本使用
    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类型   (下载图片可以如此)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    参数

    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个控制访问的参数
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    对响应内容的操作

    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表示成功
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    (2) Requests进阶:使用Session

    为什么要用 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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    需要客户端登录的网站

    客户端登录 --> 服务器返回 cookie  给客户端
    客户端带着 cookie 再去请求数据 --> 服务器知道你是谁
    
    • 1
    • 2
    # 需要登录的网站一般可以这样进行登录
    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)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    (3) 防盗链处理
    【★注意】很多 页面源代码 和 开发者工具的Elements页面代码 是存在偏差的
    页面源代码是不会改变的
    Elements页面代码是实时的网页效果的源代码,很多内容可能是Js渲染出来的内容,在网页源代码中是找不到的!
    
    • 1
    • 2
    • 3
    '''
        防盗链
            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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    (4) 代理ip
    # 原理:通过第三方的一个机器去发送请求(借用他人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)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    urllib & urllib3

    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)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    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类型   (下载图片可以如此)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    selenium (webdriver)

    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类型
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2. 节点获取 / 内容匹配

    ① re

    正则表达式

    1. 语法
    '''
        正则:用来匹配字符串的一门表达式语言
        测试的方法: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()  # 必须在设定了组名的时候才能使用
    '''
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    # 一、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'))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    # ★ 例子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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    2. 实战
    # 测试正则表达式用于爬虫
    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())  # 字典格式打印所有匹配的组
    

    ② bs4

    漂亮的汤

    1. 语法
    '''
    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))  # 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    # 解析内容: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. 实战
    # 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,方便后面用正则表达式
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    xpath

    1. 语法
    正则表达式 re模块 适合一长段凌乱的字符串的匹配
    xpah lxml-->etree 适合层次鲜明的节点的层层获取
    
    • 1
    • 2
    '''
        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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    更多内容: https://blog.csdn.net/qq_50854790/article/details/123610184

    2. 实战
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    二、多线程 和 线程池

    1. 多线程

    '''
        多线程
            让程序能同时执行多个任务
            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和主线程会有交叉,说明是多线程并发
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    2. 线程池

    处理一些庞大的、比较慢的程序时适合用多线程,如果是秒完成的程序,单单针对速度的话,用多线程提速并不明显,甚至还会变慢

    处理一些请求服务器的操作一般用多线程都比较快

    '''
        线程池
            自动管理线程
            若指定管理 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 会等待线程池内的任务全部执行完毕,才会向下执行(守护)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    3. 线程实战

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    三、协程

    协程
        当线程遇见了IO操作的时候,可以选择性地切换到其他任务上(提高CPU利用率)
    
        (多任务异步操作) 在单线程条件下
        在微观上是一个任务一个任务进行切换,切换条件一般就是IO操作
        在宏观上,我们能看到的其实是多个任务一起在执行
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • async
      • 协程函数:定义函数时加上async修饰,即async def func()
        • 用 async 可以把普通函数升级为 “异步函数”,注: 一个异步的函数, 有个更标准的称呼, 我们叫它 “协程” (coroutine)
        • 异步函数(协程)的特点是能在函数执行过程中断挂起,去执行其他异步函数,等到挂起条件(假设挂起条件是sleep(5))消失后,也就是5秒到了再回来执行
      • 协程对象:执行协程函数得到的对象
        :执行协程函数得到协程对象,函数内部代码不会执行

    • await
      • await + 可等待对象(协程对象,Future,Task对象(IO等待))
      • 等待到对象的返回结果,才会继续执行后续代码

    线程中的不足

    def func():
        print('我爱周杰伦')
        time.sleep(3)  # 让当前线程处于阻塞状态,CPU不为此线程工作
        # input()  # 程序也是处于阻塞状态  # 一般的IO操作都会使线程处于阻塞状态
        # requests.get(url)  # 在网络请求返回数据之前,也是处于阻塞状态
        print('我真的爱周杰伦')
    if __name__ == '__main__':
        func()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    1. 协程程序基本语法

    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() 并没有获取到事件循环,所以导致报错:在主线程没有事件循环。简单说,就是在同一线程中,第二个协程受到第一个的干扰。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    2. 协程常用的库

    import asyncio   # io 库
    import aiohttp   # 类似 requests,用于发送请求
    import aiofiles  # 异步 files 管理库
    
    • 1
    • 2
    • 3
    • asyncio
    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())  # 将内部代码交给事件循环执行(添加任务,直至所有任务执行完成)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • aiohttp
    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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • aiofiles
     async with aiofiles.open(file=f'img/{name}', mode='wb') as f:
            await f.write(await resp.content.read())  # 读取内容试异步的,需要 await 挂起
    
    • 1
    • 2

    3. 协程实战

    # 在爬虫领域的应用
    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)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    四、保存数据

    1. 保存到 Excel xls (xlwt)

    语法

    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')  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    实战

    # 保存数据到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)  # 保存数据表
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    2. 保存到 数据库 db (sqlite3)

    语法

    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('查询完毕!')
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    实战

    # 创建初始化数据库
    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()
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    3. 爬虫数据获取的问题

    # 用 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数据)
    
    • 1
    • 2
    • 3
    • 4

    拿到网页源代码时,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)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    五、动态数据的获取操作

    在爬取网页数据的时候,有时候会出现获取数据为空的情况(在路径、代码没问题前提下),
    这种就很有可能是爬取的数据为动态加载的数据

    有很多网页内容是js渲染或者其他手段弄出来的,这些在网页源代码中都是找不到的,
    之前的方案数都是抓包获取这些后来数据的网页链接,再拿到这些数据,
    但有些数据经过加密等一系列复杂操作,我们解密、加密操作起来十分复杂!
    现在想能不能让程序连接到浏览器,让浏览器来完成各种复杂的操作,把那些脚本都跑完了
    (即浏览器拿到这些数据,加载完毕后),
    我们直接拿到最后的结果数据 ?
    可以,那就是使用 selenium !
    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
    selenium : 本是一种自动化测试工具
    selenium 可以打开浏览器,然后像人一样去操作浏览器
    ★ 程序员可以通过selenium,直接提取网页上的各种信息
    因为对于 selenum 来说,各种网页信息都是透明的
    selenium 它本身也是浏览器,加密、反爬的东西无论如何也得让浏览器正常运行
    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

    1. selenium webdriver 驱动

    自动化测试框架 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类型
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    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
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    2. selenium 匹配节点及其内容

    😊【第二部分:匹配节点及其内容】
    # ★此后获取节点啥的和以往一样,可以用 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能拿到一些文本
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    注意:如果某些元素是ajax或者其他方式加载出来的,在selenium匹配节点的时候,很可能因为某些操作(如:点击某按钮后,页面会局部刷新,加载出来一些信息)后,导致节点还没加载出来,还没获取到,代码就已经往下执行了(因为局部刷新需要时间,而代码执行太快),此时最简单的方法就是time.sleep() 让程序睡一会儿,再往下执行,或者采用下文提到的延时等待方法。

    3. selenium 执行js操作浏览器

    😊【第三部分:可以执行js操作浏览器】  
    # 可以让 dricer 执行 js代码,来操作BOM,操作页面
    js = '''
    	console.log('hello,world!')
    '''
    driver.execute_script(js)  # 可以让 dricer 执行 js代码,来操作BOM,操作页面
    
    driver.close()  # 关闭当前页面(如果浏览器只剩下一页,则关闭整个dirver)
    driver.quit()  # 关闭dirver
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    常用的js代码

    ① 窗口下滑

    # 窗口下滑
    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中定时器的总时间多一点
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    ② 清理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()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    4. selenium 浏览器的常用方法

    参考1

    参考2:非常详细

    参考3

    ① 获取本页面URL

    driver.current_url
    
    • 1

    ② 获取日志

    driver.log_types  # 获取当前日志类型
    driver.get_log('browser')# 浏览器操作日志
    driver.get_log('driver') # 设备日志
    driver.get_log('client') # 客户端日志
    driver.get_log('server') # 服务端日志
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ③ 窗口操作

    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  #返回当前会话中的所有窗口的句柄
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    ④ 设置延时

    driver.set_script_timeout(5)  # 设置脚本延时五秒后执行
    driver.set_page_load_timeout(5)  # 设置页面读取时间延时五秒
    
    • 1
    • 2

    ⑤ 关闭

    driver.close() #关闭当前标签页
    driver.quit() #关闭浏览器并关闭驱动
    
    • 1
    • 2

    ⑥ 打印网页源代码

    driver.page_source
    
    • 1

    ⑦ 屏幕截图操作

    driver.save_screenshot('1.png')#截图,只支持PNG格式
    driver.get_screenshot_as_png() #获取当前窗口的截图作为二进制数据
    driver.get_screenshot_as_base64() #获取当前窗口的截图作为base64编码的字符串
    
    • 1
    • 2
    • 3

    ⑧ 前进后退刷新

    driver.forward()  # 页面前进
    driver.back()  # 页面后退
    driver.refresh()  # 页面刷新
    
    • 1
    • 2
    • 3

    ⑨ 执行JS代码

    在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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    ⑩ Cookies操作

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    ⑪ 获取标题内容

    driver.title
    
    • 1

    ⑫ 获取当前浏览器名

    driver.name
    
    • 1

    ⑬ 全局超时时间

    driver.implicitly_wait(5)
    
    • 1

    5. selenium 元素操作的常用方法

    对我们找到的元素进行二次操作,不仅可以再次选择子元素还可以进行其它操作。如下:

    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') # 上传文件
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    6. 键盘鼠标操作 (动作API)

    动作API是网上资料比较少的,因为之前的查找元素,调用click等已经可以解决很多的问题了,在翻看官方文档时,发现selenium还支持动作API来模拟动作。
    动作API分为四个部分,分别是键盘、鼠标、笔、滚轮。

    一些交互动作都是针对某个节点执行的,比如:对于输入框,我们就调用它的输入文字和清空文字方法;对于按钮,就调用它的点击方法,还有另外一些操作,他们没有特定的对象,比如鼠标的拖拽,键盘的按键等,这些动作用另一种方式来执行,那就是动作链

    比如:现在实现一个节点的拖拽操作,将某个节点从一处拖拽到另外一处

    • ActionChains 类常用于模拟鼠标、键盘的行为,是自动执行低级交互的一种方式,例如:鼠标单击、移动、拖拽,键盘操作,文本操作等行为
    • 如果在一个用例中只有一两个动作,那么用之前讲过的简单版的就可以了,如果动作很复杂,那么可以使用动作链了。

    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()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ① 鼠标操作

    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)       # 在某个元素位置松开鼠标左键
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    鼠标操作实例

    鼠标定义的5种按键

    • 0——鼠标左键
    • 1——鼠标中键
    • 2——鼠标右键
    • 3——X1(后退键)
    • 4——X2(前进键)

    鼠标双击

    clickable = driver.find_element(By.ID, "clickable")
    ActionChains(driver)\
        .double_click(clickable)\
        .perform()
    
    • 1
    • 2
    • 3
    • 4

    鼠标右击

    clickable = driver.find_element(By.ID, "clickable")
    
    ActionChains(driver)\
        .context_click(clickable)\
        .perform()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    按下鼠标3键

    动作行为构造类ActionBuilder,将行为绑定到WebDriver对象上
    selenium 中的鼠标操作类 -- ActionChains类,其操作鼠标行为都是使用的 ActionBuilder类。
    
    • 1
    • 2
    action = ActionBuilder(driver)
    action.pointer_action.pointer_down(MouseButton.BACK)
    action.pointer_action.pointer_up(MouseButton.BACK)
    action.perform()
    
    • 1
    • 2
    • 3
    • 4

    按下鼠标4键

    action = ActionBuilder(driver)
    action.pointer_action.pointer_down(MouseButton.FORWARD)
    action.pointer_action.pointer_up(MouseButton.FORWARD)
    action.perform()
    
    • 1
    • 2
    • 3
    • 4

    鼠标移动到元素上

    hoverable = driver.find_element(By.ID, "hover")
    ActionChains(driver)\
        .move_to_element(hoverable)\
        .perform()
    
    • 1
    • 2
    • 3
    • 4

    鼠标位移
    就是通过像素点来进行位移操作。

    从元素左顶边进行位移

    mouse_tracker = driver.find_element(By.ID, "mouse-tracker")
    ActionChains(driver)\
        .move_to_element_with_offset(mouse_tracker, 8, 11)\
        .perform()
    
    • 1
    • 2
    • 3
    • 4

    从当前窗口左上角位移

    action = ActionBuilder(driver)
    action.pointer_action.move_to_location(8, 12)
    action.perform()
    
    • 1
    • 2
    • 3

    从当前鼠标位置位移

    ActionChains(driver)\
        .move_by_offset( 13, 15)\
        .perform()
    
    • 1
    • 2
    • 3

    拖拽元素
    该方法首先单击并按住源元素,移动到目标元素的位置,然后释放鼠标。

    draggable = driver.find_element(By.ID, "draggable")
    droppable = driver.find_element(By.ID, "droppable")
    ActionChains(driver)\
        .drag_and_drop(draggable, droppable)\
        .perform()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    通过位移拖拽

    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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    滚轮(只有谷歌内核浏览器生效)
    滚动到某元素位置

    iframe = driver.find_element(By.TAG_NAME, "iframe")
    ActionChains(driver)\
        .scroll_to_element(iframe)\
        .perform()
    
    • 1
    • 2
    • 3
    • 4

    定量滚动

    footer = driver.find_element(By.TAG_NAME, "footer")
    delta_y = footer.rect['y']
    ActionChains(driver)\
        .scroll_by_amount(0, delta_y)\
        .perform()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    从一个元素滚动指定量

    iframe = driver.find_element(By.TAG_NAME, "iframe")
    scroll_origin = ScrollOrigin.from_element(iframe)
    ActionChains(driver)\
        .scroll_from_origin(scroll_origin, 0, 200)\
        .perform()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    从一个元素滚动,并指定位移

    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()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    从一个元素的原点位移

    ActionChains(driver)\
        .scroll_from_origin(scroll_origin, 0, 200)\
        .perform()
    
    • 1
    • 2
    • 3

    ② 键盘操作

    键盘代码完整列表

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    键盘操作实例

    按下某键,以输入shift+abc为例

    ActionChains(driver)\
        .key_down(Keys.SHIFT)\
        .send_keys("abc")\
        .perform()
    
    • 1
    • 2
    • 3
    • 4

    弹起某键,以输入shift+a和shift+b为例

    ActionChains(driver)\
        .key_down(Keys.SHIFT)\
        .send_keys("a")\
        .key_up(Keys.SHIFT)\
        .send_keys("b")\
        .perform()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    浏览器输入某串字符(不指定元素)

    ActionChains(driver)\
        .send_keys("abc")\
        .perform()
    
    • 1
    • 2
    • 3

    指定元素输入字符串

    text_input = driver.find_element(By.ID, "textInput")
    ActionChains(driver)\
        .send_keys_to_element(text_input, "abc")\
        .perform()
    
    • 1
    • 2
    • 3
    • 4

    复制和粘贴

    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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    ③ 其他操作

    perform()        # 执行所有操作
    pause(seconds)   # 暂停所有输入(指定持续时间以秒为单位)
    reset_actions()  # 结束已经存在的操作并重置
    
    • 1
    • 2
    • 3

    实例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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    暂停(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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    释放所有动作
    当前有动作执行时,可以使用以下方法停止这些动作,

    ActionBuilder(driver).clear_actions()
    
    • 1

    7. 选项卡管理 (窗口 window)

    通过执行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')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    8. 切换 Frame

    网页中有一种节点叫作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")>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    9. Alert

    在弹窗处理中,我们会遇到三种情况,如下:

    浏览器弹出框
    新窗口弹出框
    人为弹出框

    ② 浏览器弹出框

    首先说说浏览器弹出框,想必大家对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  # 获取弹窗内容
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    这里我们应对每种情况它上面的方法的对应位置都是会有所变化的,所以我们需要根据具体情况来进行操作,而且还可以使用另一种方法,如下:

    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   # 获取弹窗内容
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    :该类方法必须在有弹框的情况下才有作用,如没有会报错。

    ② 新窗口弹出框

    上面就是浏览器弹出框的处理方法了,如果是 新窗口弹出 的话那么就不一样了,我们需要通过句柄来定位

    具体用法如下:

    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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    :如果有多个窗口,当你关闭了当前窗口想切换到另一个窗口,你需要把没关闭的窗口切换成当前活动窗口,因为Selenium是不会为你做这件事的。

    ③ 人为弹出框

    这类弹出框是我们自己开发的,一般都是使用 div 包裹一些其它的元素标签然后形成一个整体,当我们触发某个事件的时候就会出现,否则消失。这种弹出框使用我们的众多find前缀的方法就能遍历到,很方便,这里不一一细说。

    10. 判断

    在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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    常用的 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    11. 下拉选择框

    selenium的下拉选择框。我们通常会遇到两种下拉框,一种使用的是html的标签select,另一种是使用input标签做的假下拉框。
    后者我们通常的处理方式与其他的元素类似,点击或使用JS等。
    而对于前者,selenium给了有力的支持,就是Select类。
    进行测试的网站:http://sahitest.com/demo/selectTest.htm

    参考1

    语法

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    下拉选择控件最常用的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)  # 通过下标选择值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    方式二:通过文本内容来选择值

    from selenium.webdriver.support.select import Select
    
    # 单位-默认选择第一个
    ele = get_visibile_element(driver,"id","opinionType")
    s = Select(ele)
    s.select_by_visible_text(val)  # 通过文本内容来选择值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    所有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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    实战

    实例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(),"关注了")]')))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    实例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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95

    12. 延时等待

    在Selenium中,get() 方法会在网页框架加载结束后结束执行,此时如果获取page_source,可能并不是浏览器完全加载完成的页面,如果某些页面有额外的Ajax请求,我们在网页源代码中也不一定能成功获取到,这里就需要延时等待一定时间,确保节点已经加载处理好。

    显式等待 和 隐式等待

    • 显式等待 就是浏览器在我们设置的时间内每隔一段时间(该时间一般都很短,默认为0.5秒,也可以自定义),执行自定义的程序判断条件,如果判断条件成立,就执行下一步,否则继续等待,直到超过设定的最长等待时间,如果没在规定时间内成立,就抛出TimeOutEcpection的异常信息。

    首先指定一个等待条件,并且再指定一个最长等待时间,然后在这个时间段内进行判断是否满足等待条件,如果成立就会立即返回,如果不成立,就会一直等待,直到等待你指定的最长等待时间,如果还是不满足,就会抛出异常,如果满足了就会正常返回

    • 隐式等待 则是我们设置时间,然后程序去找元素,期间会不断刷新页面(轮询查看页面/元素是否加载完成),超时仍没找到就抛异常。

      使用隐式等待执行测试的时候,如果 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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    引入WebDriverWait这个对象,指定最长等待时间,然后调用until() 方法,(可以选择传入要等待条件excpeted_conditions比如:这里传入presence_of_element_located这个条件,代表节点出现的意思,其参数就是定位节点元组)也可以自定义函数

    解释:

    • driver: 当前实例的对象
    • timeout:最长等待的时间
    • poll_frequency:多少秒检测一次 默认0.5
    • EC.presence_of_element_located ((By.CLASS_NAME, “topic-item”)):判断class_name=topic-item元素是否已经成功加载。
    • 忽略异常:如果在调用until或until_not的过程中抛出这个元组中的异常,则不中断代码,继续等待,如果抛出的是这个元组外的异常,则中断代码,抛出异常。默认只有NoSuchElementException。

    上述的例子中的条件:

    ​ EC.presence_of_element_located()是确认元素是否已经出现了

    ​ EC.element_to_be_clickable()是确认元素是否是可点击的

    WebDriverWait()类

    until()方法:条件成立为真
    until_not()方法:条件不成立为真也就是没有指定的元素为真
    
    • 1
    • 2

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    😊 隐式等待使用方法

    隐式等待很简单,就一行代码,如下:

    # 隐式等待
    driver.implicitly_wait(10)
    
    • 1
    • 2

    它的等待时间适用于全局的环境,也就是任何地方找不到某个元素,它都将发挥作用,如果找得到,则不会产生作用。

    😊 强制等待使用方法

    import time
    time.sleep(3)
    
    • 1
    • 2
  • 相关阅读:
    [ 隧道技术 ] 反弹shell的集中常见方式(一)nc反弹shell
    反序列化漏洞及漏洞复现
    括号有效配对题型问题解法
    面试准备-中文面试问答(非技术)
    管件注塑过程中采用串级PID控制法实现高压压力精密控制的解决方案
    英语翻译软件-批量自动免费翻译软件支持三方接口翻译
    通过mysql更改wordpress密码(md5类型)
    python如何获取时间和格式化时间和日期 ?
    数据仓库面试题——介绍下数据仓库
    基于JAVA一起组局校园交友平台计算机毕业设计源码+系统+数据库+lw文档+部署
  • 原文地址:https://blog.csdn.net/Syc1102g/article/details/126048151