博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
自动更改IP地址反爬虫封锁,支持多线程
阅读量:6581 次
发布时间:2019-06-24

本文共 9730 字,大约阅读时间需要 32 分钟。

hot3.png

8年多爬虫经验的人告诉你,国内ADSL是王道,多申请些线路,分布在多个不同的电信机房,能跨省跨市更好,我这里写好的断线重拨组件,你可以直接使用。

ADSL拨号上网使用动态IP地址,每一次拨号得到的IP都不一样,所以我们可以通过程序来自动进行重新拨号以获得新的IP地址,以达到突破反爬虫封锁的目的。

那么我们如何进行自动重新拨号呢?

假设有10个线程在跑,大家都正常的跑,跑着跑着达到限制了,WEB服务器提示你“非常抱歉,来自您ip的请求异常频繁”,于是大家争先恐后(几乎是同时)请求拨号,这个时候同步的作用就显示出来了,只会有一个线程能拨号,在他结束之前其他线程都在等,等他拨号成功之后,其他线程会被唤醒并返回

算法描述:

1、假设总共有N个线程抓取网页,发现被封锁之后依次排队请求锁,注意:可以想象成是同时请求。
2、线程1抢先获得锁,并且设置isDialing = true后开始拨号,注意:线程1设置isDialing = true后其他线程才可能获得锁。
3、其他线程(2-N)依次获得锁,发现isDialing = true,于是wait。注意:获得锁并判断一个布尔值,跟后面的拨号操作比起来,时间可以忽略。
4、线程1拨号完毕isDialing = false。注意:这个时候可以断定,其他所有线程必定是处于wait状态等待唤醒。
5、线程1唤醒其他线程,其他线程和线程1返回开始抓取网页。
6、抓了一会儿之后,又会被封锁,于是回到步骤1。

在本场景中,3和4的断定是没问题的,就算是出现“不可能”的情况,即线程1已经拨号完成了,可2-N还没获得锁(汗),也不会重复拨号的情况,因为算法考虑了请求拨号时间和上一次成功拨号时间。

下面以腾达300M无线路由器,型号:N302 v2为例子来说明。

首先,设置路由器:上网设置 -》请根据需要选择连接模式 -》手动连接,由用户手动进行连接,如下图所示。其他的路由器使用方法类似,参照本方法替换相应的登录地址、断开连接及建立连接地址即可。

025347_9qcG_121944.png

其次,利用Firefox的Firebug功能找到路由器的登录路径及参数、断开连接路径及参数、建立连接路径及参数,如下图所示。

030519_ufG9_121944.png

030518_q3zl_121944.png

030518_shEX_121944.png

接着,参考如下代码,替换自己相关的路径和参数:

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个线程在跑,大家都正常的跑,跑着跑着达到限制了,     * 于是大家争先恐后(几乎是同时)请求拨号,     * 这个时候同步的作用就显示出来了,只会有一个线程能拨号,     * 在他结束之前其他线程都在等,等他拨号成功之后,     * 其他线程会被唤醒并返回     *     * 算法描述:     * 1、假设总共有N个线程抓取网页,发现被封锁之后依次排队请求锁,注意:可以想象成是同时请求。     * 2、线程1抢先获得锁,并且设置isDialing = true后开始拨号,注意:线程1设置isDialing = true后其他线程才可能获得锁。     * 3、其他线程(2-N)依次获得锁,发现isDialing = true,于是wait。注意:获得锁并判断一个布尔值,跟后面的拨号操作比起来,时间可以忽略。     * 4、线程1拨号完毕isDialing = false。注意:这个时候可以断定,其他所有线程必定是处于wait状态等待唤醒。     * 5、线程1唤醒其他线程,其他线程和线程1返回开始抓取网页。     * 6、抓了一会儿之后,又会被封锁,于是回到步骤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
 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
 cookies){        return execute(cookies, "3");    }    public static boolean disConnect(Map
 cookies){        return execute(cookies, "4");    }    public static boolean execute(Map
 cookies, String action){        String url = "http://192.168.0.1/goform/SysStatusHandle";        Map
 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
 login(String userName, String password, String verify){        try {            Map
 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
 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
 words){    LOGGER.debug("待处理词数目:"+words.size());    AtomicInteger i = new AtomicInteger();    Map
> 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,

代码链接:

1、 

2、

转载于:https://my.oschina.net/apdplat/blog/391088

你可能感兴趣的文章
在MySQL数据库建立多对多的数据表关系
查看>>
突然停电或死机导致没保存的文件怎么找回
查看>>
kudu
查看>>
jquery.validate.min.js表单验证使用
查看>>
在JS中捕获console.log的输出
查看>>
Python扫描IP段指定端口是否开放(一次扫描20个B网段没问题)
查看>>
一些常用的WebServices
查看>>
CentOS7使用firewalld打开关闭防火墙与端口
查看>>
maven 添加阿里云maven镜像
查看>>
mac上安装consolas字体
查看>>
对向量、矩阵求导
查看>>
各版本linux下载地址大全
查看>>
CentOS 6.X 关闭不需要的 TTY 方法
查看>>
我的友情链接
查看>>
分区技术学习一
查看>>
Juniper 高级选项
查看>>
编程能力的四种境界
查看>>
编译安装mysql
查看>>
在windows上秒开应用程序
查看>>
【20180611】MySQL OOM
查看>>