-
说明
目前公司需求是主题爬虫的设计实现,与爬虫不同的蜘蛛程序是一个发现目标数据源网站的过程,一般从IP地址开始入手。 -
区别
蜘蛛和爬虫程序的区别:
(1)蜘蛛程序用于发现服务,获取有数据的目标网站或者链接;爬虫程序用于分析目标数据网站,爬取数据;
(2)蜘蛛的出发点是IP地址,一般通过特定的通用服务端口号,发现活跃的IP地址和端口,从而发现可用服务网站链接;爬虫的出发点是指定的网站URL,根据网站进行分析,获取数据;
(3)蜘蛛可用于探测活跃IP地址的分布,网络分布图等;爬虫可用于数据源抓取,数据分析等; -
实现逻辑框架
-
子蜘蛛实现过程
(1.1)扫描IP地址的方式
(1.1.1)Socket套接字,存在于应用层和传输层中间的一个抽象层;
try {
String ipString = "127.0.0.1";
int port = 80;
Socket s = new Socket(ipString, port);
} catch (UnknownHostException e) {
break;
} catch (Exception e) {
System.err.println(i+"端口:"+e.toString());
}
(1.1.2)ping命令,基于ICMP协议实现;
try {
String pingString = "ping 127.0.0.1";
Process process = Runtime.getRuntime().exec(pingString);
} catch (Exception e) {
System.out.println(e.getMessage());
}
(1.1.3)基于ICMP协议,判断IP地址是否可达;
try {
String ipString = "127.0.0.1";
int timeOut = 3000;
if(InetAddress.getByName(ipString).isReachable(timeOut)){
System.out.println("IP地址可达");
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
(1.1.4)基于TCP协议(三次握手,四次挥手过程),构造SYN报文,判断IP地址为活跃IP地址(需要使用jpcap插件);
/**
* 将字符串形式的MAC地址转换成存放在byte数组内的MAC地址
* @param str 字符串形式的MAC地址,如:AA-AA-AA-AA-AA
* @return 保存在byte数组内的MAC地址
*/
public static byte[] convertMac(String str) {
int hexNum = 16;
byte[] mac = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
String[] temp = str.split("-");
for (int x = 0; x < temp.length; x++) {
mac[x] = (byte) ((Integer.parseInt(temp[x], hexNum)) & 0xff);
}
return mac;
}
/**
* 发送TCP SYN报文,TCP三次握手协议,若主机在线发送SYN将会返回序列号+1的报文
* @param ip
* @param time
* @throws InterruptedException
* @throws IOException
*/
public SendSYN(JpcapSender sender,String ip, int time){
try{
// 需要打开的接口
int localInterface = 0;
// 一次捕获数据包的最大byte数
int snaplen = 65535;
// 捕获的数据包的超时设置
int timeNum = 3000;
String filterName = "tcp";
int srcPort = 80;
int dstPort = 139;
int serilerNumber = 123;
int ackFalg = 0;
int windowFlag = 1024;
int urgent = 0;
int parameters = 0;
// 服务类型
int rsv_tos = 0;
//分段偏移
int offset = 0;
// IP数据报识别标志
int ident = 0;
// 存活时间
int ttl = 63;
// 协议型号,230未定义协议
int protocol = 230;
// 本地主机的0号网络设备,根据具体实际情况对参数0进行修改
NetworkInterface device = JpcapCaptor.getDeviceList()[localInterface];
// 捕获数据包(需要打开的接口,一次捕获数据包的最大byte数,是否采用混乱模式,捕获的数据包的超时设置)
JpcapCaptor jpcap = JpcapCaptor.openDevice(device, interfaceNum, false, timeNum);
// 过滤tcp数据包
jpcap.setFilter(filterName, true);
JpcapSender sender = JpcapSender.openDevice(device);
// 本地端口,目的机端口,序列号,ACK数,urg, ack, psh, rst, syn, fin, rsv1, rsv2, window, urgent
TCPPacket tcp=new TCPPacket(srcPort, dstPort, serilerNumber, ackFlag, false, false, false, false, true, false, false, false, windowFlag, urgent);
// 本机的MAC地址
byte[] srcMac = convertMac("0C-54-A5-01-A3-FA");
// 目的机的MAC地址
byte[] dstMac = convertMac("00-23-89-a2-0f-b4");
InetAddress sendip=InetAddress.getByName("192.168.67.145");
InetAddress targetip=InetAddress.getByName(ip);
// 设置IPV4参数(优先权,服务类型设置,数据偏移设置,IP数据报识别标志)tcp.setIPv4Parameter(parameters, false, false, false, rsv_tos , true, false, false, offset, ident, ttl, protocol, sendip, targetip);
tcp.data="".getBytes();
// 设置数据链路层的帧
EthernetPacket ether=new EthernetPacket();
ether.frametype=EthernetPacket.ETHERTYPE_IP;
ether.src_mac= srcMac;
ether.dst_mac=dstMac;
tcp.datalink=ether;
System.out.println("sending SYN.."+ip);
sender.sendPacket(tcp);
sender.close();
Thread.sleep(time);
}
catch(Exception e){
System.out.println(e.getMessage());
}
}
也可通过构造FIN,ICMP报文等方法.
(1.2)IP分析
由于IP地址的特殊性,如果直接针对IP地址进行扫描,会存在多层循环,多线程内部,使用多层循环会存在不稳定性,因此将IP地址转换为二进制进行扫描,转换代码如下:
/**
* ip转换为长整型
* @param ipAddress
*/
public static long ipToLong(String ipAddress) {
long result = 0;
String[] ipAddressInArray = ipAddress.split("\\.");
for (int i = 3; i >= 0; i--) {
long ip = Long.parseLong(ipAddressInArray[3 - i]);
// left shifting 24,16,8,0 and bitwise OR
// 1. 192 << 24
// 1. 168 << 16
// 1. 1 << 8
// 1. 2 << 0
result |= ip << (i * 8);
}
return result;
}
因此在cmd终端执行:
ping 2886992166;
和
ping 172.20.1.38;
结果是相同的,这样172.20.1.39地址的二进制在2886992166+1即可,可以有效解决多层循环的性能问题;
(1.3)多线程实现扫描IP地址
public void startSpider() {
// 首先创建一个队列用于存储所有ip地址
LinkedBlockingQueue<String> allIp = new LinkedList<String>();
for (long i = 0; i < p2; i++) {
allIp.offer(String.valueOf((p1+i)));
}
// 创建一个线程池
int threadNum = 300;
ThreadPoolExecutor executor = new ThreadPoolExecutor(threadNum, threadNum+10,
Integer.parseInt(threadData[2]), TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
for (int i = 0; i < threadNum; i++) {
executor.execute(new PingRunner());
}
executor.shutdown();
try {
while (!executor.isTerminated()) {
Thread.sleep(100);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private class PingRunner implements Runnable {
private String taskIp = null;
private Socket socket;
private InetAddress addr;
// web.web,ftp,邮件,邮件,ftp,oracle,sql server,mysql,pointbase,DB2
private int[] port={80,8080,8000,21,25,110,20,1521,1433,3306,9092,5000};
private int timeOut = 300;
@Override
public void run() {
try {
while ((taskIp = getIp()) != null) {
addr = InetAddress.getByName(taskIp);
// 初始为300ms
if (addr.isReachable(timeOut)) {
System.out.println("reachable");
}
}
} catch (SocketException e) {
System.out.println("host ["+taskIp+"] permission denied");
} catch (Exception e) {
e.printStackTrace();
}
}
public String getIp() {
String ip = null;
synchronized (allIp) {
ip = allIp.poll();
}
if (ip != null) {
fetchedNum++;
}
return ip;
}
}
(1.4)分布式实现
子蜘蛛机可以根据上述的多线程实现扫描,请求母蜘蛛提供的任务接口,得到子蜘蛛分配得到的需要扫描的IP区间,进行扫描,将扫描结果和扫描时间等参数参数返回至母蜘蛛,再次请求下一次分配的扫描IP区间,监控机周期性刷新监控母蜘蛛传递的子蜘蛛扫描结果和子蜘蛛运行情况;具体分析见下回分解.