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方法,没啥好讲的
就这样,一个简单的爬虫写好了,能够获取数据头,数据体,状态,状态信息以及访问子项目等
如果对你有帮助的话请点个赞吧~ 关注我,下节教你爬资源