simple webcrawler 初步理解

来源:互联网 发布:淘宝标题自动优化软件 编辑:程序博客网 时间:2024/06/09 02:50
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;    }}
0 0