Python | 沪江部落自动登录打卡

现在好多网站都有打卡或者领金币之类的功能。但素,那么多网站,肿么可能记着都要打卡~~而且,每个网站都要上去点几下超级花费时间的。恰好又有banana pro,就想着要不弄个脚本,记个定时任务,让打卡这件事自动化好了。

沪江是个棒棒哒的外语学习网站。就以其为目标好了~

整个思路是

  1. 模拟用户登录
  2. 打卡

是不是So easy~~ 妈妈再也不用怕我忘记打卡了

登录

准备工作

一般来说,网站的登录模式就是客户端发送一个请求,这个请求包含了用户的信息(用户名、密码之类的)。服务端会对这个请求进行校验鉴权,然后返回给客户端一个响应,告知客户端用户是否登录成功。因此,首先,要先分析客户端给服务器发送了一个神马样子的请求。打开chrome,F12,尝试用沪江账号登录,可以得到一个GET请求,见下图

这里要注意一下,“Perserver log”选项要勾上,否则跳转可能会将登录请求给刷新掉,这样我们就看不到了。

从Query String Parameters里面可以看到请求参数。这些请求参数有的看一眼基本可以确认传固定值,有的需要分析是怎么来的

token:暂时还不清楚这个是干嘛的
remeberdays:记住密码的天数,这里使用固定值14即可
callback:jQuery18308307662333827466_1443247777543
_:1443247800524

由于WEB知识基本小白,因此被callback和_这两个参数困扰了N久。看js代码也没找出来这两个参数是哪里来的。后来想想,这两个参数是不是标准的语法参数而与网站自身无关呢。跑到google上一查,果然。

"jsonp": 以 JSONP 的方式载入 JSON 数据块。会自动在所请求的URL最后添加"?callback=?"。默认情况下会通过在URL中附加查询字符串变量 ,_=[TIMESTAMP], 禁用缓存结果,除非设置了cache参数为true。

闹了半天,原来是个时间戳呀。后边验证发现callback的前半部分不重要,只要后面替换为当前时间即可。而“_”参数也是一个当前时间戳。

继续研究token参数。在上面的链接中并未出现用户名密码,那服务器是怎么知道哪个用户在做登录请求呢?在过滤器中输入用户名,发现有一个链接出现了用户名密码

查看这个链接的响应,发现了有个参数是ssotoken,这个响应参数的值恰好跟登录请求里面的token值一模一样!!Lucky~~

那么我们来看看这个链接的请求参数,其中callback参数和_参数我们有了上面的解析,很容易就可以得到

callback:jQuery18307904950501397252_1443237280922
account:用户名
password:密码,看起来像是一个MD5加密的字段,验证一下,果然真是
_:1443238132704

接着研究响应。在Preview中可以看到,当返回的code值为0时表示登录成功。此时,可以输几次错误的密码看看登录失败的code值。

开始写代码

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
def __init__(self):
self._cookie = cookielib.LWPCookieJar()
self._opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self._cookie))
self.INDEX_URL = "http://bulo.hujiang.com/"
self._callback = "jQuery18307142652559559792_1442495521490"
self.TOKEN_URL = "http://pass.hujiang.com/quick/account/?callback=%s&account=%s&password=%s&code=&act=loginverify&source=bulo_anon&_=%s"
self.LOGIN_URL = "http://pass.hujiang.com/quick/synclogin.aspx?token=%s&remeberdays=14&callback=%s&_=%s"
#下面是日志部分
self._logger = logging.getLogger(__name__)
self._logger.setLevel(logging.INFO)
self._handler = logging.FileHandler(os.path.join(sys.path[0],'gargets_autoSignIn.log'))
self._handler.setLevel(logging.INFO)
self._handler.setFormatter(logging.Formatter('%(asctime)s - %(filename)s:%(lineno)d - %(levelname)s - %(message)s'))
self._logger.addHandler(self._handler)

def login(self,user="",psw=""):
print "user is: ", user
self._logger.info("user is: %s" % user)
self._getToken(user,psw)
hjHeaders = {
"Accept":"*/*",
"Accept-Encoding":"gzip, deflate, sdch",
"Accept-Language":"zh-CN,zh;q=0.8",
"Host":"pass.hujiang.com",
"Referer":"http://bulo.hujiang.com/anon/?source=nbulo&returnurl=http%3a%2f%2fbulo.hujiang.com%2fhome%2f",
"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36",
}
req = urllib2.Request(self.LOGIN_URL%(self._token,self._callback,str(int(time.time()*1000))), headers = hjHeaders)
resp = eval(self._opener.open(req).read().decode("utf-8").replace(self._callback,"").replace(";",""))
if resp["code"] == 0:
print "Login Success!"
self._logger.info("Login Success!")
return self._opener
else:
print "Wooooo, there must be something wrong~", resp["code"].encode("utf-8"), resp["message"].encode("utf-8")
self._logger.info("Wooooo, there must be something wrong~\n code = %s \n message = %s \n" % (resp["code"].encode("utf-8"), resp["message"].encode("utf-8")))

#MD5加密,用于加密密码
def _md5(self,pwd):
m = hashlib.md5()
m.update(pwd)
return m.hexdigest()
#获取登录请求中的token值
def _getToken(self,user,psw):
hjHeaders = {
"Accept":"*/*",
"Accept-Encoding":"gzip, deflate, sdch",
"Accept-Language":"zh-CN,zh;q=0.8",
"Host":"pass.hujiang.com",
"Referer":"http://bulo.hujiang.com/anon/?source=nbulo&returnurl=http%3a%2f%2fbulo.hujiang.com%2fhome%2f",
"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36",
}
req = urllib2.Request(self.TOKEN_URL%(self._callback,user,self._md5(psw),str(int(time.time()*1000))), headers = hjHeaders)
true, false, null = 'true', 'false', ''
resp = eval(self._opener.open(req).read().replace(self._callback,""))
#print resp
if resp["code"] == 0:
print "Get Token succeed~~"
self._logger.info("Get Token succeed~~")
self._token = resp["data"]["ssotoken"]
else:
print "Wooooo, there must be something wrong~", resp[code], resp[message]
self._logger.info("Wooooo, there must be something wrong~\n code = %s \n message = %s \n" % (resp["code"].encode("utf-8"), resp["message"].encode("utf-8")))

打卡

准备工作

紧跟登录的思路。打卡也是客户端给服务器发请求。继续chrome, F12.点击打卡后,我们可以发现我们签到的时候发送了一个GET请求http://bulo.hujiang.com/app/api/ajax_take_card.ashx?0.5263887415640056。其中,0.5263887415640056这个值不重要,因此可以随便生成一个满足位数要求的随机数即可。

退出登录在发送一次上面的打卡请求,发现返回了[“-1”,””]。再次登录后再发送一次打卡请求,发现返回了[“”,”406”]。总结得出,第一次打卡的时候列表的第一项是有值,且值为正。以打卡则此项值为空。

开始写代码

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
#签到
def signIn(self):
signHeaders = {
"Accept":"application/json, text/javascript, */*; q=0.01",
"Accept-Encoding":"gzip, deflate, sdch",
"Accept-Language":"zh-CN,zh;q=0.8",
"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36 LBBROWSER",
"Host":"bulo.hujiang.com",
"Referer":"http://bulo.hujiang.com/home/",
}
SIGN_URL = "http://bulo.hujiang.com/app/api/ajax_take_card.ashx?%.17f"
req = urllib2.Request(SIGN_URL%random.random(), headers = signHeaders)
resp = self._opener.open(req).read()
try:
if int(resp[0]) > 0:
print "打卡成功,获得%s沪元,共打卡%s天~~" % (resp[0],resp[1])
self._logger.info(u"打卡成功,获得%s沪元,共打卡%s天~~" % (resp[0],resp[1]))
if int(resp[0]) == 0:
print "已经打过卡了喔~~"
self._logger.info(u"已经打过卡了喔~~")
if int(resp[0]) == -1:
print "用户未激活"
self._logger.info(u"用户未激活")
except Exception, e:
print resp
self._logger.info(u"未知错误!\n %s" % resp)

碎碎念

完整代码在autoSignIn

其实研究自动登录自动打卡的模式大概是这样的

  1. 研究客户端与服务端的交互。有时候没法直接看出链接或者请求参数是怎么来的,这个时候可以借助fiddler或者浏览器自带的调试工具。参数的话,实在没辙的情况下可以研究对应的js代码。一般对应于登录按钮的click事件。
  2. 代码中尽可能的模仿真实用户的行为,欺骗服务器。

另外,前端知识很重要

参考

请言小午吃个甜筒~~