本人是矿大学信息安全的大三狗,混了两年日子之后幡然醒悟决定做些自己早就想做的事情,学校的联通宽带是按时长收费的,但是每次查询已用时长和所扣费用步骤都十分的繁琐,大二的时候都想着要自己做一个APP来帮助大家减少这些繁琐的步骤,终于拖了半年多才开始做。从只有一点Java基础到最后做出来可用的APP只用了一个星期的时间,以下是干货内容。
我们最先要上的是成果图,还有在学校发的帖子:http://tieba.baidu.com/p/4233858160?pid=81256661492&cid=0#81256661492
首先我们先看一下正常的查询步骤,第一步,登陆网上营业厅,还要输入繁琐的宽带账户,的确是比较麻烦。
第二步以及以后,反正就是点点点,就不讲解了。
(加载比较慢不好意思,一月份因为没有用所以没有数据,就拿去年12月的代替吧。)
然后我要怎么做呢,首先我知道应该用网络爬虫,我就百度怎么用Java写一个爬虫,然后知道了服务器传输数据都是用的post和get方法,然后在浏览器里面使用合适的工具来将想要的包抓取一下,我学习使用的是火狐浏览器的插件firebug,真的是非常好用的工具推荐给大家。
至于在实际动手写的过程中走过许多许多弯路,最后真正实现了以后才发现其实是很简单。以下开始讲电脑上Java的具体实现。
先讲一下最基本的原理,我们用一个httpclient包中的httpclient帮我们处理cookies,cookies就是一个网站的通用登录凭证,一次登录凭借cookie访问其他页面不用再次登录。我们首先在登录界面登录,再访问查询的页面,应该就能得到想要的数据,这是基本思想。
首先我们先观察登陆的时候是怎么给服务器发送数据的。
其中几个参数意义不明,我换其他账号用相同的参数get过去是一样的,所以我们构造一个url传送给服务器进行验证即可。
以下是Java代码中构造的一段代码(Android中自带的org.apache包中好像没有这个构造函数了,所以还是直接写一个url上去比较方便。)
/**
*登陆
*生成uri用get方法传递过去即可
*/
URIuri=new URIBuilder()
.setScheme("https")
.setHost("uac.10010.com")
.setPath("/portal/Service/MallLogin")
.setParameter("callback","jQuery17204165900954174946_1450622603532")
.setParameter("redirectURL","http%3A%2F%2Fwww.10010.com")
.setParameter("userName","051602198839")//用户名
.setParameter("password","xxxxxx")//密码
.setParameter("pwdType","01")
.setParameter("productType","04")
.setParameter("redirectType","01")
.setParameter("areaCode","350")
.setParameter("arrcity","%E5%BE%90%E5%B7%9E")
.setParameter("_","1450622609168")
.build();//生成想要的URL
HttpGethtg0=new HttpGet(uri);
接下来我们再看我们想要的数据是怎么来的。
很明显是通过一系列的post方法从服务器的response中以json的形式返回的。其中flowfee就是费用,totalflow时长。
这里有一个问题难住了很久,百度了好久最后才实验出来一个解决方法,那就是我直接模拟这个post包向服务器的地址post数据并没有获得想要的返回,而是提示500错误,最后的解决办法是这样的。从上图可以看到我们psot过去的还有很多不明意义的东西,我点开都看了看完全不知道是干嘛的,但是事实就是我们把前面那几条不明意义的数据向服务器post过去后就可以正常返回我们想要的数据了。至于应该怎么解析json之类的小问题百度一下就好啦。
在本文最后会附上电脑的Java源码,记得要导入几个jar包才可以运行。
接下来就是神一般的三天安卓速成大法了,我在网上找了几本Android开发教程,从目录里找我需要用到的章节,只学需要用到的地方,所以才能只用了三天就写出来这个安卓程序。实际上也很简单,只有一个Activity,布局上直接拖得控件也没做什么设计。真正的难点是多线程的使用。因为在Android中需要处理网络任务的时候不能再主线程中处理,主线程只能进行UI的处理。所以在如何使用handler这方面百度了很多很多例子,最终成功的实现了多线程的编写。除了最主要的抓取数据的程序外,剩下就是一些记住密码啊,从系统中读取当前日期之类的小地方的细节,最终写出来了这个APP并且先给同学试用了一下,最终上传到百度云通过贴吧和空间稍微推广一下希望能够帮到更多的同学。
其实做完之后自己的感想就是,现在网络上资源十分丰富,也有各种前辈写的各种blog能够给你提供详细的讲解和实例,你真正需要挑战的是自己的耐性。能不能够静下心来决心去做好这件事,然后在试错的道路上坚持下来一直走到你最终找到了正确的道路的那一刻。
我一开始不知道httpclient,试图自己处理cookies,下载安卓的开发软件和环境也是个挑战,后来Android开发的时候导入jar包也出了很多问题(最后用的本身SDK带的org.apache包),调试的时候不知道断点怎么用的,多线程试图模仿着写了3个都没能用,还有好多乱七八糟的问题,但是现在都想不起来了,只是记得当时自己哪怕很烦,哪怕半夜断网我开着流量下sdk,我都没有放弃,可能总共花了5,60个小时做这个事,其中估摸着除了十个小时是在做正确的事,其他时候都是在做无用功,但是我还是做出来了。
这是我上大学以来做出的最有成就感的事情了,完全独立的解决(好吧,Android调试我问了问做过开发的同学)一个问题,真的很有成就感。这个算是教程也算是心得的东西早就想写了,但是自己又犯了拖延症一直到放假都没有写出来。现在发出来望各位大学迷茫的it狗们共勉。
附:
import java.net.URI; import java.util.ArrayList; import java.util.List; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; importorg.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.utils.URIBuilder; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import net.sf.json.JSONObject; public class HelloWorld { public static void main(String[] args) throws Exception{ // TODO Auto-generated method stub CloseableHttpClienthttpclient = HttpClients.createDefault(); /** *登陆 *生成uri用get方法传递过去即可 */ URIuri=new URIBuilder() .setScheme("https") .setHost("uac.10010.com") .setPath("/portal/Service/MallLogin") .setParameter("callback","jQuery17204165900954174946_1450622603532") .setParameter("redirectURL","http%3A%2F%2Fwww.10010.com") .setParameter("userName","051602198839")//用户名 .setParameter("password","xxxxxx")//密码 .setParameter("pwdType","01") .setParameter("productType","04") .setParameter("redirectType","01") .setParameter("areaCode","350") .setParameter("arrcity","%E5%BE%90%E5%B7%9E") .setParameter("_","1450622609168") .build();//生成想要的URL HttpGethtg0=new HttpGet(uri); HttpResponseresponse0 = httpclient.execute(htg0); System.out.println(htg0.getURI()); System.out.println(response0.getStatusLine()); Stringconfirm = EntityUtils.toString(response0.getEntity(),"utf-8"); if (confirm.contains("resultCode:\"0000\"")) System.out.println("登陆成功"); /* * 依次向服务器post * 前面这些貌似必须先请求响应,会自动生成cookie * 均为不带post实体内容的 */ HttpPostpostU=new HttpPost(); List<String>postUri=new ArrayList<>(); postUri.add("http://iservice.10010.com/e3/static/check/checklogin/?_=1450697102496"); postUri.add("http://iservice.10010.com/e3/static/common/info?_=1450697103996"); postUri.add("http://iservice.10010.com/e3/static/header"); postUri.add("http://iservice.10010.com/e3/static/query/newsAssistant/search?_=1450697104007"); postUri.add("http://iservice.10010.com/e3/static/check/checklogin?_=1450697104017"); postUri.add("http://iservice.10010.com/e3/static/check/checklogin?_=1450697104361"); for(Stringpost:postUri) { URIpoU=new URIBuilder(post).build(); postU.setURI(poU); response0=httpclient.execute(postU); System.out.println(response0.getStatusLine()); System.out.println("执行完成"); Stringresult0=EntityUtils.toString(response0.getEntity(),"utf-8"); System.out.println(result0); } System.out.println("准备完成"); /* * 最后post能够得到想要数据的那条 */ HttpPosthtp=new HttpPost("http://iservice.10010.com/e3/static/query/callFlow?_=1450697104585&menuid=000100030004"); List<NameValuePair>parameters =new ArrayList<NameValuePair>(); //请求体 parameters.add(new BasicNameValuePair("pageNo","1")); parameters.add(new BasicNameValuePair("pageSize","20")); parameters.add(new BasicNameValuePair("beginDate","2015-12-01"));// parameters.add(new BasicNameValuePair("endDate","2015-12-25")); UrlEncodedFormEntity urlEntity = newUrlEncodedFormEntity(parameters,"UTF-8"); htp.setEntity(urlEntity); CloseableHttpResponseresponse1 =httpclient.execute(htp); System.out.println(response1.getStatusLine()); String result = EntityUtils.toString(response1.getEntity(),"utf-8"); System.out.println(result); JSONObject ob = JSONObject.fromObject(result); String totalflow=ob.get("totalflow").toString(); System.out.println(totalflow); } }
附2(Android源码):
package com.example.sunyang.myapplication; import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import org.json.JSONArray; import org.json.JSONObject; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.MulticastSocket; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; public class MainActivity extends AppCompatActivity { TextView res; HttpClient httpClient; SharedPreferences preferences; SharedPreferences.Editor editor; /* 主线程中构造handler,更新UI的请求用sendMessage发送,在下面完成. */ Handler handler=new Handler(){ public void handleMessage(Message msg){ if (msg.what==0x123){ res.append(msg.obj.toString()+"\n"); } } }; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); httpClient=new DefaultHttpClient(); preferences=getSharedPreferences("userInfo",MODE_WORLD_READABLE); editor=preferences.edit(); res=(TextView) findViewById(R.id.result); Button bn=(Button) findViewById(R.id.button); final EditText username=(EditText) findViewById(R.id.userName); final EditText password=(EditText) findViewById(R.id.password); final CheckBox checkBox=(CheckBox) findViewById(R.id.checkBox); if (preferences.getBoolean("AUTO_ISCHECK",true)){ username.setText(preferences.getString("userName", "")); password.setText(preferences.getString("password", "")); } bn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final String name=username.getText().toString(); final String pwd=password.getText().toString(); if(checkBox.isChecked()){ editor.putString("userName", name); editor.putString("password", pwd); editor.putBoolean("AUTO_ISCHECK", true).commit();//存储用户名密码在xml文件 } else editor.putBoolean("AUTO_ISCHECK",false).commit(); new Thread() { /* 用于网络的线程 */ public void run(){ try{ /* 用于登陆的代码 */ String url="https://uac.10010.com/portal/Service/MallLogin?callback=jQuery17204165900954174946_1450622603532&redirectURL=http%253A%252F%252Fwww.10010.com" + "&userName=" +name+ "&password=" +pwd+ "&pwdType=01&productType=04&redirectType=01&areaCode=350&arrcity=%25E5%25BE%2590%25E5%25B7%259E&_=1450622609168"; HttpGet get=new HttpGet(url); HttpResponse response=httpClient.execute(get); String confirm = EntityUtils.toString(response.getEntity(), "utf-8"); Message msg = new Message(); msg.what=0x123; if (confirm.contains("resultCode:\"0000\"")){ msg.obj="登陆成功"; handler.sendMessage(msg); } else { msg.obj="用户名或者密码错误"; handler.sendMessage(msg); } /* 发送post请求的代码 */ List<String> postUri=new ArrayList<>(); postUri.add("http://iservice.10010.com/e3/static/check/checklogin/?_=1450697102496"); postUri.add("http://iservice.10010.com/e3/static/common/info?_=1450697103996"); postUri.add("http://iservice.10010.com/e3/static/header"); postUri.add("http://iservice.10010.com/e3/static/query/newsAssistant/search?_=1450697104007"); postUri.add("http://iservice.10010.com/e3/static/check/checklogin?_=1450697104017"); postUri.add("http://iservice.10010.com/e3/static/check/checklogin?_=1450697104361"); for(String post:postUri) { HttpPost postU=new HttpPost(post); HttpResponse response0=httpClient.execute(postU); } Message msg1=new Message(); msg1.what=0x123; msg1.obj="发送请求"; handler.sendMessage(msg1); SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd"); SimpleDateFormat sdf1=new SimpleDateFormat("yyyy-MM"); String endDate=sdf.format(new Date()); String beginDate=sdf1.format(new Date())+"-01"; Message msg2=new Message(); msg2.what=0x123; msg2.obj="开始日期:"+beginDate+"\t截止日期:"+endDate; handler.sendMessage(msg2); HttpPost htp=new HttpPost("http://iservice.10010.com/e3/static/query/callFlow?_=1450697104585&menuid=000100030004"); List<NameValuePair> parameters = new ArrayList<NameValuePair>(); //请求体 parameters.add(new BasicNameValuePair("pageNo", "1")); parameters.add(new BasicNameValuePair("pageSize", "20")); parameters.add(new BasicNameValuePair("beginDate", beginDate)); parameters.add(new BasicNameValuePair("endDate", endDate)); UrlEncodedFormEntity urlEntity = new UrlEncodedFormEntity(parameters, "UTF-8"); htp.setEntity(urlEntity); HttpResponse result=httpClient.execute(htp); String s = EntityUtils.toString(result.getEntity(), "utf-8"); //处理结果 JSONObject ob = new JSONObject(s); String totalflow=ob.get("totalflow").toString(); String fee=ob.get("flowfee").toString(); int total=Integer.parseInt(totalflow)/3600; Message msg3=new Message(); msg3.what=0x123; msg3.obj="时长合计:"+totalflow+"秒\n"+"大约为"+total+"小时\n"+"费用合计:"+fee; handler.sendMessage(msg3); } catch (Exception e){ e.printStackTrace(); } } }.start(); } }); } }