前言
先吐槽一下北京市预约挂号统一平台,这个平台是为了方便更多的人进行挂号,不用去排长长的队伍,但是!但是!9:30 号出来了,服务器失去了响应,等可以响应了,号已经没了,甚是痛苦。因此突发奇想,是不是可以做一个自动挂号的东西呢?虽然违背原则,但是,谁让我会写程序呢?哈哈!
实现思路
使用程序进行模拟用户登录,之后获取到相应医院,科室,医生,订单的ID,进行多线程操作,即可实现自动登录功能。ps:说的简单,搞了一个周末。
准备工作
软件: eclipse、 Postman 、 Burp Suite Community Edition 、 firefox
Jar包:commons-logging-1.2.jar、httpclient-4.5.3.jar、httpcore-4.4.6.jar、json-20160810.jar、jsoup-1_10_2.jarlog4j-1.2.17.jar
网站分析
网站的功能较为简单,由医院、科室、医生构成。当下订单的时候还有一个订单号。当然还有个在线支付的功能,我使用的医院没有采用线上支付。因此这部分不做考虑。
获取医生ID、订单ID的网址为:yyghwx.bjguahao.gov.cn/common/dutysource/appoint/1,200041160.htm?dutyDate=2017-12-27&departmentName=%25E7%2589%25B9%25E9%259C%2580%25E6%2594%25BE%25E5%25B0%2584%25E7%25A7%2591%25E9%2597%25A8%25E8%25AF%258A1&jingdu=&weidu= 打开这个网址,发现医生对应的行是一个<a>标签,即可获取到医生ID,订单ID。
订单信息确认URL:yyghwx.bjguahao.gov.cn/common/order/confirm/1/200041160/201122658/50169528.htm
编程实践
- 构建自己的HttpClient,让自己的访问一直是在同一个session下
构造自己的Httpclient
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HeaderIterator;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.*;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.DefaultRedirectStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.json.JSONObject;
public class HttpClient_Mine {
private static final Logger LOG = LogManager.getLogger(HttpClient_Mine.class);
public static CloseableHttpClient httpClient = null;
public static HttpClientContext context = null;
public static CookieStore cookieStore = null;
public static RequestConfig requestConfig = null;
static {
init();
}
private static void init() {
context = HttpClientContext.create();
cookieStore = new BasicCookieStore();
// 配置超时时间
requestConfig = RequestConfig.custom().setConnectTimeout(120000).setSocketTimeout(60000).setConnectionRequestTimeout(60000).build();
// 设置默认跳转以及存储cookie
httpClient = HttpClientBuilder.create().setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
.setRedirectStrategy(new DefaultRedirectStrategy()).setDefaultRequestConfig(requestConfig)
.setDefaultCookieStore(cookieStore).build();
}
/**
* http get
*
* @param url
* @return response
* @throws ClientProtocolException
* @throws IOException
*/
public static CloseableHttpResponse get(String url) throws ClientProtocolException, IOException {
HttpGet httpget = new HttpGet(url);
CloseableHttpResponse response = httpClient.execute(httpget, context);
try {
cookieStore = context.getCookieStore();
List<Cookie> cookies = cookieStore.getCookies();
for (Cookie cookie : cookies) {
System.out.println("key:" + cookie.getName() + " value:" + cookie.getValue());
LOG.debug("key:" + cookie.getName() + " value:" + cookie.getValue());
}
} finally {
response.close();
}
return response;
}
/**
* http post
* @param url
* @param parameters
* form表单
* @return response
* @throws ClientProtocolException
* @throws IOException
*/
public static CloseableHttpResponse post(String url, String parameters)
throws ClientProtocolException, IOException {
HttpPost httpPost = new HttpPost(url);
List<BasicNameValuePair> nvps = toNameValuePairList(parameters);
httpPost.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost, context);
try {
cookieStore = context.getCookieStore();
List<Cookie> cookies = cookieStore.getCookies();
for (Cookie cookie : cookies) {
LOG.debug("key:" + cookie.getName() + " value:" + cookie.getValue());
}
} finally {
response.close();
}
return response;
}
@SuppressWarnings("unused")
private static List<BasicNameValuePair> toNameValuePairList(String parameters) {
List<BasicNameValuePair> nvps = new ArrayList<BasicNameValuePair>();
if(parameters == null || "".equals(parameters)) {
return nvps;
}
String[] paramList = parameters.split("&");
for (String parm : paramList) {
int index = -1;
for (int i = 0; i < parm.length(); i++) {
index = parm.indexOf("=");
break;
}
String key = parm.substring(0, index);
String value = parm.substring(++index, parm.length());
nvps.add(new BasicNameValuePair(key, value));
}
System.out.println(nvps.toString());
return nvps;
}
/**
* 模拟AJAX post/json 方式进行提交。
* @param <PostMethod>
* @param url
* @param parameters
* @return response
* @throws ClientProtocolException
* @throws IOException
*/
public static CloseableHttpResponse postAjax(String url, String parameters)
throws ClientProtocolException, IOException {
//设置代理
//HttpHost proxy = new HttpHost("127.0.0.1", Integer.valueOf(8080));
//RequestConfig config = RequestConfig.custom().setProxy(proxy).build();
HttpPost httpPost = new HttpPost(url);
//httpPost.setConfig(config);
//parameters = toJsonString(parameters);
StringEntity entity = new StringEntity(parameters, "utf-8");// 处理中文乱码问题。
entity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "*/*");
//设置post请求方式
CloseableHttpResponse response = httpClient.execute(httpPost);
if (response.getStatusLine().getStatusCode() == 200) {
HttpEntity he = response.getEntity();
String s = EntityUtils.toString(he, "UTF-8");
System.out.println(s);
if(s.contains("成功")) {
Proxy.status = "200";
}
}
try {
cookieStore = context.getCookieStore();
List<Cookie> cookies = cookieStore.getCookies();
for (Cookie cookie : cookies) {
LOG.debug("key:" + cookie.getName() + " value:" + cookie.getValue());
}
} finally {
response.close();
}
return response;
}
/**
* 手动增加cookie
*
* @param name
* @param value
* @param domain
* @param path
*/
public static void addCookie(String name, String value, String domain, String path) {
BasicClientCookie cookie = new BasicClientCookie(name, value);
cookie.setDomain(domain);
cookie.setPath(path);
cookieStore.addCookie(cookie);
}
/**
* 把结果console出来
*
* @param httpResponse
* @throws ParseException
* @throws IOException
*/
public static void printResponse(HttpResponse httpResponse) throws ParseException, IOException {
// 获取响应消息实体
HttpEntity entity = httpResponse.getEntity();
// 响应状态
System.out.println("status:" + httpResponse.getStatusLine());
System.out.println("headers:");
HeaderIterator iterator = httpResponse.headerIterator();
while (iterator.hasNext()) {
System.out.println("\t" + iterator.next());
}
// 判断响应实体是否为空
if (entity != null) {
String responseString = EntityUtils.toString(entity);
System.out.println("response length:" + responseString.length());
System.out.println("response content:" + responseString.replace("\r\n", ""));
}
System.out.println("------------------------------------------------------------------------------------------\r\n");
}
/**
* 把当前cookie从控制台输出出来
*
*/
public static void printCookies() {
System.out.println("headers:");
cookieStore = context.getCookieStore();
List<Cookie> cookies = cookieStore.getCookies();
for (Cookie cookie : cookies) {
System.out.println("key:" + cookie.getName() + " value:" + cookie.getValue());
}
}
/**
* 检查cookie的键值是否包含传参
*
* @param key
* @return
*/
public static boolean checkCookie(String key) {
cookieStore = context.getCookieStore();
List<Cookie> cookies = cookieStore.getCookies();
boolean res = false;
for (Cookie cookie : cookies) {
if (cookie.getName().equals(key)) {
res = true;
break;
}
}
return res;
}
/**
* 直接把Response内的Entity内容转换成String
*
* @param httpResponse
* @return
* @throws ParseException
* @throws IOException
*/
public static String toString(CloseableHttpResponse httpResponse) throws ParseException, IOException {
// 获取响应消息实体
HttpEntity entity = httpResponse.getEntity();
if (entity != null)
return EntityUtils.toString(entity);
else
return null;
}
- 获取医生索引网页基本信息
解析本页面中的 超链接标签 获取到链接标签之后,对其进行拆分,获得医院ID,部门ID,医生ID,订单ID。为后续做准备。
![]()
获取方法:
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
public static void getAlldoctor(String strUrl) {
try {
URL url = new URL(strUrl);
Document doc = Jsoup.parse(url, 20000);
Elements element = doc.getElementsByClass("hyxzym_box");
Elements enables;
for (Element elem : element) {
enables = elem.getElementsByClass("hyxzym_b_sj hy_wxyh");
for (Element enable : enables) {
try {
String href = enable.getElementsByTag("a").first().attr("href");
href = href.substring(href.indexOf("http:"), href.indexOf(".htm") + 4);
href = href.substring(href.indexOf("confirm") + 8, href.lastIndexOf("."));
String[] split = href.split("/");
Map<String, String> map = new HashMap<String, String>();
map.put("hospitalId", split[0]);
map.put("departmentId", split[1]);
map.put("doctorId", split[2]);
map.put("orderId", split[3]);
//多线程问题
Proxy.changeDoctor(map);
} catch (Exception e) {
e.printStackTrace();
}
}
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
System.err.println("获取失败" + strUrl);
}
}
- 实现模拟登录功能
private static void login() {
try {
//处理登录状态:
String parameters = "";
parameters += "redirectUrl=http://yyghwx.bjguahao.gov.cn/u/usercenter.htm";
parameters += "&mobileNo="+Proxy.userName;
parameters += "&password="+Proxy.password;
parameters += "&channel=ksdl";
CloseableHttpResponse post = HttpClient_Mine.post("http://yyghwx.bjguahao.gov.cn/quicklogin.htm", parameters);
HttpClient_Mine.printResponse(post);
} catch (ClientProtocolException e) {
System.err.println("登录失败啦,你重启一下吧。");
// TODO Auto-generated catch block
} catch (IOException e) {
// TODO Auto-generated catch block
} catch (ParseException e) {
// TODO Auto-generated catch block
}
}
- 发送短信方法
北京114挂号平台是通过你的登录信息发送的短信,因此只要登陆了,直接就可以发送post请求就可以进行登录了。
public static void sendMessage() {
try {
CloseableHttpResponse post = HttpClient_Mine.post("http://yyghwx.bjguahao.gov.cn/v/sendorder.htm","");
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
- 实现自动挂号
挂号还需要验证码,需要进行手动输入。这个挂号平台最坑的地方是,必须要先浏览一下页面,然后还要等5s钟之后才能发送验证码,否则会提示你验证码不正确。
public void guaHao(){
try {
String url = "http://yyghwx.bjguahao.gov.cn/common/order/confirm/"+this.hospitalId+"/"+this.departmentId+"/"+this.doctorId+"/"+this.dutySourceId+".htm";
HttpClient_Mine.get(url);
System.out.println("---------->获取订单页面成功!!\n"+url);
Thread.sleep(5000);
sendMessage();
Scanner sc = new Scanner(System.in);
System.out.println("输入验证码!");
String smsMessage = sc.nextLine();
//登录成功之后,进行挂号。
String parameters = "dutySourceId="+this.dutySourceId; //订单号,在url里面进行截取
parameters += "&hospitalId="+ this.hospitalId;
parameters += "&departmentId="+this.departmentId;
parameters += "&doctorId="+this.doctorId;//医生号,在url里面进行截取
parameters += "&patientId="+Proxy.patientId;//就诊人编号
parameters += "&hospitalCardId=";//就诊卡,可传空
parameters += "&medicareCardId="; //医保卡,可传空
parameters += "&reimbursementType=12";
parameters += "&smsVerifyCode="+smsMessage;//验证码
parameters += "&childrenBirthday=";//可传空!
parameters += "&htype=1";
parameters += "&openId=";//可传空!
parameters += "&isShare=";//可传空!
parameters += "&pubHospitalId=";//可传空!
parameters += "&isAjax=true";
HttpClient_Mine.postAjax("http://yyghwx.bjguahao.gov.cn/common/order/submit.htm", parameters);
//HttpClient_Mine.printResponse(post2);
} catch (Exception e) {
e.printStackTrace();
}
}
- 结语
将以上的方法结合起来,自动挂号功能也就实现了,当然还可以使用多线程,实现更大可能性的刷票。当然,以上方法自己动手组装吧,总会有十分有意思的事情发生。