投票服务器一般的验证条件:
- IP地址不能重复
- Cookie验证
- 验证码验证
一个自动投票工具可能涉及的问题:
- 自动变换IP地址
- 识别验证码
- 伪装成浏览器提交
自动变换IP地址
一种直接的办法就是控制ADSL的链接状态来实现IP变换,具体可以参考 这里。但如果你使用的不是ADSL,那么更直接的办法就是使用代理了。
首先你需要一个有效代理列表,可以从网上专门提供代理网站收集,也可以有专门的工具,比如商易代理IP获取器。如果是网站获取,你可以写个页面解析程序,分页获取;如果是软件,在获取可用代理后,一般可以直接导成txt文本。
接下来就是要测试代理的可用性,一般可用代理不到采集下来的1/10。
测试代理的有效性:
static String myIP = "X.X.X.X";//自己原来的对外IP private static boolean testProxy(String ip, String port) { System.getProperties().setProperty("http.proxyHost", ip); System.getProperties().setProperty("http.proxyPort", port); InputStream in = null; try { in = new URL("http://iframe.ip138.com/ic.asp").openStream(); String string = IOUtils.toString(in, "GBK"); System.out.println(string); return string.indexOf(myIP) < 0; } catch (Exception e) { return false; } finally { if (in != null) IOUtils.closeQuietly(in); } }
伪装成浏览器提交
这里是斗智斗勇的时候,有时候投票服务器会使用一些小手段来阻碍模拟者,你需要伪装成一个普通浏览器,并具有和用户操作一样的行为。
你可以使用firefox(firebug)或是chrome的调试器查看正常投票的网络信息,从而进行模拟。
一般的伪装:
DefaultHttpClient httpclient = new DefaultHttpClient(); // 代理的设置 HttpHost proxy = new HttpHost(ip, Integer.parseInt(port)); httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); httpclient.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY); httpclient.getParams().setParameter(CoreProtocolPNames.USER_AGENT, "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:18.0) Gecko/20100101 Firefox/18.0"); httpclient.getParams().setParameter( CoreProtocolPNames.HTTP_CONTENT_CHARSET, "GBK"); HttpPost httppost = new HttpPost("http://XXX"); httppost.addHeader("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64; rv:18.0) Gecko/20100101 Firefox/18.0"); httppost.addHeader("Host", "XXX); httppost.addHeader("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); httppost.addHeader("Accept-Encoding", "gzip, deflate"); httppost.addHeader("Accept-Language","zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3"); httppost.addHeader("Cache-Control", "max-age=0"); httppost.addHeader("Connection", "keep-alive"); httppost.addHeader("Referer","http://XXX");
识别验证码
这个是个大话题,识别验证码方法很多,比较有效的方法是采用 tesseract-ocr。一般的验证码都不在话下,个别的需要自己训练一下,可以参考 这里。
一般的用法:
tesseract test.jpg out -l eng digits
test.jpg为验证码图片,out为识别的验证码存放的txt文件名,-l eng为指定语言,也可以下载支持中文的包识别中文,digits为指定只识别数字,这样对于只是数字的验证码可以提高识别率。
如果你发现tesseract提示empty page!!,则需要修改一下参数:
Here's a summary of compression support and limitations: - All formats except JPEG support 1 bpp binary. - All formats support 8 bpp grayscale (GIF must have a colormap). - All formats except GIF support 24 bpp rgb color. - All formats except PNM support 8 bpp colormap. - PNG and PNM support 2 and 4 bpp images. - PNG supports 2 and 4 bpp colormap, and 16 bpp without colormap. - PNG, JPEG, TIFF and GIF support image compression; PNM and BMP do not. - WEBP supports 24 bpp rgb color.
比如最后的指令可能为:
tesseract test.jpg out -l eng -psm 7 digits。
tesseract识别格式有限,有的网站的验证码格式是bmp或其他,这个时候需要先获取下来byte[],保存成原有格式,然后再转化成支持的格式,比如jpg,tif等。转化工作可以使用 JAI(Java Advanced Imaging API)。
对于有的验证码,还需要做一些前期处理,比如去掉背景噪点,灰度化,增大对比度,去掉干扰线条等。
用java调用是必须的:
public static String doOCR(File imageFile) throws Exception { String result = ""; File outputFile = new File(imageFile.getParentFile(), "output"); StringBuffer strB = new StringBuffer(); List<String> cmd = new ArrayList<String>(); cmd.add(tessPath + "\\tesseract"); cmd.add(""); cmd.add(outputFile.getName()); cmd.add(LANG_OPTION); cmd.add("eng"); cmd.add("-psm"); cmd.add("7"); // cmd.add("nobatch"); cmd.add("digits"); ProcessBuilder pb = new ProcessBuilder(); pb.directory(imageFile.getParentFile()); cmd.set(1, imageFile.getName()); System.out.println(cmd.toString()); pb.command(cmd); pb.redirectErrorStream(true); Process process = pb.start(); int w = process.waitFor(); logger.debug("Exit value = {}", w); if (w == 0) { BufferedReader in = new BufferedReader(new InputStreamReader( new FileInputStream(outputFile.getAbsolutePath() + ".txt"), "UTF-8")); String str; while ((str = in.readLine()) != null) { strB.append(str).append(EOL); } in.close(); } else { String msg; switch (w) { case 1: msg = "Errors accessing files. There may be spaces in your image's filename."; break; case 29: msg = "Cannot recognize the image or its selected region."; break; case 31: msg = "Unsupported image format."; break; default: msg = "Errors occurred."; } System.err.println("验证码获取失败:" + msg); } new File(outputFile.getAbsolutePath() + ".txt").delete(); result = strB.toString().trim(); return result; }