8年多爬虫经验的人告诉你,国内ADSL是王道,多申请些线路,分布在多个不一样的电信机房,能跨省跨市更好,我这里写好的断线重拨组件,你能够直接使用。html
ADSL拨号上网使用动态IP地址,每一次拨号获得的IP都不同,因此咱们能够经过程序来自动进行从新拨号以得到新的IP地址,以达到突破反爬虫封锁的目的。java
那么咱们如何进行自动从新拨号呢?node
假设有10个线程在跑,你们都正常的跑,跑着跑着达到限制了,WEB服务器提示你“很是抱歉,来自您ip的请求异常频繁”,因而你们争先恐后(几乎是同时)请求拨号,这个时候同步的做用就显示出来了,只会有一个线程能拨号,在他结束以前其余线程都在等,等他拨号成功以后,其余线程会被唤醒并返回git
算法描述:
一、假设总共有N个线程抓取网页,发现被封锁以后依次排队请求锁,注意:能够想象成是同时请求。
二、线程1抢先得到锁,而且设置isDialing = true后开始拨号,注意:线程1设置isDialing = true后其余线程才可能得到锁。
三、其余线程(2-N)依次得到锁,发现isDialing = true,因而wait。注意:得到锁并判断一个布尔值,跟后面的拨号操做比起来,时间能够忽略。
四、线程1拨号完毕isDialing = false。注意:这个时候能够判定,其余全部线程一定是处于wait状态等待唤醒。
五、线程1唤醒其余线程,其余线程和线程1返回开始抓取网页。
六、抓了一下子以后,又会被封锁,因而回到步骤1。github
在本场景中,3和4的判定是没问题的,就算是出现“不可能”的状况,即线程1已经拨号完成了,可2-N还没得到锁(汗),也不会重复拨号的状况,由于算法考虑了请求拨号时间和上一次成功拨号时间。算法
下面以腾达300M无线路由器,型号:N302 v2为例子来讲明。服务器
首先,设置路由器:上网设置 -》请根据须要选择链接模式 -》手动链接,由用户手动进行链接,以下图所示。其余的路由器使用方法相似,参照本方法替换相应的登陆地址、断开链接及创建链接地址便可。
cookie
其次,利用Firefox的Firebug功能找到路由器的登陆路径及参数、断开链接路径及参数、创建链接路径及参数,以下图所示。多线程
接着,参考以下代码,替换本身相关的路径和参数:app
import org.jsoup.Connection; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; /** * * 自动更改IP地址反爬虫封锁,支持多线程 * * ADSL拨号上网使用动态IP地址,每一次拨号获得的IP都不同 * * 使用腾达300M无线路由器,型号:N302 v2 * 路由器设置中最好设置一下:上网设置 -》请根据须要选择链接模式 -》手动链接,由用户手动进行链接。 * 其余的路由器使用方法相似,参照本类替换相应的登陆地址、断开链接及创建链接地址便可 * * @author 杨尚川 */ public class DynamicIp { private DynamicIp(){} private static final Logger LOGGER = LoggerFactory.getLogger(DynamicIp.class); private static final String ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"; private static final String ENCODING = "gzip, deflate"; private static final String LANGUAGE = "zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3"; private static final String CONNECTION = "keep-alive"; private static final String HOST = "192.168.0.1"; private static final String REFERER = "http://192.168.0.1/login.asp"; private static final String USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:36.0) Gecko/20100101 Firefox/36.0"; private static volatile boolean isDialing = false; private static volatile long lastDialTime = 0l; public static void main(String[] args) { toNewIp(); } /** * 假设有10个线程在跑,你们都正常的跑,跑着跑着达到限制了, * 因而你们争先恐后(几乎是同时)请求拨号, * 这个时候同步的做用就显示出来了,只会有一个线程能拨号, * 在他结束以前其余线程都在等,等他拨号成功以后, * 其余线程会被唤醒并返回 * * 算法描述: * 一、假设总共有N个线程抓取网页,发现被封锁以后依次排队请求锁,注意:能够想象成是同时请求。 * 二、线程1抢先得到锁,而且设置isDialing = true后开始拨号,注意:线程1设置isDialing = true后其余线程才可能得到锁。 * 三、其余线程(2-N)依次得到锁,发现isDialing = true,因而wait。注意:得到锁并判断一个布尔值,跟后面的拨号操做比起来,时间能够忽略。 * 四、线程1拨号完毕isDialing = false。注意:这个时候能够判定,其余全部线程一定是处于wait状态等待唤醒。 * 五、线程1唤醒其余线程,其余线程和线程1返回开始抓取网页。 * 六、抓了一下子以后,又会被封锁,因而回到步骤1。 * 注意:在本场景中,3和4的判定是没问题的,就算是出现“不可能”的状况, * 即线程1已经拨号完成了,可2-N还没得到锁(汗),也不会重复拨号的状况, * 由于算法考虑了请求拨号时间和上一次成功拨号时间。 * @return 更改IP是否成功 */ public static boolean toNewIp() { long requestDialTime = System.currentTimeMillis(); LOGGER.info(Thread.currentThread()+"请求从新拨号"); synchronized (DynamicIp.class) { if (isDialing) { LOGGER.info(Thread.currentThread()+"已经有其余线程在进行拨号了,我睡觉等待吧,其余线程拨号完毕会叫醒个人"); try { DynamicIp.class.wait(); } catch (InterruptedException e) { LOGGER.error(e.getMessage(), e); } LOGGER.info(Thread.currentThread()+"其余线程已经拨完号了,我能够返回了"); return true; } isDialing = true; } //保险起见,这里再判断一下 //若是请求拨号的时间小于上次成功拨号的时间,则说明这个请求来的【太迟了】,则返回。 if(requestDialTime <= lastDialTime){ LOGGER.info("请求来的太迟了"); isDialing = true; return true; } LOGGER.info(Thread.currentThread()+"开始从新拨号"); long start = System.currentTimeMillis(); Map<String, String> cookies = login("username***", "password***", "phonenumber***"); if("true".equals(cookies.get("success"))) { LOGGER.info(Thread.currentThread()+"登录成功"); cookies.remove("success"); while (!disConnect(cookies)) { LOGGER.info(Thread.currentThread()+"断开链接失败,重试!"); } LOGGER.info(Thread.currentThread()+"断开链接成功"); while (!connect(cookies)) { LOGGER.info(Thread.currentThread()+"创建链接失败,重试!"); } LOGGER.info(Thread.currentThread()+"创建链接成功"); LOGGER.info(Thread.currentThread()+"自动更改IP地址成功!"); LOGGER.info(Thread.currentThread()+"拨号耗时:"+(System.currentTimeMillis()-start)+"毫秒"); //通知其余线程拨号成功 synchronized (DynamicIp.class) { DynamicIp.class.notifyAll(); } isDialing = false; lastDialTime = System.currentTimeMillis(); return true; } isDialing = false; return false; } public static boolean connect(Map<String, String> cookies){ return execute(cookies, "3"); } public static boolean disConnect(Map<String, String> cookies){ return execute(cookies, "4"); } public static boolean execute(Map<String, String> cookies, String action){ String url = "http://192.168.0.1/goform/SysStatusHandle"; Map<String, String> map = new HashMap<>(); map.put("action", action); map.put("CMD", "WAN_CON"); map.put("GO", "system_status.asp"); Connection conn = Jsoup.connect(url) .header("Accept", ACCEPT) .header("Accept-Encoding", ENCODING) .header("Accept-Language", LANGUAGE) .header("Connection", CONNECTION) .header("Host", HOST) .header("Referer", REFERER) .header("User-Agent", USER_AGENT) .ignoreContentType(true) .timeout(30000); for(String cookie : cookies.keySet()){ conn.cookie(cookie, cookies.get(cookie)); } String title = null; try { Connection.Response response = conn.method(Connection.Method.POST).data(map).execute(); String html = response.body(); Document doc = Jsoup.parse(html); title = doc.title(); LOGGER.info("操做链接页面标题:"+title); }catch (Exception e){ LOGGER.error(e.getMessage()); } if("LAN | LAN Settings".equals(title)){ if(("3".equals(action) && isConnected()) || ("4".equals(action) && !isConnected())){ return true; } } return false; } public static boolean isConnected(){ try { Document doc = Jsoup.connect("http://www.baidu.com/s?wd=杨尚川&t=" + System.currentTimeMillis()) .header("Accept", ACCEPT) .header("Accept-Encoding", ENCODING) .header("Accept-Language", LANGUAGE) .header("Connection", CONNECTION) .header("Referer", "https://www.baidu.com") .header("Host", "www.baidu.com") .header("User-Agent", USER_AGENT) .ignoreContentType(true) .timeout(30000) .get(); LOGGER.info("搜索结果页面标题:"+doc.title()); if(doc.title() != null && doc.title().contains("杨尚川")){ return true; } }catch (Exception e){ if("Network is unreachable".equals(e.getMessage())){ return false; }else{ LOGGER.error("状态检查失败:"+e.getMessage()); } } return false; } public static Map<String, String> login(String userName, String password, String verify){ try { Map<String, String> map = new HashMap<>(); map.put("Username", userName); map.put("Password", password); map.put("checkEn", "0"); Connection conn = Jsoup.connect("http://192.168.0.1/LoginCheck") .header("Accept", ACCEPT) .header("Accept-Encoding", ENCODING) .header("Accept-Language", LANGUAGE) .header("Connection", CONNECTION) .header("Referer", REFERER) .header("Host", HOST) .header("User-Agent", USER_AGENT) .ignoreContentType(true) .timeout(30000); Connection.Response response = conn.method(Connection.Method.POST).data(map).execute(); String html = response.body(); Document doc = Jsoup.parse(html); LOGGER.info("登录页面标题:"+doc.title()); Map<String, String> cookies = response.cookies(); if(html.contains(verify)){ cookies.put("success", Boolean.TRUE.toString()); } LOGGER.info("*******************************************************cookies start:"); cookies.keySet().stream().forEach((cookie) -> { LOGGER.info(cookie + ":" + cookies.get(cookie)); }); LOGGER.info("*******************************************************cookies end:"); return cookies; }catch (Exception e){ LOGGER.error(e.getMessage(), e); } return Collections.emptyMap(); } }
最后,就可使用了,例子以下:
public static void classify(Set<Word> words){ LOGGER.debug("待处理词数目:"+words.size()); AtomicInteger i = new AtomicInteger(); Map<String, List<String>> data = new HashMap<>(); words.forEach(word -> { if(i.get()%1000 == 999){ save(data); } showStatus(data, i.incrementAndGet(), words.size(), word.getWord()); String html = getContent(word.getWord()); LOGGER.debug("获取到的HTML:" +html); while(html.contains("很是抱歉,来自您ip的请求异常频繁")){ //使用新的IP地址 DynamicIp.toNewIp(); html = getContent(word.getWord()); } if(StringUtils.isNotBlank(html)) { parse(word.getWord(), html, data); }else{ NOT_FOUND_WORDS.add(word.getWord()); } }); //写入磁盘 save(data); LOGGER.debug("处理完毕,总词数目:"+words.size()); }
本文讲述的方法和代码来源于本人的开源目superword,superword是一个Java实现的英文单词分析软件,主要研究英语单词音近形似转化规律、前缀后缀规律、词之间的类似性规律等等。
代码连接: