爬虫实现自动登录教务处-Kotlin版

2 篇文章 0 订阅
1 篇文章 0 订阅

今天刚刚学了kotlin,趁热打铁,这次通过一个小爬虫来练练手。
在此之前用java写过一个爬虫和app,可以方便的查询教务处的成绩,但是后来教务处有了大改,所以也用不了。

这篇文章写的很草率主要是给自己看,无爬虫经验不建议阅读。

关键地址、暴露身份等信息会脱敏

分析表单

在输入教务处的学号和密码后,通过devtools发现上传如下字段。
post表单
username和password是账号和密码,但是注意这个密码是加密过的!在之前我实现类似功能的时候,教务处还是明文密码。

lt、dllt、execution、_eventId、rmShown这些看起来莫名其妙的参数,基本都是隐藏在网页里的,这个就不说了很好获取
html表单

主要问题

因此实现登录的主要难点就是,加密
前端有个特点就是,代码是完全公开的,你可以自由的查看它的源代码,既然加密后的密码是通过表单发送的,那么肯定是在本地加密,因此解决问题的方法也很简单,就是查看它的源代码,找到加密部分。
知道它用什么算法加密,问题自然解决。

在js文件中找到加密部分
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)
执行登陆操作
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自动管理,可以使用自动管理简化操作

炸天帮牛逼

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值