今天刚刚学了kotlin,趁热打铁,这次通过一个小爬虫来练练手。
在此之前用java写过一个爬虫和app,可以方便的查询教务处的成绩,但是后来教务处有了大改,所以也用不了。
这篇文章写的很草率主要是给自己看,无爬虫经验不建议阅读。
关键地址、暴露身份等信息会脱敏
分析表单
在输入教务处的学号和密码后,通过devtools发现上传如下字段。
username和password是账号和密码,但是注意这个密码是加密过的!在之前我实现类似功能的时候,教务处还是明文密码。
lt、dllt、execution、_eventId、rmShown这些看起来莫名其妙的参数,基本都是隐藏在网页里的,这个就不说了很好获取
主要问题
因此实现登录的主要难点就是,加密
前端有个特点就是,代码是完全公开的,你可以自由的查看它的源代码,既然加密后的密码是通过表单发送的,那么肯定是在本地加密,因此解决问题的方法也很简单,就是查看它的源代码,找到加密部分。
知道它用什么算法加密,问题自然解决。
在js文件中找到加密部分
显然用的AES加密算法
再进入encryptAES方法的js文件
加密方式已经很明显了,AES-128-CBC加密,注释都写出来了。
到此,可以按照它的加密算法用自己会的语言重写加密算法就算搞定。
kotlin实现加密解密
这是一个用kotlin写的AES工具包,仅仅实现了 AES-128-CBC的加密解密
class AesUtils{
companion object{
@kotlin.ExperimentalStdlibApi
fun encrypt(data:String,key:String,iv:String):String{
var cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
var key0:ByteArray= key.encodeToByteArray()
var iv0=IvParameterSpec(iv.encodeToByteArray())
var skeySpec = SecretKeySpec(key0,"AES")
cipher.init(Cipher.ENCRYPT_MODE,skeySpec,iv0)
var encrypted = cipher.doFinal(data.encodeToByteArray())
return BASE64Encoder().encode(encrypted)
}
@kotlin.ExperimentalStdlibApi
fun decrypt(data:String,key:String,iv:String):String{
var key0:ByteArray= key.encodeToByteArray()
var skeySpec=SecretKeySpec(key0,"AES")
var iv0=IvParameterSpec(iv.encodeToByteArray())
var cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.DECRYPT_MODE,skeySpec,iv0)
var encrypted = BASE64Decoder().decodeBuffer(data)
var charset =Charsets
var temp = cipher.doFinal(encrypted)
return String(temp)
}
}
}
根据前端的js加密文件,还需要实现randomString(),getAesString(),encryptAes()三个放啊
const val chars="ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678"
const val charsLen=chars.length
fun randomString(len:Int):String {
var retStr = ""
for (i in 0 until len) {
var index = Math.floor(Math.random()*charsLen).toInt()
retStr += chars.get(index)
}
return retStr
}
@kotlin.ExperimentalStdlibApi
fun getAesString(data:String,key0:String,iv0:String):String{
return AesUtils.encrypt(data,key0,iv0)
}
@kotlin.ExperimentalStdlibApi
fun encryptAes(data:String ,aesKey:String):String{
var encrypted =getAesString(randomString(64)+data,aesKey,randomString(16))
return encrypted
}
写爬虫
加密问题解决了,就可以写网络请求相关操作了。
分析
通过chrome打开一个无痕浏览器(没有保存cookie)
执行登陆操作
发现登陆上传表单的url是需要cookie,因此需要先访问主页获取cookie,在上传表单。
感觉很简单,没有什么好分析的,下一步吧。
写代码
网络请求库网用的okhttp,感觉挺好的
var httpclient:OkHttpClient = OkHttpClient.Builder()
.cookieJar(LocalCookieJar())
.build()
var request=Request.Builder()
.url("http://xxxxxx.index.html")
.get()
.build()
var firstresponse=httpclient.newCall(request).execute()
var html=firstresponse.body()!!.string()
var lt="name=\"lt\" value=\"(.*)\"/>"
var execution="name=\"execution\" value=\"(.*)\"/>"
var key="pwdDefaultEncryptSalt = \"(.*)\""
var password:String = encryptAes("password",getSubUtilSimple(html,key))
firstresponse.close()
第一次访问教务处,目的是获取cookie以及下次post表单时候需要的关键字段:lt、execution,以及加密的迷药key
其中getSubUtilSimple()方法是通过正则表达式获取html中指定字段
fun getSubUtilSimple(soap:String,regx:String):String{
var patter = Pattern.compile(regx)
var m = patter.matcher(soap)
while (m.find()){
return m.group(1)
}
return ""
}
在builder okhttpclient实例的时候使用了cookiejar方法并传入了LocalCookieJar(),作用是帮我们自动管理cookie,避免在每个请求都重复接受cookie上传cookie。
LocalCookieJar类实现如下
class LocalCookieJar:CookieJar{
var cookies:MutableList<Cookie> = arrayListOf()
override fun loadForRequest(p0: HttpUrl): MutableList<Cookie> {
return this.cookies
}
override fun saveFromResponse(p0: HttpUrl, p1: MutableList<Cookie>) {
this.cookies = p1
}
}
接下来就可以上传表单数据实现登录功能了
var requestbody = FormBody.Builder()
.add("username","17xxxxxx")
.add("password",password)
.add("dllt","userNamePasswordLogin")
.add("execution",getSubUtilSimple(html,execution))
.add("lt",getSubUtilSimple(html,lt))
.add("_eventId","submit")
.add("rmShown","1")
.build()
var request2=Request.Builder()
.url("http://xxxxxxxxxx.index.html")
.post(requestbody)
.build()
var response = httpclient.newCall(request2).execute()
println(response.body()!!.string())
response.close()
通过输出的html内容来看,登陆成功!
由于cookie都由cookiejar统一管理,因此接下来,就可以爬你所有想要爬的数据。
炸天帮牛逼
算了感觉字数太少了,写一个总结吧
总结
1.如果上传表单有很多字段,并且不知其意,那么大多可以在当前页面的源代码中找到。
2.涉及到加密字段,基本上都可以通过查看js代码找到解决方案
3.cookie管理可以自动化。在初学的时候,可能会请求一个链接就通过header(“set-cookie”)来获取cookie,并在下次请求带上该cookie。其实很多http请求库都有cookie自动管理,可以使用自动管理简化操作
炸天帮牛逼