记录第一次使用Selenium完成阿里云盘签到的过程。

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
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.wait import WebDriverWait

option = webdriver.ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])
option.add_argument('--disable-blink-features=AutomationControlled')
driver = webdriver.Chrome(options=option)
driver.get("https://www.aliyundrive.com/sign/in")
iframe=driver.find_element(By.CSS_SELECTOR, "iframe")
driver.switch_to.frame(iframe)
iframe=driver.find_element(By.CSS_SELECTOR, "iframe")
driver.switch_to.frame(iframe)
login=driver.find_elements(By.CSS_SELECTOR, "div.login-blocks.block0>.sms-login-link")[-1]
login.click()

user=driver.find_element(By.CSS_SELECTOR, "input#fm-login-id")
passwd=driver.find_element(By.CSS_SELECTOR, "input#fm-login-password")
user.send_keys("") # 账号
passwd.send_keys("") # 密码
time.sleep(2)


# driver.switch_to.frame(driver.find_element(By.CSS_SELECTOR, "iframe"))
# element=driver.find_element(By.CSS_SELECTOR,"div#nc_1__scale_text.scale_text.slidetounlock")
# dragger=driver.find_element(By.CSS_SELECTOR, "span#nc_1_n1z.nc_iconfont.btn_slide")
# action=ActionChains(driver)
# print(element.size['width'])
# action.click_and_hold(dragger).perform()
# action.drag_and_drop_by_offset(dragger,element.size['width'],0)
# action.perform()

driver.find_element(By.CSS_SELECTOR, "button.fm-button.fm-submit.password-login[tabindex='3']").click()

windows=driver.window_handles
driver.switch_to.window(windows[-1])
driver.find_element(By.CSS_SELECTOR, "div.sign-btn---rZSA").click()
# driver.switch_to.default_content()

driver.switch_to.frame(driver.find_element(By.CSS_SELECTOR, "iframe[src='https://pages.aliyundrive.com/mobile-page/web/dailycheckpc.html']"))
time.sleep(1)
driver.find_element(By.CSS_SELECTOR, "img[src='//gw.alicdn.com/imgextra/i4/O1CN01aVlk0J1KIALJhQfCV_!!6000000001140-2-tps-72-72.png']").click()

driver.switch_to.default_content()
time.sleep(1)
driver.find_element(By.CSS_SELECTOR, "span[data-icon-type='PDSMoreCircle']>svg").click() # 设置
time.sleep(1)

(driver.find_elements(By.CSS_SELECTOR, "div.outer-menu--ihDUR"))[-1].click() # 退出登录
time.sleep(1)

driver.find_element(By.CSS_SELECTOR, "button.ant-btn.ant-btn-primary.ant-btn-dangerous").click() # 确认
time.sleep(1)
driver.quit()

首先在阿里云盘登录界面切换至账号密码登录,此时面临的第一个问题是滑块

滑块

在输入账号密码后会弹出滑块,让用户从左推到右。这算是比较简单的一种反爬虫手段了,然而我半天都没弄好

首先是尝试动作链ActionChains

1
2
3
4
5
6
7
8
9
action = ActionChains(driver)
action.drag_and_drop(dragger, item1).perform() # 1.移动dragger到目标1
sleep(2)
action.click_and_hold(dragger).release(item2).perform() # 2.效果与上句相同,也能起到移动效果
sleep(2)
action.click_and_hold(dragger).move_to_element(item3).release().perform() # 3.效果与上两句相同,也能起到移动的效果
sleep(2)
# action.drag_and_drop_by_offset(dragger, 400, 150).perform() # 4.移动到指定坐标
action.click_and_hold(dragger).move_by_offset(400, 150).release().perform() # 5.与上一句相同,移动到指定坐标

Selenium之动作链(ActionChains) - liangxb - 博客园 (cnblogs.com)

大致就是上面几种方法,还有些改进方法,如调整拖拽速度:

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
from selenium.webdriver import ActionChains
from selenium import webdriver
import time

driver=webdriver.Chrome()
driver.get("https://www.qichacha.com/user_login")
time.sleep(1)
driver.find_element_by_xpath('//*[@id="normalLogin"]').click()
time.sleep(1)
huakuai=driver.find_element_by_xpath('//*[@id="nc_1_n1z"]')

def get_track(distance): # distance为传入的总距离
# 移动轨迹
track=[]
# 当前位移
current=0
# 减速阈值
mid=distance*4/5
# 计算间隔
t=0.2
# 初速度
v=1

while current<distance:
if current<mid:
# 加速度为2
a=4
else:
# 加速度为-2
a=-3
v0=v
# 当前速度
v=v0+a*t
# 移动距离
move=v0*t+1/2*a*t*t
# 当前位移
current+=move
# 加入轨迹
track.append(round(move))
return track
def move_to_gap(slider,tracks): # slider是要移动的滑块,tracks是要传入的移动轨迹
ActionChains(driver).click_and_hold(slider).perform()
for x in tracks:
ActionChains(driver).move_by_offset(xoffset=x,yoffset=0).perform()
time.sleep(0.5)
ActionChains(driver).release().perform()

if __name__ == '__main__':
move_to_gap(huakuai,get_track(340))

Python+Selenium 拖动滑块 (一) - 简书 (jianshu.com)

另外的参考文章:selenium之滑块操作_selenium拖动滑块_王大傻0928的博客-CSDN博客

使用selenium模拟登录解决滑块验证问题_锅炉房刘大爷的博客-CSDN博客

python- selenium 淘宝爬虫之滑块验证(滑动速度放慢版)_move_by_offset速度控制_小泽泽泽ya的博客-CSDN博客

selenium 滑动解锁(drag_and_drop_by_offset)_weixin_34067049的博客-CSDN博客

Selenium鼠标方法drag_and_drop_by_offset、move_by_offset无响应_我乱来的a的博客-CSDN博客

然而实际上都没用,因为阿里云盘设置了一些反爬虫手段


隐去特征

那么应该如何解决呢?

参考selenium 反爬虫之跳过滑块验证 - 知乎 (zhihu.com), 在原有代码前加上:

1
2
3
4
option = webdriver.ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])
option.add_argument('--disable-blink-features=AutomationControlled')
driver = webdriver.Chrome(options=option)

以及参考修改Chromedriver特征字符串_chromedriver 特征_MichaelYZ111的博客-CSDN博客,在Kali中用hexedit修改chromedriver.exe

将$cdc_asdjflasutopfhvcZLmcfl_改为任意字符

关于Options(),如登录时关闭密码保存提示框:

1
2
3
4
prefs = {}  # 设置这两个参数就可以避免密码提示框的弹出
prefs['credentials_enable_service'] = False
prefs['profile.password_manager_enabled'] = False
options.add_experimental_option('prefs', prefs)

其他用途参考:

selenium之options模块_selenium options__xiao_gu的博客-CSDN博客

就惊奇地发现不再需要滑块验证了!


Notes

之后便是水到渠成,有几个值得注意的点:

  1. 切换window, frame之后要加time.sleep(1),否则其后的find_element会找不到

  2. 有时候CSS_SELECTOR写的不好(不准确是一方面,有时候有些啥啥啥-id还会变,就不能用这个作为定位器)会导致定位到其他元素上,因此务必确保每一步都写对。

    更方便的办法是F12右键copy selector,但仍有动态变化的可能

    详见下文

  3. 做了好多次后发现突然又要滑块验证了,猜测是ip被察觉到异常访问了,于是在命令行中

    1
    2
    ipconfig/release
    ipconfig/renew

    获得新地址即可


等待

time.sleep(1)替换为隐式等待driver.implicity_wait(30)

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
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.wait import WebDriverWait

option = webdriver.ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])
option.add_argument('--disable-blink-features=AutomationControlled')
prefs = {} # 设置这两个参数就可以避免密码提示框的弹出
prefs['credentials_enable_service'] = False
prefs['profile.password_manager_enabled'] = False
option.add_experimental_option('prefs', prefs)


driver = webdriver.Chrome(options=option)
driver.get("https://www.aliyundrive.com/sign/in")

driver.implicitly_wait(30) # 隐式等待

iframe=driver.find_element(By.CSS_SELECTOR, "iframe")
driver.switch_to.frame(iframe)
iframe=driver.find_element(By.CSS_SELECTOR, "iframe")
driver.switch_to.frame(iframe)
login=driver.find_elements(By.CSS_SELECTOR, "div.login-blocks.block0>.sms-login-link")[-1]
login.click()

user=driver.find_element(By.CSS_SELECTOR, "input#fm-login-id")
passwd=driver.find_element(By.CSS_SELECTOR, "input#fm-login-password")
user.send_keys("18381605149")
passwd.send_keys("xuharry2022")

driver.find_element(By.CSS_SELECTOR, "button.fm-button.fm-submit.password-login[tabindex='3']").click()

windows=driver.window_handles
driver.switch_to.window(windows[-1])
driver.find_element(By.CSS_SELECTOR, "#layout > div.sider--3QQ75 > div > div > div > div.simplebar-wrapper > div.simplebar-mask > div > div > div > div > div.sider-top--2wTJr > ul > div > div > div.sign-btn---rZSA").click()

driver.switch_to.frame(driver.find_element(By.CSS_SELECTOR, "iframe[src='https://pages.aliyundrive.com/mobile-page/web/dailycheckpc.html']"))
driver.find_element(By.CSS_SELECTOR, "#root > div > div.rax-view-v2.DailyCheckPc--main--1cbm8da > div.rax-view-v2.DailyCheckPc--header--2CXJvuw > div > img").click() # 关闭

driver.switch_to.default_content()
driver.find_element(By.CSS_SELECTOR, "#layout > div.sider--3QQ75 > div > div > div > div.simplebar-wrapper > div.simplebar-mask > div > div > div > div > div.sider-bottom--2ltRW > div.bottom-wrapper--19rog > div.ant-dropdown-trigger.more-wrapper--2dMhX > span > svg").click() # 设置

(driver.find_elements(By.CSS_SELECTOR, "body > div:nth-child(12) > div > div > ul > li:nth-child(13) > div > div"))[-1].click() # 退出登录

driver.find_element(By.CSS_SELECTOR, "body > div:nth-child(14) > div > div.ant-modal-wrap > div > div.ant-modal-content > div > div > div.ant-modal-confirm-btns > button.ant-btn.ant-btn-primary.ant-btn-dangerous").click() # 确认
time.sleep(3)
driver.quit()

关于显式和隐式的区别,

隐式等待的好处是不用像固定等待方法一样死等时间N秒,可以在一定程度上提升测试用例的执行效率。不过这种方法也存在一定的弊端,那就是程序会一直等待整个页面加载完成,也就是说浏览器窗口标签栏中不再出现转动的小圆圈,才会继续执行下一步。

显示等待会每个一段时间(该时间一般都很短,默认为0.5秒,也可以自定义),执行自定义的程序判断条件,如果判断条件成立,就执行下一步,否则继续等待,直到超过设定的最长等待时间,然后抛出TimeOutEcpection的异常信息。

selenium之WebDriverWait类(等待机制)_虚坏叔叔的博客-CSDN博客

implicitly_wait()隐式等待和explicit_wait()显示等待_implicitly_wait用法_油菜花啊的博客-CSDN博客


正则表达式匹配selector

在“退出登录”那步,复制selector得到:body > div:nth-child(12) > div > div > ul > li:nth-child(13) > div > div

CSS中的 :nth-child() 是一个伪类选择器,用于匹配其父元素的第 n 个子元素。而 div:nth-child() 则是指父元素的所有子元素中为 div 标签的第 n 个元素。

在给定的代码中,div:nth-child(12) 表示父元素中所有 div 标签的第 12 个元素,而 li:nth-child(13) 表示父元素中所有 li 标签的第 13 个元素。最终,div:nth-child(12) > div > div > ul > li:nth-child(13) > div > div 表示一个元素,它是具有如上所述子元素层次结构的 div 元素的子元素,即父元素的第 12 个 div 标签,其子元素是一个 div 标签,该标签的子元素是一个 ul 标签,ul 标签的子元素中第 13 个 li 标签,该 li 标签的子元素是一个 div 标签,而该 div 标签又具有一个子元素为 div 标签。

但这时可能会变成body > div:nth-child(13) > div > div > ul > li:nth-child(12) > div > div

这种情况甚至不好用正则表达式匹配

重新测试数次,都没再遇到上述情况,遂作罢

那么就不再对本次代码做正则匹配了。


如果要做,比如…>div.arch_main_开头的类:

首先定位到父元素,用findall(r'arch_main_(.*)',element.get_attribute('innerHTML')获得其中以arch_main_开头的字符串

对get_attribute的测试:print(driver.find_element(By.CSS_SELECTOR, "div.sider-bottom--2ltRW > div.bottom-wrapper--19rog".get_attribute("innerHTML"))可获得设置所在的div内容

之后可以遍历字符串,去匹配类

相关参考:

正则表达式

结合Selenium和正则表达式提高爬虫效率 - TVHead - 博客园 (cnblogs.com)

selenium get_attribute的几种用法_gaimechen的博客-CSDN博客

selenium利用正则表达式定位元素_selenium 正则表达式_杨二狗2333的博客-CSDN博客


cookie登录

参考selenium cookie 登录 - 风,又奈何 - 博客园 (cnblogs.com),先手动登录并保存cookie至本地,之后即可自动登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 手动登录

from selenium import webdriver
import time
import json

#填写webdriver的保存目录
driver = webdriver.Chrome()
url='https://www.csdn.net'
#记得写完整的url 包括http和https
driver.get(url)

#程序打开网页后20秒内手动登陆账户
time.sleep(20)

with open('cookies.txt','w') as cookief:
#将cookies保存为json格式
cookief.write(json.dumps(driver.get_cookies()))

driver.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
# 自动登录

from selenium import webdriver
import time
import json

#填写webdriver的保存目录
driver = webdriver.Chrome()
url='https://www.csdn.net'
#记得写完整的url 包括http和https
driver.get(url)
#首先清除由于浏览器打开已有的cookies
driver.maximize_window()
driver.delete_all_cookies()

with open('cookies.txt','r') as cookief:
#使用json读取cookies 注意读取的是文件 所以用load而不是loads
cookieslist = json.load(cookief)
for cookie in cookieslist:
driver.add_cookie(cookie)

driver.refresh()

time.sleep(30)
driver.quit()

问题是cookie可能会失效,对此作者给出两种办法:

将expiry类型变为int / 删除该字段

另一篇文章用的方法类似,只是改为yaml: Python Selenium Cookie 绕过验证码实现登录 - Blue·Sky - 博客园 (cnblogs.com)

这个也类似,获取sessionid和token,但没有实现token的更新:Python3+Selenium获取session和token供Requests使用教程 - adolfmc - 博客园 (cnblogs.com)

类似文章:python+selenium 通过添加cookies或token解决网页上验证码登录问题_selenium token_aiee的博客-CSDN博客

【Selenium 小知识】获取 token 和 cookies_selenium获取token_Warolitbos的博客-CSDN博客

略微不同的一篇博客,用了fiddler抓包获取token,之后直接带着token前往登录后的界面:

11、python+selenium绕过验证码登录 - YLG001 - 博客园 (cnblogs.com)


翻了好久仍没找到想要的,即如何像CSDN上阿里云盘三月自动签到Python脚本,可本地、青龙、云函数自动执行_残荷亭的博客-CSDN博客做的,通过refresh_token获得新的access_token以及refresh_token,上面那些大都是通过手动方式获得token/cookie/sessionid,总结下就是cookie用get_cookie(),token/sessionid用driver.ececute_script('return sessionStorage.getItem("token")')这种。


待研究

  • 借助refresh_token获取access_token