热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

实测可用的宽度优先爬虫的实现

参考文献:自己动手写网络爬虫,罗刚,王振东著(我感觉这本书对我还是蛮有用的,爬虫大杂烩啊)前面写了一篇利用HttpClient来获取单个网页的灌水文,现在希望在此基础之上可以通过一

参考文献:自己动手写网络爬虫,罗刚,王振东著(我感觉这本书对我还是蛮有用的,爬虫大杂烩啊)

前面写了一篇利用HttpClient来获取单个网页的灌水文,现在希望在此基础之上可以通过一个种子网页能够爬更多的相关网页。

由于互联网的页面上都是相互链接的,可以看成一个超级大的图,每个页面都可以看成是一个节点,而页面中的链接可以看成是图的有向边。

因此能够通过遍历的方式对互联网这个超级大的图进行访问。

突然就把很具体的问题用数据结构抽象的方法给表述出来的了,果然还是抽象牛叉。

图的遍历常可以分为宽度优先遍历和深度优先遍历两种方式。

大多数网络爬虫都是通过宽度优先的方式爬取,爬得太深了反而不太好。

图的宽度优先遍历

图的宽度优先遍历是一个分层搜索的过程,和树的层序遍历算法相同。

从图中选中一个节点,作为起始节点,然后按照层次遍历的方式,一层一层的访问。

首先需要一个队列作为保存当前节点的子节点的数据结构。

  1. 顶点V入队列
  2. 当队列非空时继续执行,否则停止计算
  3. 出队列,获得队头节点V,访问顶点V并标记V已经被访问
  4. 查找顶点V的第一个邻接顶点col
  5. 若V的邻接顶点col未被访问过,则col进队列
  6. 继续查找V的其他邻接顶点col,转到步骤5,如果V的所有邻接顶点都已经被访问过,则转到步骤2

下图是一个待遍历的图,看看这个图通过宽度优先是如何遍历的?

技术分享

这里如果以A为种子节点的话,

A进队列

A出队列

A的子节点为:BCDEF,进队列

B出队列,由于B没有子节点,所以没有节点入队列,剩下CDEF

同理C和D由于没有子节点,也都出队列了,剩下EF

E出队列,队列中还剩下F

E的子节点H入队列,队列中还剩下FH

F,出队列,队列中还剩下H

F的子节点G入队列,还剩下HG

H出队列,还剩下G

H的子节点I入队列,还剩下GI

G出队列,还剩下I

I出队列,队列为空,遍历结束

遍历顺序为A->B->C->D->E->F->H->G->I

层次遍历的示意图如下图

技术分享

宽度优先爬虫过程

在网页中如果HTML文档中存在超链接,那么这些超链接所指向的网页可以看成是该网页的子节点,

而那些不是指向HTML文档的超链接则可以看成是终端,它们是没有子节点的。

爬虫的种子几点也可以有多个。

整个宽度优先爬虫的过程就是从一些列的种子节点开始,把这些网页中的子节点(超链接)提取出来,放入队列中依次进行抓去。

被处理过的超链接需要放入到一张表中(visited表中)。每次在处理一个新的链接之前都要查看是否已经存在于visited表中。

如果存在则证明链接已经处理过,跳过不作处理,否则进行下一步处理。

技术分享

初始的URL地址是作为爬虫系统的种子URL(一般在配置文件中指定)

然后解析这个URL,产生新的URL

爬虫过程为:

  1. 把解析出的链接和Visited表中的链接进行比较,若Visited表中不存在此链接,表示其未被访问过;
  2. 把链接放入到TODO表中;
  3. 处理完毕后,再次从TODO列表中取出一条链接,直接放入到Visited列表中;
  4. 针对这个链接所表示的网页,继续上述过程,如此循环。

宽度优先爬虫的好处

  • 重要的网页往往离种子比较近,随着不断的深入网页的重要性越来越低;
  • 万维网的实际深度最多能达到17层,但到达某个网页总存在一条很近的路径,而宽度优先能以最快的速度到达这个网页;
  • 宽度优先有利于多爬虫合作抓取,多爬虫合作通常先抓取网站内的链接

宽度优先爬虫实现

具体流程如下:

技术分享

涉及到四个类

Queue类:用于保存将要访问的URL

LinkQueue类:保存以访问的URL,并判断给定的URL是否被访问过

DownLoadFile类:下载给定的URL指向的网页,以及进行一些列设置

HtmlParserTool类:对已获取的HTML页面进行处理,用来过滤链接,获得新的链接

MyCrawler类:爬虫的主程序

书上给的代码用的HttpClient搞不清楚是哪个版本的,然后码了字之后也执行不了,各种出错,主要好似HttpClient这个类中方法变动太大。

技术分享

Queue类:用于保存将要访问的URL

package com.abc.bfs;
import java.util.LinkedList;
import java.util.Scanner;

//用链表实现队列
public class Queue {
	//realize queue with linklist
	private LinkedList queue = new LinkedList();
	//入队列
	public void enQueue(String t) {
		queue.add(t);
	}
	//出队列
	public Object deQueue() {
		return queue.removeFirst();
	}
	//判断队列是否为空
	public boolean isQueueEmpty() {
		return queue.isEmpty();
	}
	//判断队列是否包含t
	public boolean contains(String t) {
		return queue.contains(t);
	}

	//用于测试这个类
	public static void main(String[] args) {
		Queue qqq = new Queue();
		Scanner sc = new Scanner(System.in);
		System.out.println("[0] Input a object to Queue: ");
		System.out.println("[1] Output a object from Queue: ");
		System.out.println("[2] Test a object in or not in Queue: ");
		System.out.println("[3] If the Queue is empty: ");
		System.out.println("[4] Exit!"); 
		System.out.print("Enter a number: ");
		int opt = sc.nextInt();
		while (true) {
			switch (opt) {
				case 0: {
					System.out.print("[0] Input a object to Queue: ");
					sc = new Scanner(System.in);
					String a = sc.nextLine();
					qqq.enQueue(a);
					break;
				}
				case 1: {
					System.out.print("[1] Output a object from Queue: ");
					String a = (String)qqq.deQueue();
					System.out.println(a);
					break;
				}
				case 2: {
					System.out.print("[2] Test a object in or not in Queue: ");
					sc = new Scanner(System.in);
					String a = sc.nextLine();
					if (qqq.contains(a))
						System.out.print("true");
					else
						System.out.print("false");
					break;
				}
				case 3: { 
					System.out.println("[3] If the Queue is empty: ");
					if (qqq.isQueueEmpty())
						System.out.print("true");
					else
						System.out.print("false");
					break;
				}
				case 4: return;
			}
			System.out.println("[0] Input a object to Queue: ");
			System.out.println("[1] Output a object from Queue: ");
			System.out.println("[2] Test a object in or not in Queue: ");
			System.out.println("[3] If the Queue is empty: ");
			System.out.println("[4] Exit!"); 
			System.out.print("Enter a number: ");
			sc = new Scanner(System.in);
			opt = sc.nextInt();
		}	
	}
}

LinkQueue类:保存以访问的URL,并判断给定的URL是否被访问过

package com.abc.bfs;
import java.util.HashSet;
import java.util.Set;
public class LinkQueue {
	//collection of used URL
	private static Set visitedUrl = new HashSet();
	//collection of ready-to-visit URL
	private static Queue unVisitedUrl = new Queue();
	//get queue of URL
	public static Queue getUnVisitedUrl() {
		return unVisitedUrl;
	}
	//add the visited URL
	public static void addVisitedUrl(String url) {
		visitedUrl.add(url);
	}
	//remove visited URL
	public static void removeVisitedUrl(String url) {
		visitedUrl.remove(url);
	}
	//pop unvisited URL
	public static Object unVisitedUrlDeQueue() {
		return unVisitedUrl.deQueue();
	}
	//ensure each URL only visited once
	public static void addUnvisitedUrl(String url) {
		if (url != null && !url.trim().equals("")
				&& !visitedUrl.contains(url)
				&& !unVisitedUrl.contains(url))
			unVisitedUrl.enQueue(url);
	}
	public static int getVisitedUrlNum() {
		return visitedUrl.size();
	}
	//judge the unvisited URL empty or not
	public static boolean unVisitedUrlIsEmpty() {
		return unVisitedUrl.isQueueEmpty();
	}
}

DownLoadFile类:下载给定的URL指向的网页,以及进行一些列设置

package com.abc.bfs;

import java.io.IOException;
import java.net.UnknownHostException;
import java.io.InputStream;
import java.io.FileOutputStream;
import java.io.DataOutputStream;
import java.io.File;

import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.config.RequestConfig;
public class DownLoadFile {

	//根据URL和网页类型生成需要保存的网页的文件名,去除URL中的非文件名字符
	public String getFileNameByUrl(String url, String contentType) {
		//移除http://
		url=url.substring(7);	//返回从第7个到最后一个字符之间的子串
		//text/html类型
		if (contentType.indexOf("html") != -1) {	//如果是html类型的文本
			url=url.replaceAll("[\\?/:*|<>\"]", "_")+".html";
			return url;
		}
		else {	//如果不是html类型的文本
			return url.replaceAll("[\\?/:*|<>\"]","_")+"."+
				contentType.substring(contentType.lastIndexOf("/")+1);
		}
	}
	//保存网页字节数到本地文件,filepath为要保存文件的相对地址
	private void saveToLocal(HttpEntity entity, String filePath) {
		try {
			
			if(filePath.indexOf("JPG") != -1 || filePath.indexOf("png") != -1
					 || filePath.indexOf("jpeg") != -1) {
				File storeFile = new File(filePath);
				FileOutputStream output = new FileOutputStream(storeFile);

				// 得到网络资源的字节数组,并写入文件
				if (entity != null) {
					InputStream instream = entity.getContent();
					byte b[] = new byte[1024];
					int j = 0;
					while( (j = instream.read(b))!=-1){
						output.write(b,0,j);
						}
					}
				output.flush();
				output.close();
				return;
			}
			
			 if (entity != null) {
	                InputStream input = entity.getContent();
	                DataOutputStream output = new DataOutputStream(
	        				new FileOutputStream(new File(filePath)));
	 
	                int tempByte=-1;
	                while ((tempByte=input.read())>0) {
	                    output.write(tempByte);
	                }
	 
	                if (input != null) {
	                    input.close();
	                }
	 
	                if (output != null) {
	                    output.close();
	                }
	            }
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	//下载URL指定的网页
	public String downloadFile(String url) throws IOException {
		String filePath = null;
		
		//生成CloseableHttpClient对象并设置参数
		CloseableHttpClient httpclient = HttpClients.createDefault();
		

		
		//执行请求
		try {
			
			//生成GetMethod并设置参数
			HttpGet httpget = new HttpGet(url);
			
			//设置请求时间5秒钟
			RequestConfig requestCOnfig= RequestConfig.custom().setConnectTimeout(5000)
				.setConnectionRequestTimeout(1000).setSocketTimeout(5000).build();
			
			httpget.setConfig(requestConfig);
			
			CloseableHttpResponse respOnse= httpclient.execute(httpget);
			//判断返回状态
			int statusCode = response.getStatusLine().getStatusCode();
			
			//System.out.println("得到的结果:" + response.getStatusLine().getStatusCode());//得到请求结果  
			HttpEntity entity = response.getEntity();//得到请求回来的数据
			
			
			if (statusCode != HttpStatus.SC_OK) {
				System.err.println("Method failed: " + response.getStatusLine());
				//System.err.println("Method failed: " + getMethod.getStatusLine());
				filePath = null;
			}
			//处理HTTP响应内容
			// read byte array
			
			filePath = "D:\\temp\\"
					+ getFileNameByUrl(url, entity.getContentType().getValue());

			saveToLocal(entity, filePath);
		} catch (IllegalArgumentException e) {
			System.out.println("Illegal URL!");
		}
		
		catch (UnknownHostException e) {
			// fatal error
			System.out.println("Please check your provided http address!");
		} catch (IOException e) {
			// web error
			e.printStackTrace();
		} finally {
			// realease connection
			httpclient.close();
		}
		return filePath;
	}
	public static void main(String[] args) throws IOException {
		DownLoadFile a = new DownLoadFile();
		String tmp=null;
		tmp = a.downloadFile("http://www.lietu.com");
		System.out.println(tmp);
	}
}

HtmlParserTool类:对已获取的HTML页面进行处理,用来过滤链接,获得新的链接

package com.abc.bfs;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.htmlparser.Node;
import org.htmlparser.NodeFilter;
import org.htmlparser.Parser;
import org.htmlparser.filters.NodeClassFilter;
import org.htmlparser.filters.OrFilter;
import org.htmlparser.tags.LinkTag;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;



public class HtmlParserTool {
	//获取一个网站上的链接,filter用来过滤链接
	public static Set extractLinks(String url, LinkFilter filter) {
		Set links = new HashSet();
		try {
			Parser parser = new Parser(url);
			parser.setEncoding("utf-8");
			//过滤标签的filter,用来提取frame标签里的src属性
			NodeFilter frameFilter = new NodeFilter() {
				/**
				 * 
				 */
				private static final long serialVersiOnUID= 1L;

				public boolean accept(Node node) {
					if (node.getText().startsWith("frame src=")) {
						return true;
					}
					else {
						return false;
					}
				}
			};
			
			OrFilter linkFilter = new OrFilter(new NodeClassFilter(LinkTag.class), frameFilter);
			//得到所有经过过滤的标签
			NodeList list = parser.extractAllNodesThatMatch(linkFilter);
			for (int i=0; i");
					String frameUrl = frame.substring(5, end-1);
					if (filter.accept(frameUrl))
						links.add(frameUrl);
				}
			}
		} catch (ParserException e) {
			e.printStackTrace();
		}
		return links;
	}
	
	public static void main(String args[]) {
		System.out.println("This is a test for main function!");
		LinkFilter filter = new LinkFilter() {
			public boolean accept(String url) {
				if(url.startsWith("http://www.lietu.com"))
					return true;
				else
					return false;
			}
		};
		Set links = HtmlParserTool.extractLinks("http://www.lietu.com", filter);
		
		Iterator it = links.iterator();
		while (it.hasNext()) {
			String str = it.next();
			System.out.println(str);
		} 
	}
}

MyCrawler类:爬虫的主程序

package com.abc.bfs;
import java.io.IOException;
import java.util.Set;

public class MyCrawler {
	/**
	 * 使用种子初始化URL队列
	 * @return
	 * @param seeds 种子URL
	 */
	
	private void initCrawlerWithSeeds(String[] seeds) {
		for(int i=0; i links = HtmlParserTool.extractLinks(visitUrl, filter);
			//新的未访问的URL入队
			for(String link:links) {
				LinkQueue.addUnvisitedUrl(link);
			}
		}
	}
	
	//main入口方法
	public static void main(String args[]) throws IOException {
		MyCrawler crawler = new MyCrawler();
		crawler.crawling(new String[]{"http://www.lietu.com"});
		System.out.println("爬完了\n");
	}
}

接口LinkFilter:对解析出来的URL进行过滤,什么样的url要,什么样的不要

package com.abc.bfs;

public interface LinkFilter {
	public boolean accept(String url);
}

小结:中间还改了一点东西,可以下一些jpeg或者bmp的图片了,感觉还是蛮有意思的

爬虫运行:

技术分享

要把java学的更好,爬取页面只是进行挖掘的第一步,后的应该是还要进一步学习与页面解析有关的,提取到更多的有用信息,然后放到数据库中

接下来什么hadoop,spark都拿过来用下

任我来挖掘吧,挖掘技术哪家强,反正现在我不强,嘻嘻

实测可用的宽度优先爬虫的实现


推荐阅读
  • 本文详细介绍了Linux系统中用于管理IPC(Inter-Process Communication)资源的两个重要命令:ipcs和ipcrm。通过这些命令,用户可以查看和删除系统中的消息队列、共享内存和信号量。 ... [详细]
  • 蒜头君的倒水问题(矩阵快速幂优化)
    蒜头君将两杯热水分别倒入两个杯子中,每杯水的初始量分别为a毫升和b毫升。为了使水冷却,蒜头君采用了一种特殊的方式,即每次将第一杯中的x%的水倒入第二杯,同时将第二杯中的y%的水倒入第一杯。这种操作会重复进行k次,最终求出两杯水中各自的水量。 ... [详细]
  • 经过一年的思考,我发现自己对开发的兴趣并不浓厚,而对算法研究则更加热衷。本文将探讨开发与算法之间的本质差异,并分享我的未来学习计划。 ... [详细]
  • 本文介绍了Java编程语言的基础知识,包括其历史背景、主要特性以及如何安装和配置JDK。此外,还详细讲解了如何编写和运行第一个Java程序,并简要介绍了Eclipse集成开发环境的安装和使用。 ... [详细]
  • Bootstrap 缩略图展示示例
    本文将展示如何使用 Bootstrap 实现缩略图效果,并提供详细的代码示例。 ... [详细]
  • 本文介绍了一种支付平台异步风控系统的架构模型,旨在为开发类似系统的工程师提供参考。 ... [详细]
  • malloc 是 C 语言中的一个标准库函数,全称为 memory allocation,即动态内存分配。它用于在程序运行时申请一块指定大小的连续内存区域,并返回该区域的起始地址。当无法预先确定内存的具体位置时,可以通过 malloc 动态分配内存。 ... [详细]
  • 本文介绍了多种开源数据库及其核心数据结构和算法,包括MySQL的B+树、MVCC和WAL,MongoDB的tokuDB和cola,boltDB的追加仅树和mmap,levelDB的LSM树,以及内存缓存中的一致性哈希。 ... [详细]
  • 网络爬虫的规范与限制
    本文探讨了网络爬虫引发的问题及其解决方案,重点介绍了Robots协议的作用和使用方法,旨在为网络爬虫的合理使用提供指导。 ... [详细]
  • 本文介绍了 AngularJS 中的 $compile 服务及其用法,通过示例代码展示了如何使用 $compile 动态编译和链接 HTML 元素。 ... [详细]
  • [c++基础]STL
    cppfig15_10.cppincludeincludeusingnamespacestd;templatevoidprintVector(constvector&integer ... [详细]
  • ZooKeeper 入门指南
    本文将详细介绍ZooKeeper的工作机制、特点、数据结构以及常见的应用场景,包括统一命名服务、统一配置管理、统一集群管理、服务器动态上下线和软负载均衡。 ... [详细]
  • 自动验证时页面显示问题的解决方法
    在使用自动验证功能时,页面未能正确显示错误信息。通过使用 `dump($info->getError())` 可以帮助诊断和解决问题。 ... [详细]
  • 本文详细介绍了如何解决DNS服务器配置转发无法解析的问题,包括编辑主配置文件和重启域名服务的具体步骤。 ... [详细]
  • 数字资产量化交易通过大数据分析,以客观的方式制定交易决策,有效减少人为的主观判断和情绪影响。本文介绍了几种常见的数字资产量化交易策略,包括搬砖套利和趋势交易,并探讨了量化交易软件的开发前景。 ... [详细]
author-avatar
手机用户2502904377
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有