simple webcrawler 初步理解

package com.user.simpleCrawlerTest;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SimpleCrawler {
    /*
     * public interface List<E> extends Collection<E>用此接口的类,用户可以对列表中的每个
     * 元素的插入位置进行精确地控制。
     * public class ArrayList<E> extends AbstractList<E> implements List<E>,
     * Randomaccess, Cloneable, Serializable 实现List接口的可变大小的数组。实现了所有可选列表
     * 操作,并允许包括null在内的所有元素。随着想ArrayList中不断添加元素u,其容量也自动增长。
     * ?此实现不是同步的。如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了
     * 列表,那么它必须保持外部同步。这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在
     * 这样的对象,则应该使用Collections.synchronizedList方法将该列表"包装"起来。这最好在创建
     * 时完成,以防止对列表进行不同步的访问:List list = Collections.synchronizedLis(new ArrayList(...));
     */
    List<String> findUrls = new ArrayList<String>();// 所有发现的URL队列
    List<String> pendUrls = new ArrayList<String>();// 待处理的URL队列
    /*
     * public class Hashtable<K, V> extends Dictionary<K, V> implements Map<K, V>, 
     * Cloneable, Serializable 此类实现一个哈希表,该哈希表将键映射到相应的值。任何非null对象
     * 都可以用作键或值
     */
    Hashtable<String, Integer> deepUrls = new Hashtable<String, Integer>();// 存储所有URL深度

    String HomePage;// 主页
    int webDepth = 3;// 搜索深度
    // Domain领域,域名
    private String myDomain;

    public static void main(String[] args)
    {
        SimpleCrawler nb = new SimpleCrawler();
        nb.HomePage = "http://www.sina.com.cn/";// 设置种子URL
        nb.startHmpg();
    }
    // 让此方法同步,多个线程中,同时且只有一个可以运行这个方法,一个线程运行完成后其他进程才能运行
    public synchronized String getAUrl()
    {
        String tmpAUrl = pendUrls.get(0);// 得到还没有被处理的URL
        /*
         * E remove(int index)移除列表中指定位置的元素(可选操作)。将所有的后续元素向左移动(将
         * 其索引减1)。返回从列表中移除的元素。
         */
        pendUrls.remove(0);//移除一个被处理的URL
        return tmpAUrl;
    }

    public String getDomain()
    {
        // 正则表达式表示匹配域名
        // 匹配以http://(字母或数字0到100个,中间有0到1个.)开头的。后面是非点和非任何不可见字符(0到多个(懒惰模式))最后是一个.和(com|cn|net|org|biz|info|cc|tv)中的其中一个
        String reg = "(?<=http\\://[a-zA-Z0-9]{0,100}[.]{0,1})[^.\\s]*?\\.(com|cn|net|org|biz|info|cc|tv)";
        /*
         * public final class Pattern extends Object implements Serializable 正则
         * 表达式的编译表示形式
         * 指定为字符串的正则表达式必须首先编译为此类的实例。然后,可将得到的模式用于创建Matcher
         * 对象,依照正则表达式,该对象可以与任意字符序列匹配。执行匹配所涉及的所有状态都驻留在
         * 匹配器中,所以多个匹配器可以共享同一模式
         * public static Pattern compile(String regex, int flags)将给定的正则表达式编译到具有
         * 给定标志的模式中 regex - 要编译的表达式 flags - 匹配标志
         * public  static final int CASE_INSENSITIVE 启用不区分大小写的匹配,默认情况下不区分
         * 大小写的匹配,假定仅匹配US-ASCII字符集中的字符
         */
        Pattern p = Pattern.compile(reg, Pattern.CASE_INSENSITIVE);
        /*
         * public Matcher matcher(CharSequence input) 创建匹配给定输入与此模式的匹配器
         */
        /*
         * public final class Matcher extends Object implements MatchResult 通过解释
         * Pattern对character sequence执行匹配操作的引擎。通过调用模式的matcher方法从模式创建
         * 匹配器。创建匹配器后,可以使用它执行三种不同的匹配操作:
         * matches方法尝试将整个输入序列与该模式匹配。
         * lookingAt尝试将输入序列从头开始与该模式匹配。(?模式指的是正则表达式)
         * find方法扫描输入序列以查找与该模式匹配的下一个子序列
         * 每个方法都返回一个表示成功或失败的布尔值。通过查询匹配器的状态可以获取关于成功匹配的
         * 更多信息。
         * public boolean find()尝试查找与该模式(正则表达式)匹配的输入序列的下一个?子序列
         * 此方法从匹配器区域的开头开始,如果该方法的前一次调用成功了并且从那时开始匹配器没有被重置,
         * 则从以前匹配器操作没有匹配的第一个字符开始。
         * 如果匹配成功,则可以通过start、end和group方法获取更多信息。
         */
        Matcher m = p.matcher(HomePage);
        // 使用find()查找之后如果返回false(之后都没有匹配),则重置匹配器m
        // 如果放回true这保留此时m.end()返回的索引,等待下次调用find()时从此处开始查找
        boolean blnp = m.find();
        if(blnp == true)
        {
            System.out.println("check: " + m.group(0));
            return m.group(0);
        }

        return null;
    }

    public void startHmpg()
    {   // myDomain = sina.com
        this.myDomain = getDomain();
        this.myDomain = replaceRegexSignAll(this.myDomain);

        findUrls.add(HomePage);
        pendUrls.add(HomePage);
        deepUrls.put(HomePage, 1);
        // 从待处理队列取出一个URL字符串
        String tmp = getAUrl();
        /*
         * 类URL代表一个统一资源定位符,它是指向互联网"资源"的指针。资源可以是简单的文件或目录,
         * 也可以是更为复杂的对象的引用,例如数据库或搜索引擎的查询。
         */
        URL url = null;
        try
        {
            // 第一次:此时tmp是:http://www.sina.com.cn/
            url = new URL(tmp);
        }
        catch(MalformedURLException e)
        {
            e.printStackTrace();
        }

        // 根据规则进行URL的抓取
        this.findUrlFromHtml(url);
        int i = 0;
        /*
         * 开启八个线程
         */
        for(i = 0; i < 8; i++)
        {
            new Thread(new Processer(this)).start();// 启动8线程来进行抓取
        }
    }

    public void findUrlFromHtml(URL url)
    {
        // 用正则表达式识别href属性中的URL, 补全域名前缀
        /*
         * 开头是href=,接着是有0个或1个"和0个或1个',然后是http://,后面加上任意多个不是不可见字符
         * 或"或'或\或?,后面是myDomain变量的内容,然后是任意多个非不可见字符或"或'或>
         */
        String ptn = "(?<=(href=)[\"]?[\']?)(http://)[^\\s\"\'\\?]*("
                + this.myDomain + ")[^\\s\"\'>]*";
        // Pattern.CASE_INSENSITIVE 启动不区分大小写模式
        Pattern p = Pattern.compile(ptn, Pattern.CASE_INSENSITIVE);
        /*
         * public class BufferedReader extends Reader从字符输入流中读取文本,缓冲各个字符,
         * 从而实现字符、数组和行的高效读取。
         */
        BufferedReader br = null;
        /*
         * public 
         */
        HttpURLConnection conn;
        try
        {
            // 使用实验一,  通过JDK提供的API进行页面URL读取
            /*
             * public URLConnection openConnection() throws IOException
             * 返回一个URLConnection对象,表示到URL所引用的远程对象的连接。
             * 每次调用此URL的协议处理程序的openConnection方法都打开一个新的连接
             */
            /*
             * public abstract class URLConnection extends Object
             * 抽象类URLConnection是所有类的超类,它代表应用程序和URL之间的通信链接。此类的
             * 实例可用于读取和写入此URL引用的资源
             */
            /*
             * public abstract class HttpURLConnection extends URLConnection
             * 支持HTTP特定功能的URLConnection。每个HttpURLConnection实例都可用与生成单个请求,
             * 但是其他实例可以透明地共享连接到HTTP服务器的基础网络。请求后再HttpURLConnection的
             * InputStream或OutputStream上调用close()方法可以释放与此实例关联的网络资源,
             * 但对共享的持久连接没有任何影响。如果在调用disconnect()时持久连接空闲,则可能关闭
             * 基础套接字
             */
            conn = (HttpURLConnection) url.openConnection();
            /*
             * public InputStream getInputStream() throws IOException
             * 返回从此打开的链接读取的输入流。在读取返回的输入流时,如果在数据可供读取之前达到
             * 读入超时时间,则会抛出SocketTimeoutException
             */
            br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;
            /*
             * 读取页面中的数据一行,也就是一行一行的读取。
             */
            while((line = br.readLine()) != null)
            {
                /*
                 * 将字符串用正则表达式匹配
                 */
                Matcher m = p.matcher(line);
                /*
                 * 查看此页面的深度是否大于预设的深度(此时是3),如果大于或等于则退出
                 * 其实这个if语句可以放到while()语句的上一层
                 */
                if(deepUrls.get(url.toString()) < webDepth)
                {
                    /*
                     * 给Pattern.matcher()一段很长的字符串,用Matcher.find()来查找的时候,
                     * 当find()一次就会根据模式(正则表达式)查找,一查找到就返回true,此时
                     * 才查找到字符串的一半位置,这时Matcher匹配器会刚刚查找的字符串的最后一个
                     * 字符的索引后面一个位子留下指针。等待下一次find()的时候从这个指针开始查
                     * 找起。如果Matcher发现指针最后是留在了字符串的最末一个字符索引的后
                     * 一位,就会重置指针。此时如果find()的话会重新开始扫描字符串
                     */
                    while(m.find())
                    {
                        /*
                         * boolean contains(Object o) 如果列表包含指定的元素,则返回true。更
                         * 确切的讲,当且仅当列表包含满足(o == null ? e == null : o.equals(e))
                         * 的元素e时才返回true。指定者:接口Conllection<E>中的contains
                         */
                        /*
                         * deepUrls为3
                         * 种子网子是http://www.sina.com.cn/。此时检测这个网址之前是否找过
                         * findUrls.contains(m.group()),如果没有则将这个网址打印出来。
                         * 并且加入发现队列(findUrls)和待查找队列(pendUrls)并且指明这个
                         * 网址的深度(计算过程是将发现这个网址的网址的网址深度加1再赋值给存放刚发现
                         * 的这个网址的存储深度的哈希表deepUrls.put(m.group(),
                         * (deepUrls.get(url.toString()) + 1))
                         */
                        if(!findUrls.contains(m.group()))
                        {
                            System.out.println(m.group());
                            findUrls.add(m.group());
                            pendUrls.add(m.group());
                            deepUrls.put(m.group(), (deepUrls.get(url.toString()) + 1));
                        }
                    }
                }
            }
            /*
             * 当这个网址的数据已经被读取完时输出这个网址。
             */
            System.out.println("抓取到页面:" + url);
        }
        catch(IOException e)
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    class Processer implements Runnable// 线程类
    {
        SimpleCrawler nb;
        String s;
        public Processer(SimpleCrawler nb)
        {
            this.nb = nb;
        }

        public Processer(SimpleCrawler nb, String s)
        {
            this.nb = nb;
            this.s = s;
        }

        public void run()// 线程体, 定义抓取的过程
        {
            // Thread.sleep(5000);
            while(!pendUrls.isEmpty())
            {
                try
                {
                    String tmp = getAUrl();
                    URL url = null;
                    url = new URL(tmp);
                    nb.findUrlFromHtml(url);
                }
                catch(MalformedURLException e)
                {
                    e.printStackTrace();
                }
            }
        }
    }
    /**
     * @param string change the string inner regex special mean to general string
     */
    private String replaceRegexSignAll(String string)
    {
        /*
         * public String replaceAll(String regex, String replacement) 使用给定的replacement
         * 替换此字符串所有匹配给定的正则表达式的子字符串
         * 调用此方法的str.eplaceAll(regex,repl)形式与一下表达式产生的结果完全相同:
         * Pattern.compile(regex).matcher(str).replaceAll(repl)
         * 注意,在替代字符串中使用反斜杠(\)和美元付($)与将其视为字面值替代字符串所得的结果可能不同;
         * 请参阅Matcher.replaceAll。如有需要,可以使用Matcher.quoteReplacement(java.lang.
         * String)取消这些字符特殊含义。
         * 参数:
         * regex - 用来匹配此字符串的正则表达式
         * replacement - 用来替换每个匹配项的字符串
         * 返回:
         * 所得String
         */
        string = string.replaceAll("\\\\", "\\\\\\\\");
        string = string.replaceAll("\\.", "\\\\.");
        string = string.replaceAll("\\*", "\\\\*");
        string = string.replaceAll("\\?", "\\\\?");
        string = string.replaceAll("\\^", "\\\\^");
        string = string.replaceAll("\\$", "\\\\$");
        string = string.replaceAll("\\+", "\\\\+");
        string = string.replaceAll("\\|", "\\\\|");
        return string;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值