`

自动更改IP地址反爬虫封锁,支持多线程

阅读更多

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为例子来说明。

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

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

 

 

 

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

 

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<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实现的英文单词分析软件,主要研究英语单词音近形似转化规律、前缀后缀规律、词之间的相似性规律等等。

 

代码链接:

1、https://github.com/ysc/superword/blob/master/src/main/java/org/apdplat/superword/tools/DynamicIp.java 

2、https://github.com/ysc/superword/blob/master/src/main/java/org/apdplat/superword/tools/WordClassifier.java

 

 

 

 

 

 

 

 

2
6
分享到:
评论
3 楼 lvzhirong1 2016-07-07  
杭州的大神啊,我在西湖区顶-一-下
2 楼 haifengwuch 2015-04-07  
牛人啊,强大。
1 楼 yq5858588 2015-03-26  
很牛逼的代码呀

相关推荐

    qt5多线程pingIP地址(线程池)

    在本项目中,“qt5多线程pingIP地址(线程池)”是一个利用Qt5框架和多线程技术来实现对多个IP地址进行并发ping操作的应用。这个应用可能会被网络管理员或者开发人员用来快速检测网络连通性,特别是在大规模网络环境...

    反爬虫策略反爬虫手段

    在互联网世界中,爬虫与反爬虫是一场持续的博弈。爬虫,作为一种自动抓取网页信息的程序,被广泛用于数据挖掘、市场分析、搜索引擎优化等领域。然而,随着爬虫技术的发展,网站所有者也开始采取各种反爬虫策略以保护...

    多线程换IP源码(调用鱼刺线程池)

    在IT行业中,多线程和IP切换是两个关键概念,特别是在网络爬虫、分布式系统以及自动化测试等领域。这里提到的“多线程换IP源码(调用鱼刺线程池)”是一个程序,它利用多线程技术来实现IP地址的快速切换,并且这个...

    c#多线程程序设计,IP地址dns域名解析

    本项目"c#多线程程序设计,IP地址dns域名解析"正是结合了这两个核心概念,通过创建Windows应用程序来实现高效地扫描网络中的计算机,并进行DNS域名解析。 首先,我们需要理解多线程的概念。在单线程环境中,程序...

    大数据时代的反爬虫技术_陈利婷

    3. IP反制技术:通过限制同一IP地址的访问频率或者在检测到爬虫行为后封禁IP地址,可以防止爬虫程序对网站的过度抓取。 4. 用户代理识别:网站通过检查HTTP请求中的User-Agent字段来识别是否是爬虫,由于爬虫和正常...

    Java多线程网络爬虫(时光网为例)源码

    - IP代理:为了避免被目标网站封IP,可以使用IP代理池,定期更换爬虫的IP地址。 - 防反爬策略:很多网站会有一些反爬机制,如验证码、User-Agent检测等,需要模拟浏览器行为,设置合适的User-Agent,甚至使用浏览器...

    局域网IP扫描器,支持多线程

    本文将深入探讨一种特别设计用于局域网的IP扫描工具,其特点在于支持多线程扫描,从而提高扫描效率。 首先,我们要理解什么是IP扫描。IP扫描,顾名思义,是通过网络技术获取网络内所有活动设备的IP地址的过程。这一...

    常见反爬虫策略

    网络爬虫作为一种自动化获取网页信息的工具,对网站的数据安全和服务器压力构成了潜在威胁,因此,网站开发者会采用各种反爬虫策略来保护自身资源。本文将深入探讨常见的前端和后端反爬虫手段。 首先,前端反爬虫...

    java多线程代理IP池

    多线程代理IP池,一直看到有关这方面的技术,最近实现了一个。简单的来说,启动后,会一直定时的获取代理ip,并自动检测代理ip的活跃度。运用多线程的技术,在极短的时间内获取大量的ip进行筛选。架构也比较清楚,...

    linux下c语言多线程网页爬虫源代码

    多线程技术允许程序同时执行多个任务,提高效率,而网页爬虫则用于自动化地从互联网上抓取信息。下面将详细讨论这些知识点: 1. **Linux编程环境**:Linux提供了丰富的命令行工具和开源库,如glibc,用于支持C语言...

    基于Python的网络爬虫与反爬虫技术研究.pdf

    它通过设置各种门槛,如需要模拟正常用户的行为进行登录、验证码识别、动态网页处理、IP封锁等方式,来阻止爬虫程序的抓取行为。随着技术的发展,反爬虫的技术也在不断更新,使得网络爬虫和反爬虫之间的对抗日益激烈...

    IP助手自动换IP地址

    "IP助手自动换IP地址"是一款针对ADSL用户设计的小型软件,它的主要功能是自动化地更改用户的IP地址。这在某些特定情况下非常有用,比如网络投票。在投票活动中,为了增加某个选项的票数,可能需要从不同的IP地址进行...

    qt5多线程pingIP地址(纯线程)

    标题中的“qt5多线程pingIP地址(纯线程)”指的是使用Qt5库的多线程功能来并发地ping多个IP地址。Qt是一个跨平台的C++应用程序开发框架,它提供了丰富的API来处理图形用户界面、网络通信以及多线程编程。在这个场景...

    SinaSpider, 动态IP解决新浪的反爬虫机制,快速抓取内容。.zip

    2. **多线程与异步IO**:SinaSpider采用多线程或多进程并行处理,同时配合异步IO技术(如Python的asyncio库),提高爬取效率,减少单个请求的响应时间,降低被检测为爬虫的可能性。 3. **模拟浏览器行为**:Sina...

    python爬虫-python多线程爬虫爬取电影天堂资源.zip

    Python爬虫技术是数据获取的重要工具,特别是在网络信息丰富的今天,爬虫可以帮助我们高效地抓取和处理大量的网页数据。...通过这些步骤,我们可以构建出一个高效且稳定的多线程爬虫,实现电影资源的自动化抓取。

    【python爬虫】python多线程爬虫爬取电影天堂资源【源码+lw+部署文档】

    **内容概要**:本资源包提供了一个使用Python语言实现的多线程爬虫项目,用于爬取电影天堂网站上的电影资源。项目包含完整的源码、论文(lw)、部署文档以及详细讲解,帮助用户理解和掌握Python多线程爬虫的开发技巧...

    多线程爬取1000个网页_python爬虫_thread_

    8. **反爬虫策略**:了解和应对常见的反爬虫技术,如验证码、IP封锁、动态加载等。 综上所述,多线程和多进程都是Python中实现爬虫并行化的方法,它们各有优缺点,适用于不同的场景。在处理大量网页爬取任务时,...

    基于Python的网络爬虫与反爬虫技术的研究.pdf

    常见的反爬虫技术包括但不限于:IP地址封锁、验证码验证、session访问限制、蜘蛛陷阱和数据加密等。应对这些反爬虫措施,爬虫程序开发者需要采取相应的策略,比如使用代理IP池、设置合理的请求间隔、设置用户代理...

    基于Python的反反爬虫技术分析与应用.pdf

    反爬虫机制主要包括伪装用户代理、设置IP地址代理、使用自动化测试工具调用浏览器等策略。伪装用户代理是指改变爬虫程序的User-Agent,使它看起来像是正常的浏览器请求,以避免被网站的User-Agent过滤规则所屏蔽。...

Global site tag (gtag.js) - Google Analytics