前段时间弄爬虫的时候,在爬iteye的时候碰到过一个场景,Spider跑几次或者抓取的时间间隔小一点之后就会出现401错误
16-08-16 15:05:49,687 INFO us.codecraft.webmagic.Spider(Spider.java:307) ## Spider 843977358.iteye.com started! 16-08-16 15:05:49,696 INFO us.codecraft.webmagic.downloader.HttpClientDownloader(HttpClientDownloader.java:87) ## downloading page http://843977358.iteye.com/ 16-08-16 15:05:50,056 WARN us.codecraft.webmagic.downloader.HttpClientDownloader(HttpClientDownloader.java:100) ## code error 401 http://843977358.iteye.com/
然后,此时在去访问iteye就会提示你“您所在的IP地址对ITeye网站访问过于频繁,为了判断您的访问是真实用户,请您填写验证码,谢谢!”
不用想,一定是人家后台给限制住了。
因此,自己就瞎鼓捣搞了一个简单点的,仅仅可以实现对用户IP次数的检查和对违规用户的封禁。
用到的技术:
过滤器(Filter):统计用户访问次数,记录访问时间、封禁时间
监听器(Listener):工程运行时初始化IP存储器(此处用的Map)
我的思路:
工程启动时,创建两个Map,一个(ipMap)用来存放用户Ip和访问时间等主要信息,另一个(limitedIpMap)用来存放被限制的用户IP。Map的key为用户的IP,value为具体内容。
当用户访问系统时,通过IPFilter检查limitedIpMap中是否存在当前IP,如果存在说明该IP之前存在过恶意刷新访问,已经被限制,跳转到异常提示页面;如果limitedIpMap
中不存在则检查ipMap中是否存在当前IP,如果ipMap中不存在则说明用户初次访问,用户访问次数+1,初始访问时间为当前时间;如果存在则检查用户访问次数是否在规定的短时间内进行了大量的访问操作;如果是,则将当前IP添加到limitedIpMap中,并跳转到异常提示页面,否则不进行操作,直接放行本次请求。
(简单画了下流程图,看不懂的留言问我话)
配置文件:
<!-- 配置过滤器 start --> <filter> <filter-name>IPFilter</filter-name> <filter-class>com.test.interceptor.IPFilter</filter-class> </filter> <filter-mapping> <filter-name>IPFilter</filter-name> <url-pattern>/render/*</url-pattern> </filter-mapping> <!-- 配置过滤器 end --> <!-- 配置监听器 start --> <listener> <listener-class>com.test.listener.MyListener</listener-class> </listener> <!-- 配置监听器 start -->
监听器MyListener:
import java.util.HashMap; import java.util.Map; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; /** * @Description 自定义监听器,项目启动时初始化两个全局的map, * ipMap(ip存储器,记录IP的访问次数、访问时间) * limitedIpMap(限制IP存储器)用来存储每个访问用户的IP以及访问的次数 * @author zhangyd * @date 2016年7月28日 下午5:47:23 * @since JDK : 1.7 */ public class MyListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { ServletContext context = sce.getServletContext(); // IP存储器 Map<String, Long[]> ipMap = new HashMap<String, Long[]>(); context.setAttribute("ipMap", ipMap); // 限制IP存储器:存储被限制的IP信息 Map<String, Long> limitedIpMap = new HashMap<String, Long>(); context.setAttribute("limitedIpMap", limitedIpMap); } @Override public void contextDestroyed(ServletContextEvent sce) { } }
过滤器IPFilter:
import java.io.IOException; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.test.util.IPUtil; /** * * @Description 自定义过滤器,用来判断IP访问次数是否超限。<br> * 如果前台用户访问网站的频率过快(比如:达到或超过50次/s),则判定该IP为恶意刷新操作,限制该ip访问<br> * 默认限制访问时间为1小时,一小时后自定解除限制 * * @author zhangyd * @date 2016年7月28日 下午5:54:51 * @since JDK : 1.7 */ public class IPFilter implements Filter { /** * 默认限制时间(单位:ms) */ private static final long LIMITED_TIME_MILLIS = 60 * 60 * 1000; /** * 用户连续访问最高阀值,超过该值则认定为恶意操作的IP,进行限制 */ private static final int LIMIT_NUMBER = 20; /** * 用户访问最小安全时间,在该时间内如果访问次数大于阀值,则记录为恶意IP,否则视为正常访问 */ private static final int MIN_SAFE_TIME = 5000; private FilterConfig config; @Override public void init(FilterConfig filterConfig) throws ServletException { this.config = filterConfig; } /** * @Description 核心处理代码 * @param servletRequest * @param servletResponse * @param chain * @throws IOException * @throws ServletException * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, * javax.servlet.ServletResponse, javax.servlet.FilterChain) */ @SuppressWarnings("unchecked") @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; ServletContext context = config.getServletContext(); // 获取限制IP存储器:存储被限制的IP信息 Map<String, Long> limitedIpMap = (Map<String, Long>) context.getAttribute("limitedIpMap"); // 过滤受限的IP filterLimitedIpMap(limitedIpMap); // 获取用户IP String ip = IPUtil.getIp(request); // 判断是否是被限制的IP,如果是则跳到异常页面 if (isLimitedIP(limitedIpMap, ip)) { long limitedTime = limitedIpMap.get(ip) - System.currentTimeMillis(); // 剩余限制时间(用为从毫秒到秒转化的一定会存在些许误差,但基本可以忽略不计) request.setAttribute("remainingTime", ((limitedTime / 1000) + (limitedTime % 1000 > 0 ? 1 : 0))); request.getRequestDispatcher("/error/overLimitIP").forward(request, response); return; } // 获取IP存储器 Map<String, Long[]> ipMap = (Map<String, Long[]>) context.getAttribute("ipMap"); // 判断存储器中是否存在当前IP,如果没有则为初次访问,初始化该ip // 如果存在当前ip,则验证当前ip的访问次数 // 如果大于限制阀值,判断达到阀值的时间,如果不大于[用户访问最小安全时间]则视为恶意访问,跳转到异常页面 if (ipMap.containsKey(ip)) { Long[] ipInfo = ipMap.get(ip); ipInfo[0] = ipInfo[0] + 1; System.out.println("当前第[" + (ipInfo[0]) + "]次访问"); if (ipInfo[0] > LIMIT_NUMBER) { Long ipAccessTime = ipInfo[1]; Long currentTimeMillis = System.currentTimeMillis(); if (currentTimeMillis - ipAccessTime <= MIN_SAFE_TIME) { limitedIpMap.put(ip, currentTimeMillis + LIMITED_TIME_MILLIS); request.setAttribute("remainingTime", LIMITED_TIME_MILLIS); request.getRequestDispatcher("/error/overLimitIP").forward(request, response); return; } else { initIpVisitsNumber(ipMap, ip); } } } else { initIpVisitsNumber(ipMap, ip); System.out.println("您首次访问该网站"); } context.setAttribute("ipMap", ipMap); chain.doFilter(request, response); } @Override public void destroy() { } /** * @Description 是否是被限制的IP * @author zhangyd * @date 2016年8月8日 下午5:39:17 * @param limitedIpMap * @param ip * @return true : 被限制 | false : 正常 */ private boolean isLimitedIP(Map<String, Long> limitedIpMap, String ip) { if (limitedIpMap == null || ip == null) { // 没有被限制 return false; } Set<String> keys = limitedIpMap.keySet(); Iterator<String> keyIt = keys.iterator(); while (keyIt.hasNext()) { String key = keyIt.next(); if (key.equals(ip)) { // 被限制的IP return true; } } return false; } /** * @Description 过滤受限的IP,剔除已经到期的限制IP * @author zhangyd * @date 2016年8月8日 下午5:34:33 * @param limitedIpMap */ private void filterLimitedIpMap(Map<String, Long> limitedIpMap) { if (limitedIpMap == null) { return; } Set<String> keys = limitedIpMap.keySet(); Iterator<String> keyIt = keys.iterator(); long currentTimeMillis = System.currentTimeMillis(); while (keyIt.hasNext()) { long expireTimeMillis = limitedIpMap.get(keyIt.next()); if (expireTimeMillis <= currentTimeMillis) { keyIt.remove(); } } } /** * 初始化用户访问次数和访问时间 * * @author zhangyd * @date 2016年7月29日 上午10:01:39 * @param ipMap * @param ip */ private void initIpVisitsNumber(Map<String, Long[]> ipMap, String ip) { Long[] ipInfo = new Long[2]; ipInfo[0] = 0L;// 访问次数 ipInfo[1] = System.currentTimeMillis();// 初次访问时间 ipMap.put(ip, ipInfo); } }
为了方便测试,我把封禁时间调到1分钟
/** * 默认限制时间(单位:ms) */ private static final long LIMITED_TIME_MILLIS = 60 * 1000; /** * 用户连续访问最高阀值,超过该值则认定为恶意操作的IP,进行限制 */ private static final int LIMIT_NUMBER = 20; /** * 用户访问最小安全时间,在该时间内如果访问次数大于阀值,则记录为恶意IP,否则视为正常访问 */ private static final int MIN_SAFE_TIME = 5000;
上面这三项是自定义的,根据自己情况来。
测试:
演示统共分三步:
第一步:正常访问并且间隔时间略长,访问20次为第一步
第二步:按住F5狂刷,一直到跳转到限制页面为第二步
第三步:等待1min,限制时间过后,重新刷新页面
(此处没有大象......)
我有罪,我不该把时间调成1min的, 应该20秒。。。好吧。
大功告成!!!
相关推荐
手把手教你学DSP:基于TMS320F28335 手把手教你学DSP:基于TMS320F28335 手把手教你学DSP:基于TMS320F28335 手把手教你学DSP:基于TMS320F28335 手把手教你学DSP:基于TMS320F28335 手把手教你学DSP:基于TMS320F...
手把手教你学2812,很全面的一本书。电子版,适合初学者学习,
手把手教你配路由器手把手教你配路由器手把手教你配路由器手把手教你配路由器手把手教你配路由器
手把手教你学28335PDF文档,看了这个确实和2812有了对比
手把手教你如何从一无所有到财务自由.pdf
手把手教你DSP配套资料 很有用的资料,用钱买来的资料
手把手教你学DSP28335高清pdf文件,北京航空航天大学出版社
高清珍藏学习嵌入式开发入门最好资料《手把手教你学51单片机》教材pdf
手把手教你学dsp电子版,文档有标签,一本很好的dsp入门书,希望大家一起学习!
手把手教你学dsp2812,这本书是顾卫刚的,他的讲解比较详细
手把手教你单片机程序框架---吴坚鸿doc,手把手教你单片机程序框架---吴坚鸿
代码为博客实例代码:http://blog.csdn.net/lmj623565791/article/details/38339817, 有问题请博客留言
手把手教你学DSP配套资源,里面包含例程和头文件以及配套调试助手
003《老HRD手把手教你做绩效考核》.pdf
这是《手把手教你学51单片机C语言版》的官方电子版,出自www.kingst.org
手把手教你学DSP 顾卫刚.以TMS320X281X, DSP的开发为主线,采用生动的语言深入浅出的介绍与DSP开发的方方面面,适合初学者。
手把手教你实现如何自定义一个ExpandableListView,代码中含有大量注释,一看就懂,可以当做模板来用
宋雪松先生 《手把手教你学51单片机-C语言版 》 非常贴合实际的单片机教程,感谢宋雪松先生给大家提供优秀的教程
手把手带你自定义 Gradle 插件 —— Gradle 系列(2).doc