自动更改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实现的英文单词分析软件,主要研究英语单词音近形似转化规律、前缀后缀规律、词之间的相似性规律等等。
代码链接:
http://yangshangchuan.iteye.com/blog/2195287
自动更改IP地址反爬虫封锁,支持多线程(转)的更多相关文章
- 爬虫实现:根据IP地址反查域名
域名解析与IP地址 域名解析是把域名指向网站空间IP,让人们通过注册的域名可以方便地访问到网站的一种服务:IP地址是网络上标识站点的数字地址,为了方便记忆,采用域名来代替IP地址标识站点地址.域名解析 ...
- 批处理快速更改ip地址
在各种网络中切换,windows更换ip地址步骤: 进入控制面板--网络和internet--网络和共享中心--理性适配器设置--然后找到网卡--进入属性--然后internet 协议--更改ip信 ...
- 详解如何设置CentOS 7开机自动获取IP地址
本例中以CentOS 7举例说明如何设置Linux开机自动获取IP地址和设置固定IP地址. 自动获取动态IP地址 1.输入“ip addr”并按回车键确定,发现无法获取IP(CentOS 7默认没有i ...
- 使用DOS批处理更改IP地址
有时候电脑在家需要自动获取IP,在公司需要手动填写IP,改来改去很麻烦,于是做一个批处理一键修改很方便: @echo off cls color 0A @echo off echo. echo === ...
- 自动配置IP地址.bat
※※※※※※※※※※※※※※※※※※※※※※※※※※※※ @echo ※ ※ @echo ...
- ARM-Linux配置DHCP自动获取IP地址
备注:内核版本:2.6.30.9busybox版本:1.15.2 PC Linux和开发板Linux的工作用户:root 1. 配置内核:[*] Networking support --->N ...
- Oracle VM Virtual 下CentOS不能自动获取IP地址
在CentOS配置网卡开机自动获取IP地址: vi /etc/sysconfig/network-scripts/ifcfg-eth0 将 ONBOOT="no" 改为 ONBOO ...
- Centos7(Linux)网络配置,自动获取ip地址
Centos7.0 Vmware 网络桥接配置,利用DHCP自动获取ip地址 首先要将Vmware10.0.3设置为桥接模式. CentOS 7.0默认安装好之后是没有自动开启网络连接的! cd / ...
- linux如何自动获取ip地址
第一步:激活网卡 系统装好后默认的网卡是eth0,用下面的命令将这块网卡激活. # ifconfig eth0 up 第二步:设置网卡进入系统时启动 想要每次开机就可以自动获取IP地址上网,就要设置网 ...
随机推荐
- Eclipse和PyDev搭建完美Python开发环境(Windows篇)(转)
摘要:本文讲解了用Eclipse和PyDev搭建Python的开发环境. 十一长假在家闲着没事儿,准备花点时间学习一下Python. 今儿花了一个下午搭建Python的开发环境,不禁感叹————开 ...
- C++ template error: undefined reference to XXX
一般来说,写C++程序时推荐“类的声明和实现分离”,也就是说一个类的声明放在example.h文件中,而这个类的实现放在example.cpp文件中,这样方便管理,条理清晰. 但是如果类的声明用到了模 ...
- 设计模式6:Composite
Entry.java: package gendwang.cisco.com; public abstract class Entry { private int height = 0; privat ...
- wifi定位原理
wifi定位和手机基站定位类别似,两者都需要收集wifi位置信息接入点. 其实WIFI奇妙,它靠的是侦測附近周围全部的无线网路基地台 (WiFi Access Point) 的 MAC Address ...
- Android 保存用户偏好设置
很多情况下都允许用户根据自己的习惯和爱好去设置软件,而我们需要保存这些设置,可以用一个专业保存用户偏好的类:SharedPreferences. 这个类是实现方法其实也就是创建和修改 XML 文件, ...
- [置顶] Embedded Server:像写main函数一样写Web Server
1.传统的JEE Web Server 传统的JEE中,如果我们想要部署一个Web Application,我们需要首先安装一个Container Server,如JBoss,WebLogic,Tom ...
- TApplication.Initialize的前世今生
---------------------------------------------------------------------------------------------------- ...
- 一个计算器的C语言实现
今天在读<编译原理及实践>时.看到了一个简单的整数计算器的实现. 依照书上的思路,我略微进行了扩展: 1.从整数计算器扩展到小数计算器. 2.支持除法 3.支持空字符. 执行效果例如以下: ...
- ASP.Net状态管理读书笔记--思维导图
课前提问几个问题 使用Session 配置 model aspnet_regsql.exe 常见问答 问:为什么Session在有些机器上偶尔会丢失?答:可能和机器的环境有关系,比如:防火墙或者杀毒软 ...
- 函数指针玩得不熟,就不要自称为C语言高手(函数指针是解耦对象关系的最佳利器,还有signal)
记得刚开始工作时,一位高手告诉我说,longjmp和setjmp玩得不熟,就不要自称为C语言高手.当时我半信半疑,为了让自己向高手方向迈进,还是花了一点时间去学习longjmp和setjmp的用法.后 ...