总结一下五种实现网络爬虫的方法(一,基于socket通信编写爬虫)

7 篇文章 0 订阅

最近呢,由于实习需要呢,复习一遍爬虫,前断时间闭关刷题去了,也会把刷题心得总结成博客分享给大家,比如java集合类特性及源码解析,操作系统数据结构的一些算法,设计模式等,放心,肯定不会鸽的,虽然可能会晚一点写。

言归正传,java实现网络爬虫一般有五种方法(据我所知,要是有其他方法的同学欢迎分享)

1.基于socket通信编写爬虫:最底层的方式,同时也是执行最高效的,不过开发效率最低。

2.基于HttpURLConnection类编写爬虫:java se的net包的核心类,主要用于http的相关操作。

3.基于apache的HttpClient包编写爬虫:由net包拓展而来,专为java网络通信编程而服务。

4.基于phantomjs之类的无头(无界面)浏览器:

    (1)它是浏览器的核心,并非浏览器。换言之,它是没有UI的浏览器。

    (2)它提供的js api,故它可以方便直接的被各种程序语言调用。换言之,似乎是js写的。

5.基于Selenium或者是WebDriver之类的有头(有界面)浏览器

    (1)它是浏览器核心,并非浏览器。换言之,它是没有界面UI的浏览器。无头,即无界面。

    (2)它提供的js api,故它可以方便直接的被各种程序语言调用。

其实第五个呢,我也不是很熟悉,到时候写第五篇的时候呢,会一边学一边写,可能会有比较幼稚的错误,欢迎大家指点哈。

这部分呢。借鉴了周光亮老师的视频上的部分讲解。

首先,这篇介绍的是socket编程编写爬虫,当然,一般在程序开发的时候我们一般不会用这种方式,毕竟httpclient几行代码的事情,但是基于这种方式使其他的方式更易于理解,了解一下还是比较必要的。

    socket并不是什么通信协义,只是为了方便tcp/ip层的上层访问tcp/ip层而做一层封装。即应用层以下负责ip地址间的传输,而应用层应用socket实现端对端的传输,即精确到    IP:端口。

首先呢,因为之后要写出很多的类,所以提前规划一下包结构是比较好的,如下:


com.lzx.simple.control这个包是用来给用户控制行为的包

com.lzx.simple.enumeration 这个包集合了一些枚举类型,如TaskLevel用来控制优先级。

package com.lzx.simple.enumeration;

/**
 * 抓取任务的优先级等级
 * @author Administrator
 *
 */
public enum TaskLevel {
	HIGH,MIDDLES,LOW
}

com.lzx.simple.iface.crawl这个包集合了一些接口,毕竟我们需要面向接口来编程,什么是面向接口编程?去拿本设计模式的书出去罚站

package com.lzx.simple.iface.crawl;

import com.lzx.simple.pojos.CrawlResultPojo;
import com.lzx.simple.pojos.UrlPojo;

public interface ICrawler {
	public CrawlResultPojo crawl(UrlPojo urlPojo);
}

com.lzx.simple.imple.crawl这个包集合了一些接口的实现类,比如这次的SocktCrawlerImpl类实现了ICrawler接口,这个类的代码不放了,待会儿单独解释。

com.lzx.simple.manager这个包集合了一些管理方法

com.lzx.simple.pojos这个包集合了一些简单对象,例如UrlPojo类

package com.lzx.simple.pojos;

import java.net.MalformedURLException;
import java.net.URL;

import com.lzx.simple.enumeration.TaskLevel;

/**
 * 简单的Java对象(Plain Ordinary Java Objects)
 * @author Administrator
 *
 */
public class UrlPojo {
	private String url;
	private TaskLevel taskLevel;
	
	public String getHost(){
		try {
			URL url = new URL(this.url);
			return url.getHost();
		} catch (MalformedURLException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
		return null;
	}
	
	
	public UrlPojo(String url) {
		this.url = url;
	}
	public UrlPojo(String url, TaskLevel taskLevel) {
		this.url = url;
		this.taskLevel = taskLevel;
	}
	public String getUrl() {
		return url;
	}
	public void setUrl(String url) {
		this.url = url;
	}
	public TaskLevel getTaskLevel() {
		return taskLevel;
	}
	public void setTaskLevel(TaskLevel taskLevel) {
		this.taskLevel = taskLevel;
	}
	
}

我们可以看到UrlPojo中有url属性(网址)和taskLevel(优先级),除了getset还有gethost函数,这个类封装 了一个需抓取的网页,还有另一个类如下:

package com.lzx.simple.pojos;

public class CrawlResultPojo {
	private boolean isSuccess;
	private String pageContent;
	private int httpStatuCode;
	public boolean isSuccess() {
		return isSuccess;
	}
	public void setSuccess(boolean isSuccess) {
		this.isSuccess = isSuccess;
	}
	public String getPageContent() {
		return pageContent;
	}
	public void setPageContent(String pageContent) {
		this.pageContent = pageContent;
	}
	public int getHttpStatuCode() {
		return httpStatuCode;
	}
	public void setHttpStatuCode(int httpStatuCode) {
		this.httpStatuCode = httpStatuCode;
	}
}
我们用CrawlResultPojo这个类来表示抓取结果,三个属性都很显而易见,抓取是否成功,网页内容,状态码。还有相应getset。

有些package还没有文件,之后用到会添加并说明。

然后开始我们的socket通信的类:

package com.lzx.simple.imple.crawl;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;

import javax.swing.text.AbstractDocument.BranchElement;

import com.lzx.simple.iface.crawl.ICrawler;
import com.lzx.simple.pojos.CrawlResultPojo;
import com.lzx.simple.pojos.UrlPojo;

public class SocketCrawlerImpl implements ICrawler{

	public CrawlResultPojo crawl(UrlPojo urlPojo) {
		CrawlResultPojo crawlResultPojo=new CrawlResultPojo();
		if (urlPojo==null||urlPojo.getUrl()==null ){
			crawlResultPojo.setSuccess(false);
			crawlResultPojo.setPageContent(null);
			
			return crawlResultPojo;
		}
		String host=urlPojo.getHost();
		if (host==null) {
			crawlResultPojo.setSuccess(false);
			crawlResultPojo.setPageContent(null);
			
			return crawlResultPojo;
		}
		BufferedWriter bufferedWriter=null;
		BufferedReader bufferedReader=null;
		try {
			Socket socket=new Socket(host, 80);
			//socket.setKeepAlive(false);
			bufferedWriter=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
			bufferedWriter.write("GET "+urlPojo.getUrl()+" HTTP/1.1\r\n");
			bufferedWriter.write("HOST:"+host+"\r\n");
			bufferedWriter.write("Connection:close"+"\r\n");
			bufferedWriter.write("\r\n");//提示http header结束
			bufferedWriter.flush();//flush()表示强制将缓冲区中的数据发送出去,不必等到缓冲区满.
			
			bufferedReader=new BufferedReader(new InputStreamReader(socket.getInputStream()));
			String line;
			StringBuilder stringBuilder=new StringBuilder();
			while((line=bufferedReader.readLine())!=null){
				stringBuilder.append(line+"\n");
			}
			crawlResultPojo.setSuccess(true);
			crawlResultPojo.setPageContent(stringBuilder.toString());
			
			return crawlResultPojo;
		} catch (UnknownHostException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}finally {
			try {
				if (bufferedReader!=null) {
					bufferedReader.close();					
				}
				if (bufferedWriter!=null) {
					bufferedWriter.close();									
				}
			} catch (Exception e2) {
				// TODO: handle exception
				e2.printStackTrace();
			}
		}
		return null;
	}
	public static void main(String[] args) {
		SocketCrawlerImpl socketCrawlerImpl=new SocketCrawlerImpl();
		UrlPojo urlPojo=new UrlPojo("http://www.baidu.com");
		CrawlResultPojo crawlResultPojo=socketCrawlerImpl.crawl(urlPojo);
		System.out.println(crawlResultPojo.getPageContent());
	}

}

这个是整个类的源码

主要的抓取工作就在crawl这个函数中,因为我们要返回CrawlResultPojo对象,所以一开始new了一个CrawlResultPojo;

然后判断待抓取的网页对象urlPojo是不是空,以及它的属性url是不是空。如果是就构造个返回失败的CrawlResultPojo返回回去。

然后gethost获得待抓取对象的主机名,用于待会儿构造http header。当然也要判断一下首部是否为空了。

然后构造缓冲输入流和输出流bufferedWriter和bufferedReader;

然后构造Socket,构造函数的参数这里是主机名+端口,端口默认是80,如果有特殊需要再改动;当然构造函数的参数也可以是IP地址+端口,端对端通信的本质。

然后在bufferedWriter构造http首部以便发送。

这里就有一个比较有趣事情了,周光亮老师在视频的讲解中发现了一个bug,他发现使用http/1.1的协议会导致抓取有一段真空期,即程序输出完这个页面的字符串后,会停顿一段时间然后再结束程序,而改用http/1.0的协议就不会,抓取完直接结束程序,周老师当时调试了很久都没有解决。

其实主要原因呢,是他在构造http首部的时候没有加

bufferedWriter.write("Connection:close"+"\r\n");

这段代码,为什么加了这段代码后就可以去除真空期呢?

因为,

http 1.0中默认是关闭的,需要在http头加入"Connection: Keep-Alive",才能启用Keep-Alive;

http 1.1中默认启用Keep-Alive,如果加入"Connection: close ",才关闭。

嗯,1.1版本更持久一点- -,


构造完就调用flush扔出去了

然后输入流就不多说了,基本套路。

这样就实现了socket抓取网页= =。


  • 4
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值