java urlparser_HtmlParser + HttpClient 实现爬虫

本文介绍了如何使用Java的HttpClient获取网页源码,结合HtmlParser解析并提取超链接,实现一个简单的网络爬虫。爬虫采用了宽度优先的策略,从种子URL开始,过滤并下载以特定域名开头的网页。
摘要由CSDN通过智能技术生成

简易爬虫的实现

HttpClient 提供了便利的 HTTP 协议访问,使得我们可以很容易的得到某个网页的源码并保存在本地;HtmlParser 提供了如此简便灵巧的类库,可以从网页中便捷的提取出指向其他网页的超链接。笔者结合这两个开源包,构建了一个简易的网络爬虫。

爬虫 (Crawler) 原理

学过数据结构的读者都知道有向图这种数据结构。如下图所示,如果将网页看成是图中的某一个节点,而将网页中指向其他网页的链接看成是这个节点指向其他节点的边,那么我们很容易将整个 Internet 上的网页建模成一个有向图。理论上,通过遍历算法遍历该图,可以访问到Internet 上的几乎所有的网页。最简单的遍历就是宽度优先以及深度优先。以下笔者实现的简易爬虫就是使用了宽度优先的爬行策略。

图 2. 网页关系的建模图

ee12ddeca35de3fca7fd9115779c3876.png

简易爬虫实现流程

在看简易爬虫的实现代码之前,先介绍一下简易爬虫爬取网页的流程。

图 3. 爬虫流程图

8ab9597bf361128c9ec8c6ee8756aac7.png

各个类的源码以及说明

对应上面的流程图,简易爬虫由下面几个类组成,各个类职责如下:

Crawler.java:爬虫的主方法入口所在的类,实现爬取的主要流程。

LinkDb.java:用来保存已经访问的 url 和待爬取的 url 的类,提供url出对入队操作。

Queue.java: 实现了一个简单的队列,在 LinkDb.java 中使用了此类。

FileDownloader.java:用来下载 url 所指向的网页。

HtmlParserTool.java: 用来抽取出网页中的链接。

LinkFilter.java:一个接口,实现其 accept() 方法用来对抽取的链接进行过滤。

下面是各个类的源码,代码中的注释有比较详细的说明。

清单6 Crawler.java

package com.ie;

import java.util.Set;

public class Crawler {

/* 使用种子 url 初始化 URL 队列*/

private void initCrawlerWithSeeds(String[] seeds)

{

for(int i=0;i

LinkDB.addUnvisitedUrl(seeds[i]);

}

/* 爬取方法*/

public void crawling(String[] seeds)

{

LinkFilter filter = new LinkFilter(){

//提取以 http://www.twt.edu.cn 开头的链接

public boolean accept(String url) {

if(url.startsWith("http://www.twt.edu.cn"))

return true;

else

return false;

}

};

//初始化 URL 队列

initCrawlerWithSeeds(seeds);

//循环条件:待抓取的链接不空且抓取的网页不多于 1000

while(!LinkDB.unVisitedUrlsEmpty()&&LinkDB.getVisitedUrlNum()<=1000)

{

//队头 URL 出对

String visitUrl=LinkDB.unVisitedUrlDeQueue();

if(visitUrl==null)

continue;

FileDownLoader downLoader=new FileDownLoader();

//下载网页

downLoader.downloadFile(visitUrl);

//该 url 放入到已访问的 URL 中

LinkDB.addVisitedUrl(visitUrl);

//提取出下载网页中的 URL

Set links=HtmlParserTool.extracLinks(visitUrl,filter);

//新的未访问的 URL 入队

for(String link:links)

{

LinkDB.addUnvisitedUrl(link);

}

}

}

//main 方法入口

public static void main(String[]args)

{

Crawler crawler = new Crawler();

crawler.crawling(new String[]{"http://www.twt.edu.cn"});

}

}

清单7 LinkDb.java

package com.ie;

import java.util.HashSet;

import java.util.Set;

/**

* 用来保存已经访问过 Url 和待访问的 Url 的类

*/

public class LinkDB {

//已访问的 url 集合

private static Set visitedUrl = new HashSet();

//待访问的 url 集合

private static Queue unVisitedUrl = new Queue();

public static Queue getUnVisitedUrl() {

return unVisitedUrl;

}

public static void addVisitedUrl(String url) {

visitedUrl.add(url);

}

public static void removeVisitedUrl(String url) {

visitedUrl.remove(url);

}

public static String unVisitedUrlDeQueue() {

return unVisitedUrl.deQueue();

}

// 保证每个 url 只被访问一次

public static void addUnvisitedUrl(String url) {

if (url != null && !url.trim().equals("")

&& !visitedUrl.contains(url)

&& !unVisitedUrl.contians(url))

unVisitedUrl.enQueue(url);

}

public static int getVisitedUrlNum() {

return visitedUrl.size();

}

public static boolean unVisitedUrlsEmpty() {

return unVisitedUrl.empty();

}

}

清单8 Queue.java

package com.ie;

import java.util.LinkedList;

/**

* 数据结构队列

*/

public class Queue {

private LinkedList queue=new LinkedList();

public void enQueue(T t)

{

queue.addLast(t);

}

public T deQueue()

{

return queue.removeFirst();

}

public boolean isQueueEmpty()

{

return queue.isEmpty();

}

public boolean contians(T t)

{

return queue.contains(t);

}

public boolean empty()

{

return queue.isEmpty();

}

}

清单 9 FileDownLoader.java

package com.ie;

import java.io.DataOutputStream;

import java.io.File;

import java.io.FileOutputStream;

import java.io.IOException;

import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;

import org.apache.commons.httpclient.HttpClient;

import org.apache.commons.httpclient.HttpException;

import org.apache.commons.httpclient.HttpStatus;

import org.apache.commons.httpclient.methods.GetMethod;

import org.apache.commons.httpclient.params.HttpMethodParams;

public class FileDownLoader {

/**根据 url 和网页类型生成需要保存的网页的文件名

*去除掉 url 中非文件名字符

*/

public String getFileNameByUrl(String url,String contentType)

{

url=url.substring(7);//remove http://

if(contentType.indexOf("html")!=-1)//text/html

{

url= url.replaceAll("[\\?/:*|<>\"]", "_")+".html";

return url;

}

else//如application/pdf

{

return url.replaceAll("[\\?/:*|<>\"]", "_")+"."+ \

contentType.substring(contentType.lastIndexOf("/")+1);

}

}

/**保存网页字节数组到本地文件

* filePath 为要保存的文件的相对地址

*/

private void saveToLocal(byte[] data,String filePath)

{

try {

DataOutputStream out=new DataOutputStream(

new FileOutputStream(new File(filePath)));

for(int i=0;i

out.write(data[i]);

out.flush();

out.close();

} catch (IOException e) {

e.printStackTrace();

}

}

/*下载 url 指向的网页*/

public String downloadFile(String url)

{

String filePath=null;

/* 1.生成 HttpClinet 对象并设置参数*/

HttpClient httpClient=new HttpClient();

//设置 Http 连接超时 5s

httpClient.getHttpConnectionManager().getParams().

setConnectionTimeout(5000);

/*2.生成 GetMethod 对象并设置参数*/

GetMethod getMethod=new GetMethod(url);

//设置 get 请求超时 5s

getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT,5000);

//设置请求重试处理

getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,

new DefaultHttpMethodRetryHandler());

/*3.执行 HTTP GET 请求*/

try{

int statusCode = httpClient.executeMethod(getMethod);

//判断访问的状态码

if (statusCode != HttpStatus.SC_OK)

{

System.err.println("Method failed: "+ getMethod.getStatusLine());

filePath=null;

}

/*4.处理 HTTP 响应内容*/

byte[] responseBody = getMethod.getResponseBody();//读取为字节数组

//根据网页 url 生成保存时的文件名

filePath="temp\\"+getFileNameByUrl(url,

getMethod.getResponseHeader("Content-Type").getValue());

saveToLocal(responseBody,filePath);

} catch (HttpException e) {

// 发生致命的异常,可能是协议不对或者返回的内容有问题

System.out.println("Please check your provided http

address!");

e.printStackTrace();

} catch (IOException e) {

// 发生网络异常

e.printStackTrace();

} finally {

// 释放连接

getMethod.releaseConnection();

}

return filePath;

}

//测试的 main 方法

public static void main(String[]args)

{

FileDownLoader downLoader = new FileDownLoader();

downLoader.downloadFile("http://www.twt.edu.cn");

}

}

清单 10 HtmlParserTool.java

package com.ie;

import java.util.HashSet;

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 extracLinks(String url,LinkFilter filter) {

Set links = new HashSet();

try {

Parser parser = new Parser(url);

parser.setEncoding("gb2312");

// 过滤 标签的 filter,用来提取 frame 标签里的 src 属性所表示的链接

NodeFilter frameFilter = new NodeFilter() {

public boolean accept(Node node) {

if (node.getText().startsWith("frame src=")) {

return true;

} else {

return false;

}

}

};

// OrFilter 来设置过滤 标签,和 标签

OrFilter linkFilter = new OrFilter(new NodeClassFilter(

LinkTag.class), frameFilter);

// 得到所有经过过滤的标签

NodeList list = parser.extractAllNodesThatMatch(linkFilter);

for (int i = 0; i < list.size(); i++) {

Node tag = list.elementAt(i);

if (tag instanceof LinkTag)// 标签

{

LinkTag link = (LinkTag) tag;

String linkUrl = link.getLink();// url

if(filter.accept(linkUrl))

links.add(linkUrl);

} else// 标签

{

// 提取 frame 里 src 属性的链接如

String frame = tag.getText();

int start = frame.indexOf("src=");

frame = frame.substring(start);

int end = frame.indexOf(" ");

if (end == -1)

end = frame.indexOf(">");

String frameUrl = frame.substring(5, end - 1);

if(filter.accept(frameUrl))

links.add(frameUrl);

}

}

} catch (ParserException e) {

e.printStackTrace();

}

return links;

}

//测试的 main 方法

public static void main(String[]args)

{

Set links = HtmlParserTool.extracLinks(

"http://www.twt.edu.cn",new LinkFilter()

{

//提取以 http://www.twt.edu.cn 开头的链接

public boolean accept(String url) {

if(url.startsWith("http://www.twt.edu.cn"))

return true;

else

return false;

}

});

for(String link : links)

System.out.println(link);

}

}

清单11 LinkFilter.java

package com.ie;

public interface LinkFilter {

public boolean accept(String url);

}

这些代码中关键的部分都在 HttpClient 和 HtmlParser 介绍中说明过了。

HtmlParse,解析给定url中的中文字符,输出到文本文件中: url:可配置多个 输出路径:可配置 package com.lhs; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Properties; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 现在要求用Java编写一个程序,该程序访问上面的URL,并从页面中, * 提取出上图所示位置的页面信息(仅图中所要求的内容),将其内容按如下格式,输出到控制台 * GrapWeatherInfo * @author lihsh * @version 1.0 * */ public class HtmlParse { List configList = new ArrayList(); private String savePath = "d:\\htmlParse.txt"; private String reg = "[\u4E00-\u9FA5]+"; Set resultSet = new LinkedHashSet(); /** * @param args */ public static void main(String[] args) { HtmlParse hp = new HtmlParse(); hp.getConfig(); hp.start(); hp.write2file(); } /** * 获得配置文件,得到公司要求的文件类型 */ private void getConfig() { Properties props = new Properties(); InputStream in = getClass().getResourceAsStream("/config.properties"); try { props.load(in); Enumeration en = props.propertyNames(); System.out.print("读取配置文件:"); while(en.hasMoreElements()) { String key = (String) en.nextElement(); String value = (String) props.get(key); if(key.startsWith("url")) { configList.add(value); }else if(key.equals("savePath")) { savePath = value; }else if(key.equals("reg")) { reg = value; } System.out.print(key + ":" + value +"; "); } System.out.println(); } catch (IOException e) { e.printStackTrace(); System.out.println("读取配置文件/config.properties出错"); } } /** * 程序总入口 */ private void start() { for(int i = 0; i < configList.size(); i++) { URLConnection con = getConnection(configList.get(i)); readContent(con); System.out.println("读取:" + configList.get(i) + " 结束"); } } /** * 获取url链接 * @return 链接 */ private URLConnection getConnection(String _url) { URLConnection con = null; URL url = null; try { url = new URL(_url); con=url.openConnection(); } catch (IOException e) { e.printStackTrace(); } return con; } /** * 初步过滤出含有天气的行 * @param con url链接 * @return 关键行 */ private void readContent(URLConnection con) { BufferedReader br=null; BufferedWriter bw = null; try { br = new BufferedReader(new InputStreamReader(con.getInputStream(),"UTF-8")); bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(savePath)), "UTF-8")); String line=""; while((line=br.readLine()) != null) { resultSet.addAll(parse(line)); } bw.flush(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e1) { e1.printStackTrace(); } catch (IOException e1) { e1.printStackTrace(); }finally { try { bw.close(); br.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * 正则表达式匹配关键数据 * @param line * @return */ private Set parse(String line) { Set resSet = new LinkedHashSet(); Pattern pattern = Pattern.compile(reg); Matcher matcher = pattern.matcher(line); while(matcher.find()) { String group = matcher.group(); resSet.add(group); } return resSet; } private void write2file() { BufferedWriter bw = null; try { bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(savePath)), "UTF-8")); for(Iterator it = resultSet.iterator(); it.hasNext();) { bw.write(it.next()); bw.newLine(); } bw.flush(); System.out.println("解析结果保存至:" + savePath); } catch (IOException e1) { e1.printStackTrace(); }finally { try { bw.close(); } catch (IOException e) { e.printStackTrace(); } } } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值