欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

教你如何编写简单的网络爬虫

程序员文章站 2023-12-21 14:40:40
一、网络爬虫的基本知识 网络爬虫通过遍历互联网络,把网络中的相关网页全部抓取过来,这体现了爬的概念。爬虫如何遍历网络呢,互联网可以看做是一张大图,每个页面看做其中的一个节...

一、网络爬虫的基本知识

网络爬虫通过遍历互联网络,把网络中的相关网页全部抓取过来,这体现了爬的概念。爬虫如何遍历网络呢,互联网可以看做是一张大图,每个页面看做其中的一个节点,页面的连接看做是有向边。图的遍历方式分为宽度遍历和深度遍历,但是深度遍历可能会在深度上过深的遍历或者陷入黑洞。所以,大多数爬虫不采用这种形式。另一方面,爬虫在按照宽度优先遍历的方式时候,会给待遍历的网页赋予一定优先级,这种叫做带偏好的遍历。

实际的爬虫是从一系列的种子链接开始。种子链接是起始节点,种子页面的超链接指向的页面是子节点(中间节点),对于非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 {
 // 已访问的 url 集合
 private static set visitedurl = new hashset();
 // 待访问的 url 集合
 private static queue unvisitedurl = new priorityqueue();

 // 获得url队列
 public static queue getunvisitedurl() {
  return unvisitedurl;
 }

 // 添加到访问过的url队列中
 public static void addvisitedurl(string url) {
  visitedurl.add(url);
 }

 // 移除访问过的url
 public static void removevisitedurl(string url) {
  visitedurl.remove(url);
 }

 // 未访问的url出队列
 public static object unvisitedurldequeue() {
  return unvisitedurl.poll();
 }

 // 保证每个 url 只被访问一次
 public static void addunvisitedurl(string url) {
  if (url != null && !url.trim().equals("") && !visitedurl.contains(url)
    && !unvisitedurl.contains(url))
   unvisitedurl.add(url);
 }

 // 获得已经访问的url数目
 public static int getvisitedurlnum() {
  return visitedurl.size();
 }

 // 判断未访问的url队列中是否为空
 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) {
  // remove http://
  url = url.substring(7);
  // text/html类型
  if (contenttype.indexof("html") != -1) {
   url = url.replaceall("[\\?/:*|<>\"]", "_") + ".html";
   return url;
  }
  // 如application/pdf类型
  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();
  }
 }

 /* 下载 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 = "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 {
 // 获取一个网站上的链接,filter 用来过滤链接
 public static set<string> extraclinks(string url, linkfilter filter) {

  set<string> links = new hashset<string>();
  try {
   parser parser = new parser(url);
   //parser.setencoding("utf-8");
   // 过滤 <frame >标签的 filter,用来提取 frame 标签里的 src 属性所表示的链接
   nodefilter framefilter = new nodefilter() {
    public boolean accept(node node) {
     if (node.gettext().startswith("frame src=")) {
      return true;
     } else {
      return false;
     }
    }
   };
   // orfilter 来设置过滤 <a> 标签,和 <frame> 标签
   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)// <a> 标签
    {
     linktag link = (linktag) tag;
     string linkurl = link.getlink();// url
     if (filter.accept(linkurl))
      links.add(linkurl);
    } else// <frame> 标签
    {
     // 提取 frame 里 src 属性的链接如 <frame src="test.html"/>
     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) { // 定义过滤器,提取以http://www.lietu.com开头的链接
  linkfilter filter = new linkfilter() {
   public boolean accept(string url) {
    if (url.startswith("http://www.baidu.com"))
     return true;
    else
     return false;
   }
  };
  // 初始化 url 队列
  initcrawlerwithseeds(seeds);
  // 循环条件:待抓取的链接不空且抓取的网页不多于1000
  while (!linkqueue.unvisitedurlsempty()
    && linkqueue.getvisitedurlnum() <= 1000) {
   // 队头url出队列
   string visiturl = (string) linkqueue.unvisitedurldequeue();
   if (visiturl == null)
    continue;
   downloadfile downloader = new downloadfile();
   // 下载网页
   downloader.downloadfile(visiturl);
   // 该 url 放入到已访问的 url 中
   linkqueue.addvisitedurl(visiturl);
   // 提取出下载网页中的 url
   set<string> links = htmlparsertool.extraclinks(visiturl, filter);
   // 新的未访问的 url 入队
   for (string link : links) {
    linkqueue.addunvisitedurl(link);
   }
  }
 }

 // main 方法入口
 public static void main(string[] args) {
  mycrawler crawler = new mycrawler();
  crawler.crawling(new string[] { "http://www.baidu.com" });
 }

}


至此,可以看到f:\spider文件夹下面已经出现了很多html文件,都是关于百度的,以“www.baidu.com”为开头。

上一篇:

下一篇: