好好学习,天天向上

再谈自动打卡之自动化浏览器操作小利器:splinter

去年Ele练手弄了一个repo,玩了几个网站的登录和打卡功能之后就把这个小东东扔到香蕉派上,一并加入cron套餐。之后就没再理之。

小东西好好地跑了几个月,之后的某一天登上去看日志,发现好几个网站的自动登录和打卡都error了,懵逼之下跑到那些个网站上一看,哎呀,改版变规则什么的,就是辣么任性/(ㄒoㄒ)/~~

所以只能重写了。但是重写并非分分钟的事情(过程见[Python]沪江部落自动登录打卡),懒癌犯了的Ele就暂时这样LET IT GO了。

之后这个repo居然陆陆续续有人star,这引发了Ele的羞耻心,毕竟,坑人是不对的不对的不对的!!!又后来,懒惰的Ele看到了selenium,再然后又看到了splinter,正如干柴遇上了烈火(貌似有什么不对的东西混进来了O__O "…)

此为前因。

splinter是Python系用来测试web应用的一个工具,允许你自动化浏览器操作,例如访问URL,与页面元素进行交互等。

因此,我们可以用它来模拟登陆过程(输入用户名密码,点击登陆)和打卡(直接访问URL或者点击打卡按钮)过程,而后面的JavaScript等一系列点击响应事件则交给splinter处理,而无需像之前那样操心网络层面与服务器端是如何交互的。

为了和前面手解登录和打卡作对比,此次仍然以沪江部落为例

准备

  1. 使用pip安装splinter包:
    1
    pip install splinter
  2. 浏览器准备
  • 如果使用firefox,则需要下载geckodriver,并将其解压到PATH可以找到的目录中。否则,运行时会出现错误:Selenium.common.exceptions.WebDriverException: Message: 'geckodriver' executable needs to be in PATH.
  • 如果使用chrome,则除了要安装chrome浏览器外,还需要下载chromedriver并解压。我们这里以chrome为例,将chromedriver解压缩到/Users/elexu/Downloads下。

准备好了,我们先来讲讲如何模拟登陆

登陆

我们先来看看一般手动登录过程。

从浏览器访问http://www.hjenglish.com/,可以看到右上角有个登录按钮。点击登录按钮后,在弹出的登录框里填入正确的用户名密码,点击绿色的登录按钮。等待片刻即可登录成功。

现在,我们来看看,如何使用splinter来实现这一过程。

首先,我们要初始化一个Browser实例:

1
2
from splinter import Browser
b = Browser(driver_name = "chrome", executable_path = "/Users/elexu/Downloads/chromedriver")
此时,我们可以看到一个新的chrome窗口弹出。接下来,我们需要访问沪江部落,然后让登录框弹出。

打开chrome的开发者工具,审查登录按钮。

为了让登录框弹出,我们需要定位到这个元素,然后点击一下。splinter提供了多种查找元素的方法,例如,css, xpath, tag, id, name等。我们现在来看看如何根据上面的图来找到这个登录按钮,并完成点击操作:

1
2
3
4
5
6
# 访问网站
b.visit("http://www.hjenglish.com/")
import time
time.sleep(3)
b.find_by_xpath('//*[@id="passport_userinfo"]/li[1]/a[1]').first.click()
time.sleep(3)

这里有几点说明: - 这里需要sleep一段时间,使得页面可以完全加载完毕,否则有可能在查找元素的时候,想查找的元素还未加载出来,从而导致查找不到元素。同理,点击完后,也要有一定的时间供给页面加载 - 使用Browser实例哪一个find_by_xxx方法就看元素如何是如何写的。像上面用下标定位出来的,万一页面元素发生了变动,就会失效了。如果元素可以直接通过name/id来定位,首选find_by_name和find_by_id - find_by_xpath方法返回的是一个splinter.element_list.ElementList实例。我们可以直接在这个实例上调用click方法进行点击,也可以像上面代码那样,找到定位到的第一个元素,再调用click方法。

现在,从浏览器上可以看到,登录框已经弹出来了。接下来就是依葫芦画瓢找到用户名密码所在的输入框,填充值,然后点击登录按钮。

1
2
3
b.fill("username", "ele")
b.fill("password", "xxxxxx")
b.find_by_xpath('//*[@id="hp-login-normal"]/button').click()
Browser()实例的fill(name, value)方法的作用是用参数value的内容填充由name标识的域。运行上面代码,我们可以在打开的浏览器上看到,用户名密码输入框中迅速地填充了我们指定的值,然后在click操作后,成功登录。一切都跟我们手工登录过程一毛一样。

打卡

打开部落个人home页面,然后审查右上角的打卡按钮

我们要做的很简单,只要点击这个按钮即可

1
2
3
4
5
6
bulo_home = "http://bulo.hujiang.com/home"
b.visit(bulo_home)
time.sleep(3)
if b.is_element_present_by_id("btn_card_do"):
#b.execute_script("$(btn_card_do).attr('style','z-index:99999;display:block;position:absolute;')")
b.find_by_id("btn_card_do").first.click()

代码很直观,但是有几点说明: - 供给页面加载时间还是需要的,这里我们选择了3s。其实可以不用那么长,这个可以自己调整 - Browser()实例也提供了多种is_element_present_by_xxx方法,可以来检测当前页面是否存在某个元素。这里我们需要检测下页面是否有打卡按钮,如果没有,就不用点了。

运行一下,程序抛异常:

1
ElementNotVisibleException: Message: element not visible
跑到页面上一看,才发现打卡按钮被大大的div遮住了。因此,我们需要通过一些手段将button展示出来。比方说修改元素的css。Browser()实例提供了execute_script()方法,可以用来执行javascript代码。将上面注释掉的那行代码的注释取消掉,再运行一下就发现OK了。

这是一种方法。当然,我们还可以利用[Python]沪江部落自动登录打卡中找到的url,直接访问,进行打卡

1
2
3
import random
SIGN_URL = "http://bulo.hujiang.com/app/api/ajax_take_card.ashx?%.17f"%random.random()
b.visit(SIGN_URL)
### 碎碎念 先来说说这种方法的优点。

通过splinter,实现模拟人与浏览器的互动,写代码的人完全就不用管底层是怎么跟对端服务器进行交互的。通过splinter封装的API,基本可以满足所需的浏览器操作。简直就是懒人常备小利器。

另外,很多时候,网站变更是隐藏在背后的,大多数让用户无感知。因此,不管底层逻辑怎么变更,只要打开看到的页面没有变动,这一套自动登录打卡便行之有效。免去了代码的频繁变更以适应新的网站逻辑。

于是,懒惰的Ele分分钟就把daka全部迁成splinter实现了~

当然,这种方法知其然而不知其所以然,一点都不geek。另外,对那些想要通过了解机制来学到点什么的小伙伴来说,最好还是跟自己死磕一下,老老实实抓包看代码吧。

参考

请言小午吃个甜筒~~