实际的爬虫是从一系列的种子链接开始。种子链接是起始节点,种子页面的超链接指向的页面是子节点(中间节点),对于非html文档,如excel等,不能从中提取超链接,看做图的终端节点
网络爬虫的基本知识
网络爬虫通过遍历互联网络,把网络中的相关网页全部抓取过来,这体现了爬的概念。爬虫如何遍历网络呢,互联网可以看做是一张大图,每个页面看做其中的一个节点,页面的连接看做是有向边。图的遍历方式分为宽度遍历和深度遍历,但是深度遍历可能会在深度上过深的遍历或者陷入黑洞。所以,大多数爬虫不采用这种形式。另一方面,爬虫在按照宽度优先遍历的方式时候,会给待遍历的网页赋予一定优先级,这种叫做带偏好的遍历。
实际的爬虫是从一系列的种子链接开始。种子链接是起始节点,种子页面的超链接指向的页面是子节点(中间节点),对于非html文档,如excel等,不能从中提取超链接,看做图的终端节点。整个遍历过程中维护一张visited表,记录哪些节点(链接)已经处理过了,跳过不作处理。
使用宽度优先搜索策略,主要原因有:
a、重要的网页一般离种子比较近,例如我们打开的新闻网站时候,往往是最热门的新闻,随着深入冲浪,网页的重要性越来越低。
b、万维网实际深度最多达17层,但到达某个网页总存在一条很短路径,而宽度优先遍历可以最快的速度找到这个网页
c、宽度优先有利于多爬虫合作抓取。
网络爬虫的简单实现
代码预览
1、定义已访问队列,待访问队列和爬取得URL的哈希表,包括出队列,入队列,判断队列是否空等操作
代码如下:
package webspider; import java.util.HashSet; import java.util.PriorityQueue; import java.util.Set; import java.util.Queue;
public class LinkQueue { private static Set visitedUrl = new HashSet(); private static Queue unVisitedUrl = new PriorityQueue();
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 Object unVisitedUrlDeQueue() { return unVisitedUrl.poll(); }
public static void addUnvisitedUrl(String url) { if (url != null && !url.trim().equals("") && !visitedUrl.contains(url) && !unVisitedUrl.contains(url)) unVisitedUrl.add(url); }
public static int getVisitedUrlNum() { return visitedUrl.size(); }
public static boolean unVisitedUrlsEmpty() { return unVisitedUrl.isEmpty(); }
}
|
2、定义DownLoadFile类,根据得到的url,爬取网页内容,下载到本地保存。此处需要引用
commons-httpclient.jar,commons-codec.jar,commons-logging.jar。
代码如下:
package webspider;
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 DownLoadFile { * 根据 url 和网页类型生成需要保存的网页的文件名 去除掉 url 中非文件名字符 */ public String getFileNameByUrl(String url, String contentType) { url = url.substring(7); if (contentType.indexOf("html") != -1) { url = url.replaceAll("[\\?/:*|<>\"]", "_") + ".html"; return url; } else { 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 < data.length; i++) out.write(data[i]); out.flush(); out.close(); } catch (IOException e) { e.printStackTrace(); } }
public String downloadFile(String url) { String filePath = null; HttpClient httpClient = new HttpClient(); httpClient.getHttpConnectionManager().getParams() .setConnectionTimeout(5000);
GetMethod getMethod = new GetMethod(url); getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 5000); getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler());
try { int statusCode = httpClient.executeMethod(getMethod); if (statusCode != HttpStatus.SC_OK) { System.err.println("Method failed: " + getMethod.getStatusLine()); filePath = null; }
byte[] responseBody = getMethod.getResponseBody(); filePath = "f:\\spider\\" + 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; } }
|
3、定义HtmlParserTool类,用来获得网页中的超链接(包括a标签,frame中的src等等),即为了得到子节点的URL。需要引入
htmlparser.jar 代码如下:
package webspider;
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 { public static Set<String> extracLinks(String url, LinkFilter filter) {
Set<String> links = new HashSet<String>(); try { Parser parser = new Parser(url); NodeFilter frameFilter = new NodeFilter() { 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 < list.size(); i++) { Node tag = list.elementAt(i); if (tag instanceof LinkTag) { LinkTag link = (LinkTag) tag; String linkUrl = link.getLink(); if (filter.accept(linkUrl)) links.add(linkUrl); } else { 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; } }
|
4、编写测试类MyCrawler,用来测试爬取效果
代码如下:
package webspider;
import java.util.Set;
public class MyCrawler { * 使用种子初始化 URL 队列 * * @return * @param seeds * 种子URL */ private void initCrawlerWithSeeds(String[] seeds) { for (int i = 0; i < seeds.length; i++) LinkQueue.addUnvisitedUrl(seeds[i]); }
* 抓取过程 * * @return * @param seeds */ public void crawling(String[] seeds) { LinkFilter filter = new LinkFilter() { public boolean accept(String url) { if (url.startsWith("http://www.baidu.com")) return true; else return false; } }; initCrawlerWithSeeds(seeds); while (!LinkQueue.unVisitedUrlsEmpty() && LinkQueue.getVisitedUrlNum() <= 1000) { String visitUrl = (String) LinkQueue.unVisitedUrlDeQueue(); if (visitUrl == null) continue; DownLoadFile downLoader = new DownLoadFile(); downLoader.downloadFile(visitUrl); LinkQueue.addVisitedUrl(visitUrl); Set<String> links = HtmlParserTool.extracLinks(visitUrl, filter); for (String link : links) { LinkQueue.addUnvisitedUrl(link); } } }
public static void main(String[] args) { MyCrawler crawler = new MyCrawler(); crawler.crawling(new String[] { "http://www.baidu.com" }); }
}
|
5.linkfiter接口
package webspider;
public interface LinkFilter { public boolean accept(String url); }
|
至此,可以看到f:\spider文件夹下面已经出现了很多html文件,都是关于百度的,以“www.baidu.com”为开头。