原生Java使用Socket制作爬虫重复造轮子系列-一步步带-1

Hello,大家好,我是Shendi, 这次给大家带来爬虫系列, 网上大部分都是使用了框架的,这次我用单纯的Socket来制作爬虫

纯手码,如果有地方有点小错误请指出

先说一下思路:既然要做爬虫,那么就需要 知道http协议

先使用Socket创建TCP连接

Socket socket = new Socket("www.baidu.com",80);

就这一行代码,我们就将我们的socket指向百度的主机,但因为是http协议的,我们乱发数据没用,所以服务端不会返回任何数据

所以我们需要告诉那边的服务端, 我要访问你, 也就是获取输出流OutputStream发送指定的协议内容

输出指定的协议内容来与服务端进行通信

OutputStream output = socket.getOutputStream();
output.write("GET / HTTP/1.1\r\n".getBytes());
output.write(("HOST: www.baidu.com\r\n").getBytes());
output.write("\r\n".getBytes());

解释一下上面输出的数据

GET / HTTP/1.1\r\n 固定的(GET请求 获取/路径,关于访问子项目以及类型的下次说)

HOST: www.baidu.com\r\n 代表访问的主机

注意,我们访问的只能是根域名或者直接是ip 不能像www.baidu.com/index.php 这个地方只能填ip或根域名

\r\n换行 固定写法

我们发送指定的数据过去,服务端就会回应数据,我们现在就需要接收数据 使用InputStream

接收服务端返回的数据

InputStream input = socket.getInputStream();
int len = -1;
byte[] data = new byte[1024];
while ((len = input.read()) != -1) {
    System.out.print(new String(data,0,len));
}

这样我们就已经看到了百度的信息头与信息数据了 但是程序没结束,最后报超时错,这个下面会讲

思路大概就这样,接下来开始写项目(算是对自己的一次总结吧)

创建一个GetHttp类(运行测试类)

public class GetHttp {
	public static void main(String[] args) throws MalformedURLException, IOException {
		HttpSocketUtils socket = new HttpSocketUtils("www.hackshendi.club/App",80);
		HttpUtils http = socket.openHttpConn();
		System.out.println("------状态");
		System.out.println(http.getState());
		System.out.println("------状态信息");
		System.out.print(http.getStateInfo());
		System.out.println("------头");
		System.out.print(http.getHead());
		System.out.println("------body");
		System.out.print(http.getBody());
		socket.close();
	}
}

创建一个接口 Protocol 协议接口(后面协议可能不止一种)

/**
 * -协议接口
 * @author <a href='tencent://AddContact/?fromId=45&fromSubId=1&subcmd=all&uin=1711680493'>Shendi</a>
 */
public interface Protocol {
    /**
	 * -打开http连接
	 * @return 返回一个HttpUtils
	 */
	public HttpUtils openHttpConn();
}

接下来创建一个Socket 工具类 HttpSocketUtils (一步步来 下方代码讲解)

/**
 * -Socket用于Http的封装工具类 只要建立此类连接就已经打开
 * @author <a href='tencent://AddContact/?fromId=45&fromSubId=1&subcmd=all&uin=1711680493'>Shendi</a>
 */
public class HttpSocketUtils implements Protocol {
	private Socket socket;
	private InputStreamUtils input;
	private OutputStream output;
	private String host;// 主机名
	private int port;// 端口
	private int outTime;// 超时
	private Protocol protocol;// 协议
	private String subitem = "";//子项
	
	/**
	 * -使用ip+端口建立连接
	 * @param host
	 * @param port
	 */
	public HttpSocketUtils(String host, int port) {
		// 默认五秒的超时时间
		this("GET",host, port, 5000);
	}

	/**
	 * 使用ip+端口+请求类型建立连接
	 * @param reqType GET or POST
	 * @param host
	 * @param port
	 */
	public HttpSocketUtils(String reqType,String host, int port) {
		// 默认五秒的超时时间
		this(reqType,host, port, 5000);
	}
	
	/**
	 * -使用ip+端口+超时时间+请求类型建立连接
	 * @param reqType GET or POST
	 * @param host
	 * @param port
	 * @param outTime
	 */
	public HttpSocketUtils(String reqType,String hostArg, int port, int outTime) {
		try {
			this.host = hostArg;
			this.port = port;
			this.outTime = outTime;
			//判断是否有子项目 有则获取子项目
			int index = host.indexOf('/');
			if (index != -1) {
				subitem = host.substring(index,host.length());
				this.host = host.substring(0,index);
			}
			//获取连接
			socket = new Socket(host, port);
			socket.setSoTimeout(outTime);
			// 初始化协议
			protocol = new Protocol() {
				public HttpUtils openHttpConn() {
					try {
						//输出http协议
						output = socket.getOutputStream();
						output.write((reqType+" " + subitem + "/ HTTP/1.1\r\n").getBytes());
						output.write(("HOST: "+ host +" \r\n").getBytes());
						output.write("\r\n".getBytes());
						//解析 获取结果
						input = new InputStreamUtils(socket.getInputStream());
						return new HttpUtils(input);
					} catch (IOException e) {
						e.printStackTrace();
						WebLog.printErr(e.getMessage());
					}
					return null;
				}
			};
		} catch (IOException e) {
			e.printStackTrace();
			WebLog.printErr(e.getMessage());
		}
	}

	/**
	 * -打开http连接根据地址
	 * @param host 域名或者ip
	 * @param port 端口
	 * @return 返回Http工具类 如果为null则代表获取失败
	 */
	public HttpUtils openHttpConn() {
		return protocol.openHttpConn();
	}

	public void close() {
		try {
			if (input != null)
				input.close();
		} catch (IOException e) {
			e.printStackTrace();
			WebLog.printErr(e.getMessage());
		} finally {
			try {
				if (output != null)
					output.close();
			} catch (IOException e) {
				e.printStackTrace();
				WebLog.printErr(e.getMessage());
			} finally {
				try {
					if (socket != null)
						socket.close();
				} catch (IOException e) {
					e.printStackTrace();
					WebLog.printErr(e.getMessage());
				}
			}
		}
	}
	
	public String getHost() {
		return host;
	}
	
	public int getOutTime() {
		return outTime;
	}	
	
	public int getPort() {
		return port;
	}
}

既然是Socket工具类,那么就肯定有Socket操作,所以我们需要创建Socket,我写在了构造函数里,实现了接口

主要的函数就是openHttpConn函数了,获取一个HttpUtils的对象, 这个调用指向的是构造 函数里实现里写的那个方法

 在此实现里,我做了url中带子项路径的操作,这个类主要是用于获取连接+输出信息头给服务端,如果不懂请详看上面

接下来就是获取数据操作的类了 HttpUtils;WebLog 类为日志打印类

/**
 * -Http协议的工具类 用于搭建与http通信
 * @author <a href='tencent://AddContact/?fromId=45&fromSubId=1&subcmd=all&uin=1711680493'>Shendi</a>
 */
public class HttpUtils {
	private InputStreamUtils input;
	private String head;//数据头
	private String body;//数据
	private String state;//状态
	private String stateInfo;//状态信息
	public HttpUtils(InputStreamUtils input) throws IOException {
		if (input == null) {
			throw new IOException("input is null");
		}
		this.input = input;
		//初始化 获取数据头 数据
		Initialize();
	}

	private void Initialize() throws IOException {
		if (input == null) {
			throw new IOException("input is null");
		}
		//获取状态
		StringBuffer dataHead = new StringBuffer();
		String data = null;
		//第一行为状态
		data = input.readLine();
		if (data != null) {
			dataHead.append(data);
			String[] datas = data.split(" ");
			if (datas.length > 1) {
				state = datas[1];
				if (datas.length > 2) {
					stateInfo = datas[2].substring(0,datas[2].length()-2);
				}
			}
			//快速释放资源
			datas = null;
		}
		int dataBodySize = -1;
		//获取数据头
		while ((data = input.readLine()) != null) {
			dataHead.append(data);
			//取得长度后 获取数据
			String[] map = data.split(":");
			if (map.length > 0) {
				String key = map[0].trim();
				if (map.length > 1) {
					String value = map[1].trim();
					//到了数据头的尾部
					if ("Content-Length".equals(key)) {
						//在读一行 跳出
						input.readLine();
						dataBodySize = Integer.parseInt(value);
						break;
					}
				}
			} else {
				throw new IOException("no content length");
			}
		}
		//去掉换行符
		dataHead.substring(0,dataHead.length()-2);
		if (dataHead != null && !"".equals(dataHead.toString())) {
			head = dataHead.toString();
		}
		if (dataBodySize != -1) {
			//读取数据内容
			byte[] dataBody = new byte[dataBodySize];
			input.read(dataBody);
			body = new String(dataBody);
		}
		
	}
	
	public String getHead() {
		return head;
	}
	
	public String getBody() {
		return body;
	}
	/**
	 * -状态
	 * @return
	 */
	public String getState() {
		return state;
	}
	/**
	 * -状态的信息
	 * @return
	 */
	public String getStateInfo() {
		return stateInfo;
	}
}

这个类用于InputStream操作,将数据处理好,爬出整个网页的html内容  

然后就是我造的轮子 InputStreamUtils类

/**
 * -读取的封装流 增强操作
 * @author <a href='tencent://AddContact/?fromId=45&fromSubId=1&subcmd=all&uin=1711680493'>Shendi</a>
 */
public class InputStreamUtils {
	private InputStream input;
	public InputStreamUtils(InputStream input) throws IOException {
		if (input == null) {
			throw new IOException("input is null");
		}
		this.input = input;
	}
	
	/**
	 * -调用input的read方法
	 */
	public int read() throws IOException {
		return input.read();
	}
	/**
	 * -调用input的read方法
	 */
	public int read(byte[] b) throws IOException {
		return input.read(b);
	}
	
	/**
	 * -读取一行
	 * @return 如果没有数据 则返回null
	 * @throws IOException 
	 */
	public String readLine() throws IOException {
		int value = -1;
		//有效数据长度
		int len = 0;
		byte[] data = new byte[1024];
		while ((value = input.read()) != -1) {
			//如果数组长度不够则增长
			if (len >= data.length) {
				byte[] temp = data;
				data = new byte[len+1024];
				System.arraycopy(temp,0,data,0,temp.length);
			}
			data[len] = (byte) value;
			len++;
			//如果读到\n则读取完这一行
			if ((char)value == '\n') {
				break;
			}
		}
		//如果数组长度为0 则返回null
		if (len == 0) {
			return null;
		}
		return new String(data,0,len);
	}
	
	public void close() throws IOException {
		input.close();
	}
	
}

这个类主要是提供一个readLine方法,没啥好讲的

就这样,一个简单的爬虫写好了,能够获取数据头,数据体,状态,状态信息以及访问子项目等

 如果对你有帮助的话请点个赞吧~ 关注我,下节教你爬资源

发布了38 篇原创文章 · 获赞 23 · 访问量 9051

猜你喜欢

转载自blog.csdn.net/qq_41806966/article/details/102903174