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;
}
}
simple webcrawler 初步理解
最新推荐文章于 2024-09-12 08:27:14 发布