最近帮别人做的一个抢单程序,客户提供的《需求说明文档》如下:


    综述:抢单功能比较容易实现,目的是为了抢过别的抢单竞争对象,需要综合外挂软件\电脑硬件\网络\网路提出综合方案实现抢单

    一、登录网站

    网址:https://www.agkmg.com/zh-tw/UserLogin

    要求自动输入账号密码登录

    登录后还需要输入一次交易密码

    二、抢EP

    登录成功后,点击EP交易—EP买入,这个时候会提示输入交易密码

    输入后显示抢ep界面,需要不停的刷新这个界面,直到列表出现一条或多条记录,选择EP数量最大的优先购买,点击购买,如果没抢到会出现如下提示,直接屏蔽继续刷。

    如果抢到出现如下界面,输入验证码提交

    提交成功后,需要自动点掉弹出的提示窗口。并保存抢购记录。

    这样就抢成功了,如果没抢到还会出现上面不存在的提示。

    三、切换账号

    由于一个账号抢到EP后就在交易未完成的情况下就不能重复抢了,所以需要换账号。需要一个导入账号列表,可以循环抢,抢到过的账号会有“您有ep正在交易”直接跳过

    流程中出现的一些提示异常等要屏蔽,保证程序一直进行,账号不停的循环,完成了一遍继续从头开始循环。需要提供 手动开始和结束的 按钮。


    好了,进入正题。

    客户的目标是:抢单成功率要达到至少50%,最好能达80%以上。

    先说下这个网站的背景:这估计是一个类似旁氏骗局的P2P金融网站,服务器在香港,容器是IIS,开发语言是.NET,看起来比较low(而且经常会被搞挂掉)

    抢单的流程其实很简单,大致如下:

    1、模拟浏览器登录

    2、到交易大厅不断读取交易列表

    3、当看到“订单”时立刻把他抢下来

    4、提交订单,此时一个4位数的纯数字验证码:(还是很low)

    5、提交后如果返回交易待确认列表则表示抢单成功了

    【难点】实际场景中会有很多像我们一样的小程序同时在跑,所以我们的目标就是要抢赢别人!因此,一定要快!准!狠!

    【方案】通过两条线程同时跑,一条用来定时获取验证码,存储在一个成员变量中,另一条线程负责登录系统进行抢单。当获取到订单号时,就把所需的订单号、Cookie、Token和验证码一起提交给服务器。

    【瓶颈】网络质量。因为服务器搭在香港,因此就需要一台香港的主机来跑这个程序,尽量把网络延迟控制在最小。

    【功能】验证码识别的时间间隔需要可配置,抢单的最小金额和最大金额都要可配置,如下图所示。

     

    PS: 本来是设置得每隔2分钟刷新一次验证码,后来在实际运行测试中发现根本没这必要那么快更新,这LOW爆的系统验证码居然可以重复用!后来直接改成每隔30分钟刷新了,可能还可以设置得更久一些 - -!

    程序执行界面:

    主程序的完整源代码:

    package com.relyn;
    
    import java.awt.Toolkit;
    import java.io.BufferedReader;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileWriter;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Properties;
    
    import sun.audio.AudioPlayer;
    import sun.audio.AudioStream;
    
    public class RelynAGK {
    
    	public static int vCodeRefreshTime = 0;
    	public static String dateString = "";
    	public static String verifyCode = "";
    	public static String orderPrice = "";
    	public static int orderPriceMin = 0;
    	public static int orderPriceMax = 0;
    	public static String userId = "";
    	public static String userPwd = "";
    	public static String userPwd2 = "";
    	public static int roundTimes = 0;
    
    	/**
    	 * @param args
    	 */
    	public static void main(String[] args) {
    		
    		//读取配置文件
    		Properties pro = new Properties();
    		FileInputStream in;
    		try {
    			in = new FileInputStream("source/base.properties");
    			pro.load(in);
    			vCodeRefreshTime = Integer.parseInt(pro.getProperty("vCodeRefreshTime")) * 60000;
    			orderPriceMin = Integer.parseInt(pro.getProperty("orderPriceMin"));
    			orderPriceMax = Integer.parseInt(pro.getProperty("orderPriceMax"));
    			in.close();
    		} catch (Exception e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    
    		System.out.println("#RelynAGK VER.3.6");
    		System.out.println("#验证码获取时间间隔:" + vCodeRefreshTime/60000 + "分钟");
    		System.out.println("#最小抢单金额:" + orderPriceMin + "元");
    		System.out.println("#最大抢单金额:" + orderPriceMax + "元");
    		
    		Thread tGetData = new Thread(new Runnable() {
    			public void run() {
    				invokeGetData();
    			}
    		});
    		
    		Thread tVerifyCode = new Thread(new Runnable() {
    			public void run() {
    				invokeGetVerifyCode();
    			}
    		});
    		
    		// 数据请求线程启动
    		tGetData.start();
    		
    		// 验证码获取线程启动
    		tVerifyCode.start();
    	}
    	
    	public static void invokeGetData() {
    		try {
    			//登录AGK
    			System.out.println("#准备登录AGK系统...");
    			loginAGK();
    		} catch (Exception e) {
    			e.printStackTrace();
    			System.out.println("#出现异常,正在重试...");
    			invokeGetData();
    		}
    	}
    	
    	public static void loginAGK() throws Exception {
    		
    		String encoding = "UTF-8";
    		System.out.println("#正在读取用户名密码...");
    		File file = new File("AccountPassword.txt");
    		if (file.isFile() && file.exists()) { // 判断文件是否存在
    			InputStreamReader read = new InputStreamReader(new FileInputStream(file), encoding);// 考虑到编码格式
    			BufferedReader bufferedReader = new BufferedReader(read);
    			String lineTxt = null;
    			while (true) {
    				if ((lineTxt = bufferedReader.readLine()) == null) {
    					read = new InputStreamReader(new FileInputStream(file),
    							encoding);// 考虑到编码格式
    					bufferedReader = new BufferedReader(read);
    					lineTxt = bufferedReader.readLine();
    				}
    				String[] sourceStrArray = lineTxt.split(",");
    				userId = sourceStrArray[0];
    				userPwd = sourceStrArray[1];
    				userPwd2 = sourceStrArray[2];
    				System.out.print("登录账号:" + userId);
    				System.out.print(",登录密码:" + userPwd);
    				System.out.println(",交易密码:" + userPwd2);
    				// 获取打开登录页面
    				String url = "https://www.agkmg.com/zh-tw/UserLogin";
    				String str = HttpClientUtil.invokeGet(url, null, "UTF-8", 5000, 0);
    				Map<String, Object> m = new HashMap<String, Object>();
    				// 获取令牌
    				str = str.substring(str.indexOf("__RequestVerificationToken"));
    				str = str.substring(str.indexOf("value=\"") + 7);
    				str = str.substring(0, str.indexOf("\""));
    				m.put("__RequestVerificationToken", str);
    				m.put("Account", userId);
    				m.put("Password", userPwd);
    				// 登录系统
    				url = "https://www.agkmg.com/zh-tw/UserLogin";
    				str = HttpClientUtil.invokePost(url, 5000, m);
    				// 打开首页
    				url = "https://www.agkmg.com/zh-tw";
    				str = HttpClientUtil.invokeGet(url, null, "UTF-8", 5000, 0);
    				//System.out.println(str);
    				int j = 0;
    				while (str == null || str.indexOf("AGK-首頁") == -1 || str.indexOf("Server Error") != -1) {
    					//System.out.println("#重试登录中……");
    					str = HttpClientUtil.invokeGet(url, null, "UTF-8", 5000, 0);
    					j++;
    					if (j == 100)
    						break;
    				}
    				if (str.indexOf("AGK-首頁") != -1)
    					System.out.println("#登录成功,准备开始请求数据……");
    				// 第二次获取令牌
    				url = "https://www.agkmg.com/zh-tw/usersecond/EPbuy";
    				str = HttpClientUtil.invokeGet(url, null, "UTF-8", 5000, 0);
    				url = "https://www.agkmg.com/zh-tw/User/SecondLogin";
    				str = HttpClientUtil.invokeGet(url, null, "UTF-8", 5000, 0);
    				str = str.substring(str.indexOf("__RequestVerificationToken"));
    				str = str.substring(str.indexOf("value=\"") + 7);
    				str = str.substring(0, str.indexOf("\""));
    				// 输入交易密码并登录
    				m.put("__RequestVerificationToken", str);
    				m.put("ReturnUrl", "/zh-tw/usersecond/EPbuy");
    				m.put("Password2", userPwd2);
    				url = "https://www.agkmg.com/zh-tw/User/SecondLogin";
    				str = HttpClientUtil.invokePost(url, 5000, m);
    				int k = 0;
    				while (str == null || str.indexOf("Object moved") == -1 || str.indexOf("Server Error") != -1) {
    					//System.out.println("#重试登录中……");
    					str = HttpClientUtil.invokePost(url, 5000, m);
    					k++;
    					if (k == 100)
    						break;
    				}
    				//System.out.println(str);
    				if (getIfNoPay() == 1) {
    					System.out.println("#存在“已付款待確認”的信息,更换账号登录……");
    					continue;
    				}
    				else
    					startGetData();
    			}
    		} else {
    			System.out.println("#严重错误,找不到账号配置文件!");
    		}
    	}
    	
    	// 判断是否有“已付款待確認”的信息
    	public static int getIfNoPay() {
    		String url = "https://www.agkmg.com/zh-tw/usersecond/EPbuycheck";
    		String str = HttpClientUtil.invokeGet(url, null, "UTF-8", 5000, 0);
    		while (str == null || str.indexOf("Server Error") != -1)
    			str = HttpClientUtil.invokeGet(url, null, "UTF-8", 5000, 0);
    		if (str.indexOf("已付款待確認") != -1)
    			return 1;
    		else
    			return 0;
    	}
    	
    	public static void startGetData() throws Exception  {
    		// 打开第二次登录的页面
    		String url = "https://www.agkmg.com/zh-tw/usersecond/EPbuy";
    		System.out.println("#正在启动请求数据循环……");
    		String str = HttpClientUtil.invokeGet(url, null, "UTF-8", 5000, 0);
    		// 持续刷新页面
    		while (str.indexOf("EPBuy_OK") == -1) {
    			str = HttpClientUtil.invokeGet(url, null, "UTF-8", 5000, 0);
    			// 如果服务器没有响应或者服务器返回403错误则持续请求
    			while (str == null || str.indexOf("Server Error") != -1)
    				str = HttpClientUtil.invokeGet(url, null, "UTF-8", 5000, 0);
    			// 打印页面信息
    			//System.out.println(str);
    			roundTimes++;
    			if (roundTimes % 100 == 0)
    				System.out.println(">>>>>>当前请求次数:" + roundTimes 
    					+ " / " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    			if (roundTimes == 5000) {
    				System.out.println("#请求次数已达5000次,为防止Session过期,准备重新登录……");
    				roundTimes = 0;
    				break;
    			}
    		}
    		//订单金额
    		float i = 0;
    		if (roundTimes != 0) {
    			orderPrice = str.substring(str.indexOf("<td>") + 4);
    			orderPrice = orderPrice.substring(orderPrice.indexOf("<td>") + 4);
    			orderPrice = orderPrice.substring(orderPrice.indexOf("<td>") + 4);
    			orderPrice = orderPrice.substring(orderPrice.indexOf("<td>") + 4);
    			orderPrice = orderPrice.substring(orderPrice.indexOf("<td>") + 4);
    			orderPrice = orderPrice.substring(orderPrice.indexOf("<td>") + 4);
    			orderPrice = orderPrice.substring(orderPrice.indexOf("<td>") + 4);
    			orderPrice = orderPrice.substring(0, orderPrice.indexOf("<"));
    			orderPrice = orderPrice.trim();
    			i = Float.parseFloat(orderPrice);
    			System.out.println("订单金额:" + orderPrice);
    		}
    		if (i >= orderPriceMin && i <= orderPriceMax) {
    			System.out.println("#正在准备获取购买编码……");
    			buyCodeProcess(str); //调用确认购买方法
    		}
    		else if (i == 0) {
    			
    		}
    		else {
    			System.out.println("#订单金额不在指定范围,订单取消。");
    		}
    	}
    	
    	// 取得购买编码处理
    	public static void buyCodeProcess(String str) throws Exception  {
    		// 播放成功提示音
    		playMessageSound();
    		// System.out.println(str); //输出页面
    		System.out.println("正在获取购买编码……");
    		System.out.println("循环次数:" + roundTimes);
    		str = str.substring(str.indexOf("EPBuy_OK/") + 9);
    		String buyCode = str.substring(0, str.indexOf("\""));
    		String url = "https://www.agkmg.com/zh-tw/usersecond/EPBuy_OK/" + buyCode;
    		str = HttpClientUtil.invokeGet(url, null, "UTF-8", 5000, 0);
    		// System.out.println(str); //输出页面
    		// 插入日志
    		SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");// 设置日期格式
    		dateString = df.format(new Date());
    		System.out.println("请求时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    		// 如果文件存在,则追加内容;如果文件不存在,则创建文件
    		File file = new File(dateString + ".log");
    		FileWriter fw = new FileWriter(file, true);
    		PrintWriter pw = new PrintWriter(fw);
    		pw.print("账号:" + userId);
    		pw.print(",登录密码:" + userPwd);
    		pw.print(",交易密码:" + userPwd2);
    		pw.println(" / " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    		pw.flush();
    		fw.flush();
    		pw.close();
    		fw.close();
    		int k = 1; // 定义重试次数
    		// 如果服务器没有响应则持续请求
    		while (str == null || str.indexOf("__RequestVerificationToken") == -1) {
    			str = HttpClientUtil.invokeGet(url, null, "UTF-8", 5000, 0);
    			k++; // 若循环10次仍无法取得令牌则认为已经被别人抢走
    			if (k == 10) {
    				System.out.println("购买编号:" + buyCode + " 被抢走!");
    				roundTimes = 0;
    				//插入日志
    				// 如果文件存在,则追加内容;如果文件不存在,则创建文件
    				file = new File(dateString + ".log");
    				fw = new FileWriter(file, true);
    				pw = new PrintWriter(fw);
    				pw.println("购买编号:" + buyCode + " 被抢走!");
    				pw.flush();
    				fw.flush();
    				pw.close();
    				fw.close();
    				break;
    			}
    		}
    		// 成功取得编码调用确认购买方法
    		if (str.indexOf("__RequestVerificationToken") != -1)
    			confirmBuyProcess(str,buyCode);
    	}
    	
    	// 确认购买处理
    	public static void confirmBuyProcess(String str, String buyCode) throws Exception  {
    		//抢到购买编号播放提示音
    		Toolkit.getDefaultToolkit().beep();
    		System.out.println("获得购买编号:" + buyCode);
    		// 第三次获取令牌
    		str = str.substring(str.indexOf("__RequestVerificationToken"));
    		str = str.substring(str.indexOf("value=\"") + 7);
    		String token = str.substring(0, str.indexOf("\""));
    		// 获取SubmitToken
    		str = str.substring(str.indexOf("SubmitToken"));
    		str = str.substring(str.indexOf("value=\"") + 7);
    		String submitToken = str.substring(0, str.indexOf("\""));
    		System.out.println("使用已识别验证码:" + verifyCode);
    		System.out.println("请求令牌:" + token);
    		System.out.println("提交令牌:" + submitToken);
    		HashMap<String, Object> m = new HashMap<String, Object>();
    		m.put("__RequestVerificationToken", token);
    		m.put("SubmitToken", submitToken);
    		m.put("Code", verifyCode);
    		// 交易确认请求提交
    		String url = "https://www.agkmg.com/zh-tw/UserSecond/EPBuy_OK/" + buyCode;
    		str = HttpClientUtil.invokePost(url, 5000, m);
    		// 如果服务器没有响应则持续请求
    		while (str == null)
    			str = HttpClientUtil.invokePost(url, 5000, m);
    		if (str.indexOf("EPBuyCheck") != -1) {
    			System.out.println("确认购买成功!");
    			System.out.println("服务器返回信息:");
    			System.out.println(str);
    			// 提交确认付款信息
    			System.out.println("正在提交确认付款信息……");
    			url = "https://www.agkmg.com/zh-tw/usersecond/EPBuyCheck_Pay/" + buyCode;
    			String payStr = HttpClientUtil.invokeGet(url, null, "UTF-8", 5000, 0);
    			while (payStr == null)
    				payStr = HttpClientUtil.invokeGet(url, null, "UTF-8", 5000, 0);
    			System.out.println("恭喜!确认付款成功!");
    			//System.out.println("服务器返回信息:");
    			//System.out.println(payStr);
    		} else {
    			System.out.println("购买失败!");
    			System.out.println("服务器返回信息:");
    			System.out.println(str);
    		}
    		// 验证提交的验证码是否正确
    		url = "https://www.agkmg.com/zh-tw/CommonDefault/CheckCode"; 
    		m = new HashMap<String, Object>();
    		m.put("Code",verifyCode);
    		String verifyTag = HttpClientUtil.invokePost(url, 5000, m);
    		System.out.println("服务器返回验证码识别结果:" + verifyTag);
    		// 插入日志
    		System.out.println("#准备插入日志文件……");
    		// 如果文件存在,则追加内容;如果文件不存在,则创建文件
    		File file = new File(dateString + ".log");
    		FileWriter fw = new FileWriter(file, true);
    		PrintWriter pw = new PrintWriter(fw);
    		pw.println("循环结束时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    		pw.println("获得购买编号:" + buyCode);
    //		pw.println("订单金额:" + orderPrice);
    		pw.println("已识别验证码:" + verifyCode);
    		pw.println("服务器返回验证码识别结果:" + verifyTag);
    		pw.println("请求令牌:" + token);
    		pw.println("提交令牌:" + submitToken);
    		pw.println("循环次数:" + roundTimes);
    		roundTimes = 0;
    		if (str.indexOf("EPBuyCheck") != -1) {
    			pw.println("#确认购买成功!");
    			pw.println("服务器返回信息:");
    			pw.println(str);
    			// 确认购买成功单独插入Success.log日志
    			// 如果文件存在,则追加内容;如果文件不存在,则创建文件
    			File sfile = new File("Success.log");
    			FileWriter sfw = new FileWriter(sfile, true);
    			PrintWriter spw = new PrintWriter(sfw);
    			spw.print("登录账号:" + userId);
    			spw.print(",登录密码:" + userPwd);
    			spw.println(",交易密码:" + userPwd2);
    			spw.println("已购编号:" + buyCode);
    			spw.println("成功时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    			// 关闭各种流
    			spw.flush();
    			sfw.flush();
    			spw.close();
    			sfw.close();
    		} else {
    			pw.println("#购买失败!");
    			pw.println("服务器返回信息:");
    			pw.println(str);
    		}
    		// 关闭各种流
    		pw.flush();
    		fw.flush();
    		pw.close();
    		fw.close();
    		//日志文件写入结束
    	}
    
    	public static void invokeGetVerifyCode() {
    		try {
    			while (true) {
    				//System.out.println("正在启动验证码识别线程……");
    				String verifyTag = "";
    				//long verifyLast = 0; //验证码获取时间
    				String tmpVerifyCode = "";
    				while (!verifyTag.equals("true")) {
    					//System.out.println("当前验证码:"+verifyCode);
    					// 验证码识别
    					//long verifyStartTime = System.currentTimeMillis();
    					tmpVerifyCode = getVerifyCode();
    					//verifyLast = System.currentTimeMillis() - verifyStartTime; //验证码获取时间
    					//System.out.println("已识别验证码:" + tmpVerifyCode);
    					//System.out.println("耗时 :" + verifyLast + " 豪秒 ");
    					String url = "https://www.agkmg.com/zh-tw/CommonDefault/CheckCode";
    					HashMap<String, Object> m = new HashMap<String, Object>();
    					m.put("Code", tmpVerifyCode);
    					verifyTag = HttpClientUtil.invokePost(url, 5000, m);
    					while (verifyTag == "")
    						verifyTag = HttpClientUtil.invokePost(url, 5000, m);
    					//System.out.println("识别结果:" + verifyTag);
    					// /////////
    				}
    				verifyCode = tmpVerifyCode;
    				System.out.println("验证码已更新:"+verifyCode);
    				// 获取验证码的线程休息120秒
    				try {
    					Thread.sleep(vCodeRefreshTime);
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    		} catch (Exception e) {
    			// TODO Auto-generated catch block
    			//e.printStackTrace();
    			//System.out.println("验证码识别线程出现异常,正在重新启动……");
    			invokeGetVerifyCode();
    		}
    	}
    	
    	public static String getVerifyCode() {
    
    		 String url = "https://www.agkmg.com/zh-tw/CommonDefault/GetValidateCode";
    		 String str = HttpClientUtil.invokeGetImg(url, null, "UTF-8", 5000, 0);
    		 while(str.equals("0"))
    			 str = HttpClientUtil.invokeGetImg(url, null, "UTF-8", 5000, 0);
    		 //System.out.println("验证码图片读取情况:"+str);
    		 String verifyCode;
    		try {
    			verifyCode = CrashCode.getVerifyCode("1.jpg");
    		} catch (Exception e) {
    			// TODO Auto-generated catch block
    			//e.printStackTrace();
    			return "0000";
    		}
    		 return verifyCode;
    
    	}
    	
    	public static void playMessageSound() {
    		try {  
                // 1.wav 文件放在java project 下面  
                FileInputStream fileau = new FileInputStream(  
                        System.getProperty("user.dir") + "/source/message.wav");  
                AudioStream as = new AudioStream(fileau);  
                AudioPlayer.player.start(as);  
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
    	}
    }


    RelynAGK MyEclipse工程:下载