selenium用法详解【从入门到实战】【Python爬虫】【终极篇】

举报
Python新视野 发表于 2022/04/30 17:08:20 2022/04/30
【摘要】 大家好,我是丁小杰,今天来学习Selenium咯~书接上文 弹窗处理JavaScript 有三种弹窗 alert(确认)、confirm(确认、取消)、prompt(文本框、确认、取消)。处理方式:先定位(switch_to.alert自动获取当前弹窗),再使用 text、accept、dismiss、send_keys 等方法进行操作方法描述text获取弹窗中的文字accept接受(确认)...



大家好,我是丁小杰,今天来学习Selenium咯~书接上文

弹窗处理

JavaScript 有三种弹窗 alert(确认)、confirm(确认、取消)、prompt(文本框、确认、取消)。

处理方式:先定位(switch_to.alert自动获取当前弹窗),再使用 textacceptdismisssend_keys 等方法进行操作

方法 描述
text 获取弹窗中的文字
accept 接受(确认)弹窗内容
dismiss 解除(取消)弹窗
send_keys 发送文本至警告框

这里写一个简单的测试页面,其中包含三个按钮,分别对应三个弹窗。

<!DOCTYPE html>
<html lang="en">

<head>
</head>

<body>
    <button id="alert">alert</button>
    <button id="confirm">confirm</button>
    <button id="prompt">prompt</button>

    <script type="text/javascript">
        const dom1 = document.getElementById("alert")
        dom1.addEventListener('click', function(){
           alert("alert hello")
        })

                const dom2 = document.getElementById("confirm")
        dom2.addEventListener('click', function(){
            confirm("confirm hello")
        })

                const dom3 = document.getElementById("prompt")
        dom3.addEventListener('click', function(){
            prompt("prompt hello")
        })
    </script>
</body>
</html>


下面使用上面的方法进行测试。为了防止弹窗操作过快,每次操作弹窗,都使用 sleep 强制等待一段时间。

from selenium import webdriver
from pathlib import Path
from time import sleep


driver = webdriver.Firefox()
driver.get('file:///' + str(Path(Path.cwd(), '弹窗.html')))

sleep(2)

# 点击alert按钮
driver.find_element_by_xpath('//*[@id="alert"]').click()
sleep(1)
alert = driver.switch_to.alert
# 打印alert弹窗的文本
print(alert.text)
# 确认
alert.accept()

sleep(2)

# 点击confirm按钮
driver.find_element_by_xpath('//*[@id="confirm"]').click()
sleep(1)
confirm = driver.switch_to.alert
print(confirm.text)
# 取消
confirm.dismiss()

sleep(2)

# 点击confirm按钮
driver.find_element_by_xpath('//*[@id="prompt"]').click()
sleep(1)
prompt = driver.switch_to.alert
print(prompt.text)
# 向prompt的输入框中传入文本
prompt.send_keys("Dream丶Killer")
sleep(2)
prompt.accept()

'''输出
alert hello
confirm hello
prompt hello
'''

注:细心地读者应该会发现这次操作的浏览器是 Firefox ,为什么不用 Chrome 呢?原因是测试时发现执行 promptsend_keys 时,不能将文本填入输入框。尝试了各种方法并查看源码后确认不是代码的问题,之后通过其他渠道得知原因可能是 Chrome 的版本与 selenium 版本的问题,但也没有很方便的解决方案,因此没有继续深究,改用 Firefox 可成功运行。这里记录一下我的 Chrome 版本,如果有大佬懂得如何在 Chrome 上解决这个问题,请在评论区指导一下,提前感谢!
selenium:3.141.0
Chrome:94.0.4606.71



上传 & 下载文件

上传文件

常见的 web 页面的上传,一般使用 input 标签或是插件(JavaScriptAjax),对于 input 标签的上传,可以直接使用 send_keys(路径) 来进行上传。
先写一个测试用的页面。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <input type="file" name="">
</body>
</html>


下面通过 xpath 定位 input 标签,然后使用 send_keys(str(file_path) 上传文件。

from selenium import webdriver
from pathlib import Path
from time import sleep


driver = webdriver.Chrome()
file_path = Path(Path.cwd(), '上传下载.html')
driver.get('file:///' + str(file_path))

driver.find_element_by_xpath('//*[@name="upload"]').send_keys(str(file_path))

下载文件

Chrome浏览器

Firefox 浏览器要想实现文件下载,需要通过 add_experimental_option 添加 prefs 参数。

  • download.default_directory:设置下载路径。
  • profile.default_content_settings.popups:0 禁止弹出窗口。

下面测试下载搜狗图片。指定保存路径为代码所在路径。

from selenium import webdriver 

prefs = {'profile.default_content_settings.popups': 0,
         'download.default_directory': str(Path.cwd())}
option = webdriver.ChromeOptions() 
option.add_experimental_option('prefs', prefs)
driver = webdriver.Chrome(options=option)
driver.get("https://pic.sogou.com/d?query=%E7%83%9F%E8%8A%B1&did=4&category_from=copyright")
driver.find_element_by_xpath('/html/body/div/div/div/div[2]/div[1]/div[2]/div[1]/div[2]/a').click()

driver.switch_to.window(driver.window_handles[-1])
driver.find_element_by_xpath('./html').send_keys('thisisunsafe')

代码最后两句猜测有理解什么意思的吗~,哈哈,实际作用是当你弹出像下面的页面 “您的连接不是私密连接” 时,可以直接键盘输入 “thisisunsafe” 直接访问链接。那么这个键盘输入字符串的操作就是之间讲到的 send_keys,但由于该标签页是新打开的,所以要通过 switch_to.window() 将窗口切换到最新的标签页。

Firefox浏览器

Firefox 浏览器要想实现文件下载,需要通过 set_preference 设置 FirefoxProfile() 的一些属性。

  • browser.download.foladerList:0 代表按浏览器默认下载路径;2 保存到指定的目录。
  • browser.download.dir:指定下载目录。
  • browser.download.manager.showWhenStarting:是否显示开始,boolean 类型。
  • browser.helperApps.neverAsk.saveToDisk:对指定文件类型不再弹出框进行询问。
  • HTTP Content-type对照表https://www.runoob.com/http/http-content-type.html
from selenium import webdriver
import os
fp = webdriver.FirefoxProfile()
fp.set_preference("browser.download.dir",os.getcwd())
fp.set_preference("browser.download.folderList",2)
fp.set_preference("browser.download.manager.showhenStarting",True)
fp.set_preference("browser.helperApps.neverAsk.saveToDisk","application/octet-stream")

driver = webdriver.Firefox(firefox_profile = fp)
driver.get("https://pic.sogou.com/d?query=%E7%83%9F%E8%8A%B1&did=4&category_from=copyright")
driver.find_element_by_xpath('/html/body/div/div/div/div[2]/div[1]/div[2]/div[1]/div[2]/a').click()

运行效果与 Chrome 基本一致,这里就不再展示了。



cookies操作

cookies 是识别用户登录与否的关键,爬虫中常常使用 selenium + requests 实现 cookie持久化,即先用 selenium 模拟登陆获取 cookie ,再通过 requests 携带 cookie 进行请求。

webdriver 提供 cookies 的几种操作:读取、添加删除。

  • get_cookies:以字典的形式返回当前会话中可见的 cookie 信息。
  • get_cookie(name):返回 cookie 字典中 key == namecookie 信息。
  • add_cookie(cookie_dict):将 cookie 添加到当前会话中
  • delete_cookie(name):删除指定名称的单个 cookie
  • delete_all_cookies():删除会话范围内的所有 cookie

下面看一下简单的示例,演示了它们的用法。

from selenium import webdriver 

driver = webdriver.Chrome()
driver.get("https://blog.csdn.net/")

# 输出所有cookie信息
print(driver.get_cookies())

cookie_dict = {
    'domain': '.csdn.net',
     'expiry': 1664765502,
     'httpOnly': False,
     'name': 'test',
     'path': '/',
     'secure': True,
     'value': 'null'}

# 添加cookie
driver.add_cookie(cookie_dict)
# 显示 name = 'test' 的cookie信息
print(driver.get_cookie('test'))
# 删除 name = 'test' 的cookie信息
driver.delete_cookie('test')
# 删除当前会话中的所有cookie
driver.delete_all_cookies()




调用JavaScript

webdriver 对于滚动条的处理需要用到 JavaScript ,同时也可以向 textarea 文本框中输入文本( webdriver 只能定位,不能输入文本),webdriver 中使用execute_script方法实现 JavaScript 的执行。

滑动滚动条

通过 x ,y 坐标滑动

对于这种通过坐标滑动的方法,我们需要知道做表的起始位置在页面左上角(0,0),下面看一下示例,滑动 CSDN 首页。

from selenium import webdriver 
from time import sleep

driver = webdriver.Chrome()
driver.get("https://blog.csdn.net/")
sleep(1)

js = "window.scrollTo(0,500);"
driver.execute_script(js)

通过参照标签滑动

这种方式需要先找一个参照标签,然后将滚动条滑动至该标签的位置。下面还是用 CSDN 首页做示例,我们用循环来实现重复滑动。该 li 标签实际是一种懒加载,当用户滑动至最后标签时,才会加载后面的数据。

from selenium import webdriver 
from time import sleep

driver = webdriver.Chrome()
driver.get("https://blog.csdn.net/")
sleep(1)
driver.implicitly_wait(3)

for i in range(31, 102, 10):
    sleep(1)
    target = driver.find_element_by_xpath(f'//*[@id="feedlist_id"]/li[{i}]')
    driver.execute_script("arguments[0].scrollIntoView();", target)

请添加图片描述



其他操作

关闭所有页面

使用 quit() 方法可以关闭所有窗口并退出驱动程序。

driver.quit()

关闭当前页面

使用 close() 方法可以关闭当前页面,使用时要注意 “当前页面” 这四个字,当你关闭新打开的页面时,需要切换窗口才能操作新窗口并将它关闭。,下面看一个简单的例子,这里不切换窗口,看一下是否能够关闭新打开的页面。

from selenium import webdriver
from time import sleep

driver = webdriver.Chrome()
driver.get('https://blog.csdn.net/')
driver.implicitly_wait(3)

# 点击进入新页面
driver.find_element_by_xpath('//*[@id="mainContent"]/aside/div[1]/div').click()
# 切换窗口
# driver.switch_to.window(driver.window_handles[-1])

sleep(3)
driver.close()


可以看到,在不切换窗口时,driver 对象还是操作最开始的页面。

对当前页面进行截图

wendriver 中使用 get_screenshot_as_file() 对 “当前页面” 进行截图,这里和上面的 close() 方法一样,对于新窗口的操作,一定要切换窗口,不然截的还是原页面的图。对页面截图这一功能,主要用在我们测试时记录报错页面的,我们可以将 try except 结合 get_screenshot_as_file() 一起使用来实现这一效果。

try:
    driver.find_element_by_xpath('//*[@id="mainContent"]/aside/div[1]/div').click()
except:
    driver.get_screenshot_as_file(r'C:\Users\pc\Desktop\screenshot.png')

常用方法总结

# 获取当前页面url
driver.current_url

# 获取当前html源码
driver.page_source

# 获取当前页面标题
driver.title

# 获取浏览器名称(chrome)
driver.name

# 对页面进行截图,返回二进制数据
driver.get_screenshot_as_png()

# 设置浏览器尺寸
driver.get_window_size()

# 获取浏览器尺寸,位置
driver.get_window_rect()

# 获取浏览器位置(左上角)
driver.get_window_position()

# 设置浏览器尺寸
driver.set_window_size(width=1000, height=600)

# 设置浏览器位置(左上角)
driver.set_window_position(x=500, y=600)

# 设置浏览器的尺寸,位置
driver.set_window_rect(x=200, y=400, width=1000, height=600)


selenium进阶

selenium隐藏指纹特征

selenium 对于部分网站来说十分强大,但它也不是万能的,实际上,selenium 启动的浏览器,有几十个特征可以被网站检测到,轻松的识别出你是爬虫。
不相信?接着往下看,首先你手动打开浏览器输入https://bot.sannysoft.com/,在网络无异常的情况下,显示应该如下:
在这里插入图片描述
下面通过 selenium 来打开浏览器。

from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://bot.sannysoft.com/')


通过 webdriver:present 可以看到浏览器已经识别出了你是爬虫,我们再试一下无头浏览器。

from selenium import webdriver

# 设置无头浏览器
option = webdriver.ChromeOptions()
option.add_argument('--headless')

driver = webdriver.Chrome()

driver.get('https://bot.sannysoft.com/')
# 对当前页面进行截图
driver.save_screenshot('page.png')


没错,就是这么真实,对于常规网站可能没什么反爬,但真正想要抓你还是一抓一个准的。

说了这么多,是不是 selenium 真的不行?别着急,实际还是解决方法的。关键点在于如何在浏览器检测之前将这些特征进行隐藏,事实上,前人已经为我们铺好了路,解决这个问题的关键,实际就是一个 stealth.min.js 文件,这个文件是给 puppeteer 用的,在 Python 中使用的话需要单独执行这个文件,该文件获取方式需要安装 node.js ,如果已安装的读者可以直接运行如下命令即可在当前目录生成该文件。

npx extract-stealth-evasions

这里我已经成功获取了 stealth.min.js 文件。
链接:https://pan.baidu.com/s/1O6co1Exa8eks6QmKAst91g
提取码:关注文末小卡片回复“隐藏指纹特征”获取

下面我们在网站检测之前先执行该js文件隐藏特征,同样使用无头浏览器,看是否有效。

import time
from selenium.webdriver import Chrome

option = webdriver.ChromeOptions()
option.add_argument("--headless")
# 无头浏览器需要添加user-agent来隐藏特征
option.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36')

driver = Chrome(options=option)
driver.implicitly_wait(5)

with open('stealth.min.js') as f:
    js = f.read()

driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
  "source": js
})

driver.get('https://bot.sannysoft.com/')
driver.save_screenshot('hidden_features.png')


通过 stealth.min.js 的隐藏,可以看到这次使用无头浏览器特征基本都以隐藏,已经十分接近人工打开浏览器了。



实战:selenium模拟登录B站

登录验证码处理

selenium 中的难点验证码破解在上文中并没有提及,因为确实没有很好的方式,一般都需要通过第三方平台实现破解,本案例中使用的是超级鹰平台(收费,大概1元30次,测试用冲个1元就足够)。下面实战开始!

分析登录界面结构

B站登录界面如下。

首先明确我们的目标,打开登陆界面,定位用户名和密码对应的标签,输入相关数据后,点击登录,此时页面会弹出文字验证码。

下文会用两种方法进行验证码图片的获取,并提交给超级鹰进行识别,接收到汉字的坐标后,处理坐标数据,然后用动作链点击对应坐标操作,完成登录。

下面使用 selenium 打开登录页面。

driver.get('https://passport.bilibili.com/login')
# 定位用户名,密码输入框
username = driver.find_element_by_id('login-username')
password = driver.find_element_by_id('login-passwd')
# 将自己的用户名密码替换xxxxxx
username.send_keys('xxxxxx')
password.send_keys('xxxxxx')

# 定位登录按钮并点击
driver.find_element_by_xpath('//*[@id="geetest-wrap"]/div/div[5]/a[1]').click()

获取页面当前验证码图片

方法一、页面截图,将验证码区域进行裁剪保存

使用此方法时,注意我们截取验证码图片时需要截取完整,不要只截图片部分,上面文字也需要。完整验证码截图如下:

首先将点击登录后的页面进行截图,然后定位到验证码的位置,通过location()方法获取验证码左上角的坐标, size() 获取验证码的宽和高,左上角坐标加上宽和高就是验证码右下角的坐标。获取坐标后就可以用**crop()**方法来进行裁剪,然后将裁剪到的验证码图片保存。

此时虽然获取了验证码图片,但是还不能直接提交给超级鹰。
因为超级鹰识别的验证码图片的宽和高有限制,最好不超过 460px,310px
但是截取到的验证码图片宽高为 338px,432px,这时就要先将图片缩小一倍再提交即可,等到收到坐标数据再将坐标乘2

def save_img():
    # 对当前页面进行截图保存
    driver.save_screenshot('page.png')
    # 定位验证码图片的位置
    code_img_ele = driver.find_element_by_xpath('/html/body/div[2]/div[2]/div[6]/div/div')
    # 获取验证码左上角的坐标x,y
    location = code_img_ele.location
    # 获取验证码图片对应的长和宽
    size = code_img_ele.size

    # 左上角和右下角的坐标
    rangle = (
        int(location['x'] * 1.25), int(location['y'] * 1.25), int((location['x'] + size['width']) * 1.25),
        int((location['y'] + size['height']) * 1.25)
    )

    i = Image.open('./page.png')
    code_img_name = './code.png'
    # crop根据rangle元组内的坐标进行裁剪
    frame = i.crop(rangle)
    frame.save(code_img_name)
    return code_img_ele

def narrow_img():
    # 缩小图片
    code = Image.open('./code.png')
    small_img = code.resize((169, 216))
    small_img.save('./small_img.png')
    print(code.size, small_img.size)

方法二、通过网页获取图片地址,并保存

这种方法比上一种更加方便,分析网页源码获取图片地址,对该地址发送请求,接收返回的二进制文件,进行保存。首先打开网页源码找到图片地址。

图片地址是 img 标签的 src 属性值,通过 xpath 得到地址,直接对此 url 发送请求,接收数据并保存即可。
注意:由于获取的图片的高度仍然大于超级鹰标准格式,所以也需要将图片缩小。

# 获取img标签的src属性值
img_url = driver.find_element_by_xpath('/html/body/div[2]/div[2]/div[6]/div/div/div[2]/div[1]/div/div[2]/img').get_attribute('src')
headers = {
    'Users-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36'
}
# 获取图片二进制数据
img_data = requests.get(url=img_url, headers=headers).content
with open('./node1.png', 'wb')as fp:
    fp.write(img_data)
i = Image.open('./node1.png')
# 将图片缩小并保存,设置宽为172,高为192
small_img = i.resize((172, 192))
small_img.save('./small_img1.png')

使用超级鹰识别验证码

这部分没什么说的,直接调用就行。

# 将验证码提交给超级鹰进行识别
chaojiying = Chaojiying_Client('用户名', '密码', '96001')  # 用户中心>>软件ID 生成一个替换 96001
im = open('small_img.png', 'rb').read()  # 本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
# 9004是验证码类型
print(chaojiying.PostPic(im, 9004)['pic_str'])
result = chaojiying.PostPic(im, 9004)['pic_str']

提取坐标数据,动作链点击

超级鹰识别返回的数据格式是:123,12 | 234,21 。我们可以将数据以 ' | ' 进行分割,保存到列表中,再以逗号分割将 x,y 的坐标保存,得到 [ [123,12],[234,21] ] 这一格式,然后遍历这一列表,使用动作链对每一个列表元素对应的 x,y 指定的位置进行点击操作,最后定位并点击确认,登录成功。

all_list = []  # 要存储即将被点击的点的坐标  [[x1,y1],[x2,y2]]
if '|' in result:
    list_1 = result.split('|')
    count_1 = len(list_1)
    for i in range(count_1):
        xy_list = []
        x = int(list_1[i].split(',')[0])
        y = int(list_1[i].split(',')[1])
        xy_list.append(x)
        xy_list.append(y)
        all_list.append(xy_list)
else:
    x = int(result.split(',')[0])
    y = int(result.split(',')[1])
    xy_list = []
    xy_list.append(x)
    xy_list.append(y)
    all_list.append(xy_list)

# 遍历列表,使用动作链对每一个列表元素对应的x,y指定的位置进行点击操作
# x,y坐标乘2和0.8,是由于之前图片缩放过,所以*2,0.8是因为本人电脑桌面缩放比例为125%,需要还原成1
for l in all_list:
    x = l[0] * 2 * 0.8
    y = l[1] * 2 * 0.8
    # 将点击操作的参照物移动到指定的模块,
    # 若用方法二获取的验证码图片,要添加下面代码对code_img_ele赋值
    # code_img_ele = bro.find_element_by_xpath('/html/body/div[2]/div[2]/div[6]/div/div/div[2]/div[1]/div/div[2]/img')
    ActionChains(driver).move_to_element_with_offset(code_img_ele, x, y).click().perform()
    print('点击已完成')

# 完成动作链点击操作后,定位确认按钮并点击
driver.find_element_by_xpath('/html/body/div[2]/div[2]/div[6]/div/div/div[3]/a').click()

运行效果

由于验证码处理需要用到第三方平台,外加设置了强制等待,整体运行速度较慢。



【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。