零基础写Java知乎爬虫之进阶篇
说到爬虫,使用Java本身自带的URLConnection可以实现一些基本的抓取页面的功能,但是对于一些比较高级的功能,比如重定向的处理,HTML标记的去除,仅仅使用URLConnection还是不够的。
在这里我们可以使用HttpClient这个第三方jar包。
接下来我们使用HttpClient简单的写一个爬去百度的Demo:
importjava.io.InputStream;
importjava.io.OutputStream;
importorg.apache.commons.httpclient.HttpClient;
importorg.apache.commons.httpclient.HttpStatus;
importorg.apache.commons.httpclient.methods.GetMethod;
/**
*
*@authorCallMeWhy
*
*/
publicclassSpider{
privatestaticHttpClienthttpClient=newHttpClient();
/**
*@parampath
* 目标网页的链接
*@return返回布尔值,表示是否正常下载目标页面
*@throwsException
* 读取网页流或写入本地文件流的IO异常
*/
publicstaticbooleandownloadPage(Stringpath)throwsException{
//定义输入输出流
InputStreaminput=null;
OutputStreamoutput=null;
//得到post方法
GetMethodgetMethod=newGetMethod(path);
//执行,返回状态码
intstatusCode=httpClient.executeMethod(getMethod);
//针对状态码进行处理
//简单起见,只处理返回值为200的状态码
if(statusCode==HttpStatus.SC_OK){
input=getMethod.getResponseBodyAsStream();
//通过对URL的得到文件名
Stringfilename=path.substring(path.lastIndexOf("/")+1)
+".html";
//获得文件输出流
output=newFileOutputStream(filename);
//输出到文件
inttempByte=-1;
while((tempByte=input.read())>0){
output.write(tempByte);
}
//关闭输入流
if(input!=null){
input.close();
}
//关闭输出流
if(output!=null){
output.close();
}
returntrue;
}
returnfalse;
}
publicstaticvoidmain(String[]args){
try{
//抓取百度首页,输出
Spider.downloadPage("http://www.baidu.com");
}catch(Exceptione){
e.printStackTrace();
}
}
}
但是这样基本的爬虫是不能满足各色各样的爬虫需求的。
先来介绍宽度优先爬虫。
宽度优先相信大家都不陌生,简单说来可以这样理解宽度优先爬虫。
我们把互联网看作一张超级大的有向图,每一个网页上的链接都是一个有向边,每一个文件或没有链接的纯页面则是图中的终点:
宽度优先爬虫就是这样一个爬虫,爬走在这个有向图上,从根节点开始一层一层往外爬取新的节点的数据。
宽度遍历算法如下所示:
(1)顶点V入队列。
(2)当队列非空时继续执行,否则算法为空。
(3)出队列,获得队头节点V,访问顶点V并标记V已经被访问。
(4)查找顶点V的第一个邻接顶点col。
(5)若V的邻接顶点col未被访问过,则col进队列。
(6)继续查找V的其他邻接顶点col,转到步骤(5),若V的所有邻接顶点都已经被访问过,则转到步骤(2)。
按照宽度遍历算法,上图的遍历顺序为:A->B->C->D->E->F->H->G->I,这样一层一层的遍历下去。
而宽度优先爬虫其实爬取的是一系列的种子节点,和图的遍历基本相同。
我们可以把需要爬取页面的URL都放在一个TODO表中,将已经访问的页面放在一个Visited表中:
则宽度优先爬虫的基本流程如下:
(1)把解析出的链接和Visited表中的链接进行比较,若Visited表中不存在此链接,表示其未被访问过。
(2)把链接放入TODO表中。
(3)处理完毕后,从TODO表中取得一条链接,直接放入Visited表中。
(4)针对这个链接所表示的网页,继续上述过程。如此循环往复。
下面我们就来一步一步制作一个宽度优先的爬虫。
首先,对于先设计一个数据结构用来存储TODO表, 考虑到需要先进先出所以采用队列,自定义一个Quere类:
/**
*自定义队列类保存TODO表
*/
publicclassQueue{
/**
*定义一个队列,使用LinkedList实现
*/
privateLinkedList<Object>queue=newLinkedList<Object>();//入队列
/**
*将t加入到队列中
*/
publicvoidenQueue(Objectt){
queue.addLast(t);
}
/**
*移除队列中的第一项并将其返回
*/
publicObjectdeQueue(){
returnqueue.removeFirst();
}
/**
*返回队列是否为空
*/
publicbooleanisQueueEmpty(){
returnqueue.isEmpty();
}
/**
*判断并返回队列是否包含t
*/
publicbooleancontians(Objectt){
returnqueue.contains(t);
}
/**
*判断并返回队列是否为空
*/
publicbooleanempty(){
returnqueue.isEmpty();
}
}
还需要一个数据结构来记录已经访问过的URL,即Visited表。
考虑到这个表的作用,每当要访问一个URL的时候,首先在这个数据结构中进行查找,如果当前的URL已经存在,则丢弃这个URL任务。
这个数据结构需要不重复并且能快速查找,所以选择HashSet来存储。
综上,我们另建一个SpiderQueue类来保存Visited表和TODO表:
importjava.util.Set;
/**
*自定义类保存Visited表和unVisited表
*/
publicclassSpiderQueue{
/**
*已访问的url集合,即Visited表
*/
privatestaticSet<Object>visitedUrl=newHashSet<>();
/**
*添加到访问过的URL队列中
*/
publicstaticvoidaddVisitedUrl(Stringurl){
visitedUrl.add(url);
}
/**
*移除访问过的URL
*/
publicstaticvoidremoveVisitedUrl(Stringurl){
visitedUrl.remove(url);
}
/**
*获得已经访问的URL数目
*/
publicstaticintgetVisitedUrlNum(){
returnvisitedUrl.size();
}
/**
*待访问的url集合,即unVisited表
*/
privatestaticQueueunVisitedUrl=newQueue();
/**
*获得UnVisited队列
*/
publicstaticQueuegetUnVisitedUrl(){
returnunVisitedUrl;
}
/**
*未访问的unVisitedUrl出队列
*/
publicstaticObjectunVisitedUrlDeQueue(){
returnunVisitedUrl.deQueue();
}
/**
*保证添加url到unVisitedUrl的时候每个URL只被访问一次
*/
publicstaticvoidaddUnvisitedUrl(Stringurl){
if(url!=null&&!url.trim().equals("")&&!visitedUrl.contains(url)
&&!unVisitedUrl.contians(url))
unVisitedUrl.enQueue(url);
}
/**
*判断未访问的URL队列中是否为空
*/
publicstaticbooleanunVisitedUrlsEmpty(){
returnunVisitedUrl.empty();
}
}
上面是一些自定义类的封装,接下来就是一个定义一个用来下载网页的工具类,我们将其定义为DownTool类:
importjava.io.*;
importorg.apache.commons.httpclient.*;
importorg.apache.commons.httpclient.methods.*;
importorg.apache.commons.httpclient.params.*;
publicclassDownTool{
/**
*根据URL和网页类型生成需要保存的网页的文件名,去除URL中的非文件名字符
*/
privateStringgetFileNameByUrl(Stringurl,StringcontentType){
//移除"http://"这七个字符
url=url.substring(7);
//确认抓取到的页面为text/html类型
if(contentType.indexOf("html")!=-1){
//把所有的url中的特殊符号转化成下划线
url=url.replaceAll("[\\?/:*|<>\"]","_")+".html";
}else{
url=url.replaceAll("[\\?/:*|<>\"]","_")+"."
+contentType.substring(contentType.lastIndexOf("/")+1);
}
returnurl;
}
/**
*保存网页字节数组到本地文件,filePath为要保存的文件的相对地址
*/
privatevoidsaveToLocal(byte[]data,StringfilePath){
try{
DataOutputStreamout=newDataOutputStream(newFileOutputStream(
newFile(filePath)));
for(inti=0;i<data.length;i++)
out.write(data[i]);
out.flush();
out.close();
}catch(IOExceptione){
e.printStackTrace();
}
}
//下载URL指向的网页
publicStringdownloadFile(Stringurl){
StringfilePath=null;
//1.生成HttpClinet对象并设置参数
HttpClienthttpClient=newHttpClient();
//设置HTTP连接超时5s
httpClient.getHttpConnectionManager().getParams()
.setConnectionTimeout(5000);
//2.生成GetMethod对象并设置参数
GetMethodgetMethod=newGetMethod(url);
//设置get请求超时5s
getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT,5000);
//设置请求重试处理
getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
newDefaultHttpMethodRetryHandler());
//3.执行GET请求
try{
intstatusCode=httpClient.executeMethod(getMethod);
//判断访问的状态码
if(statusCode!=HttpStatus.SC_OK){
System.err.println("Methodfailed:"
+getMethod.getStatusLine());
filePath=null;
}
//4.处理HTTP响应内容
byte[]responseBody=getMethod.getResponseBody();//读取为字节数组
//根据网页url生成保存时的文件名
filePath="temp\\"
+getFileNameByUrl(url,
getMethod.getResponseHeader("Content-Type")
.getValue());
saveToLocal(responseBody,filePath);
}catch(HttpExceptione){
//发生致命的异常,可能是协议不对或者返回的内容有问题
System.out.println("请检查你的http地址是否正确");
e.printStackTrace();
}catch(IOExceptione){
//发生网络异常
e.printStackTrace();
}finally{
//释放连接
getMethod.releaseConnection();
}
returnfilePath;
}
}
在这里我们需要一个HtmlParserTool类来处理Html标记:
importjava.util.HashSet;
importjava.util.Set;
importorg.htmlparser.Node;
importorg.htmlparser.NodeFilter;
importorg.htmlparser.Parser;
importorg.htmlparser.filters.NodeClassFilter;
importorg.htmlparser.filters.OrFilter;
importorg.htmlparser.tags.LinkTag;
importorg.htmlparser.util.NodeList;
importorg.htmlparser.util.ParserException;
importmodel.LinkFilter;
publicclassHtmlParserTool{
//获取一个网站上的链接,filter用来过滤链接
publicstaticSet<String>extracLinks(Stringurl,LinkFilterfilter){
Set<String>links=newHashSet<String>();
try{
Parserparser=newParser(url);
parser.setEncoding("gb2312");
//过滤<frame>标签的filter,用来提取frame标签里的src属性
NodeFilterframeFilter=newNodeFilter(){
privatestaticfinallongserialVersionUID=1L;
@Override
publicbooleanaccept(Nodenode){
if(node.getText().startsWith("framesrc=")){
returntrue;
}else{
returnfalse;
}
}
};
//OrFilter来设置过滤<a>标签和<frame>标签
OrFilterlinkFilter=newOrFilter(newNodeClassFilter(
LinkTag.class),frameFilter);
//得到所有经过过滤的标签
NodeListlist=parser.extractAllNodesThatMatch(linkFilter);
for(inti=0;i<list.size();i++){
Nodetag=list.elementAt(i);
if(taginstanceofLinkTag)//<a>标签
{
LinkTaglink=(LinkTag)tag;
StringlinkUrl=link.getLink();//URL
if(filter.accept(linkUrl))
links.add(linkUrl);
}else//<frame>标签
{
//提取frame里src属性的链接,如<framesrc="test.html"/>
Stringframe=tag.getText();
intstart=frame.indexOf("src=");
frame=frame.substring(start);
intend=frame.indexOf("");
if(end==-1)
end=frame.indexOf(">");
StringframeUrl=frame.substring(5,end-1);
if(filter.accept(frameUrl))
links.add(frameUrl);
}
}
}catch(ParserExceptione){
e.printStackTrace();
}
returnlinks;
}
}
最后我们来写个爬虫类调用前面的封装类和函数:
importjava.util.Set;
importmodel.LinkFilter;
importmodel.SpiderQueue;
publicclassBfsSpider{
/**
*使用种子初始化URL队列
*/
privatevoidinitCrawlerWithSeeds(String[]seeds){
for(inti=0;i<seeds.length;i++)
SpiderQueue.addUnvisitedUrl(seeds[i]);
}
//定义过滤器,提取以http://www.xxxx.com开头的链接
publicvoidcrawling(String[]seeds){
LinkFilterfilter=newLinkFilter(){
publicbooleanaccept(Stringurl){
if(url.startsWith("http://www.baidu.com"))
returntrue;
else
returnfalse;
}
};
//初始化URL队列
initCrawlerWithSeeds(seeds);
//循环条件:待抓取的链接不空且抓取的网页不多于1000
while(!SpiderQueue.unVisitedUrlsEmpty()
&&SpiderQueue.getVisitedUrlNum()<=1000){
//队头URL出队列
StringvisitUrl=(String)SpiderQueue.unVisitedUrlDeQueue();
if(visitUrl==null)
continue;
DownTooldownLoader=newDownTool();
//下载网页
downLoader.downloadFile(visitUrl);
//该URL放入已访问的URL中
SpiderQueue.addVisitedUrl(visitUrl);
//提取出下载网页中的URL
Set<String>links=HtmlParserTool.extracLinks(visitUrl,filter);
//新的未访问的URL入队
for(Stringlink:links){
SpiderQueue.addUnvisitedUrl(link);
}
}
}
//main方法入口
publicstaticvoidmain(String[]args){
BfsSpidercrawler=newBfsSpider();
crawler.crawling(newString[]{"http://www.baidu.com"});
}
}
运行可以看到,爬虫已经把百度网页下所有的页面都抓取出来了:
以上就是java使用HttpClient工具包和宽度爬虫进行抓取内容的操作的全部内容,稍微复杂点,小伙伴们要仔细琢磨下哦,希望对大家能有所帮助
相关文章
- 每天20分支之java grpc的metadata
- java解析xml方法_详解Java解析XML的四种方法
- java static 变量存在哪_Java中的静态方法和静态变量存储在哪里?
- java queue toarray_Java PriorityBlockingQueue toArray()用法及代码示例
- Java中static的含义和用法
- java常量有哪些_Java中的常量有哪些?
- java环境_Java基础篇——环境配置
- java 特点_JAVA的几个重要特点[通俗易懂]
- eclipse运行java程序_如何在Eclipse中运行简单的Java程序?「建议收藏」
- Java把string转json格式_java实体类转json字符串
- java 基础语法
- rtsp 获取视频流 java_Java获取rtsp视频流,实现rtsp流预览功能,并将视频流每帧保存成图片…
- Java map转实体类_java实体类转json
- Java岗大厂面试百日冲刺 - 日积月累,每日三题【Day05】——Java高级篇
- Linux安装Java: 迈出第一步(linux上安装java)
- Java中的静态块与非静态块详解编程语言
- 处理使用Java处理Redis数据过期(redisjava过期)
- 实现使用Redis与Java实现过期数据清理(redisjava过期)
- 部署Java开发下的Linux部署(java开发linux)
- 深入浅出 使用 Java 连接 Neo4j(java连接neo4j)
- Java操作Linux系统:让命令行更轻松(java操作linux)
- Java操作Redis实现数据快速存取(java访问redis)
- Java实现Linux:跨平台解决方案(java 实现linux)
- Java更新提升Oracle软件性能(java更新oracle)
- java和matlab画多边形闭合折线图示例讲解