小黄我老早就听说过爬虫大名了,可是却一直没有敢于尝试,这回因为脑子一热,断断续续的研究了几天,没想到并没有想象中的那么难,当然,我做的仅仅是最简单最初级的爬虫,但至少拥有爬虫大致之型了吧。总之,任何高超复杂的技术,都是从最简单开始逐步完善的,所以在技术的路上,我们应该无所畏惧,敢于尝试。好了,废话不多说了,进入正题。
准备工作:使用了开源包HttpClient(使用方法和下载自己百度)
大体流程:1,初始化(获取url装入url队列)。 2,判定条件(url队列中url的限定个数)。 3,如果满足条件,通过队列中的url连接网页并下载 。4,抓取解析邮箱与url。 5,将邮箱保存在本地txt文件中。
流程大致就是上面的。
下面来看看代码:
注:本爬虫专门爬取贴吧中的邮箱,所以输入的url仅限为所选贴吧的首页,它会自动爬取首页中帖子里包含的邮箱的。
各个类以及源码:
类的主要
Crawler.java:爬虫的主方法入口所在的类,实现爬取的主要流程。
LinkDB.java:保存已访问url和未访问url的类,提供出入队的操作。
Queue.java:一个简单的队列,LinkDB实现它。
InternetConnect.java:用get方式连接网页,并下载。
HtmlParse.java:抓取下载下来的网页中url与邮箱的。
FileSave.java:抓取的邮箱的本地存储。
源码:
1.Crawler
import java.util.HashSet;
import java.util.Set;
//爬虫的主操作
public class Crawler {
private Set<String> emails = new HashSet<String>();
int num =0;
//爬取网页中的url
private void initCrawler(String url){
Set<String> set = HtmlParse.getUrl(url);
for(String s:set){
LinkDB.addUnvisitedUrl(s);
}
}
public void crawling(String url){
initCrawler(url);
//判定条件
while(!LinkDB.unVisitedUrlsEmpty()&&LinkDB.getVisitedUrlNum()<=100){
//队头出Url
String visitUrl = LinkDB.unVisitedUrlDeQueue();
//在控制台显示所爬url
System.out.println(visitUrl);
num++;
if(visitUrl==null)
continue;
LinkDB.addVisitedUrl(visitUrl);
Set<String> email = HtmlParse.getEmail(visitUrl);
emails.addAll(email);
initCrawler(visitUrl);
}
for(String s:emails){
System.out.println(s);
}
FileSave.fileSave(emails);
System.out.println("网页个数"+num);
System.out.println("邮箱个数"+emails.size());
}
public static void main(String[] args) {
//输入想要爬取的url,记住一定得是所选贴吧的首页(这个是邮箱吧)
new Crawler().crawling("http://tieba.baidu.com/f?kw=%D3%CA%CF%E4&fr=ala0");
}
}
2.LinkDB
import java.util.HashSet;
import java.util.Set;
//一般步骤为:定义数据结构及其操作
public class LinkDB {
//已访问的url
private static Set<String> visitedUrl = new HashSet<String>();
//未访问的url
private static Queue<String> unVisitedUrl = new Queue<String>();
public static Queue<String> getUnVisitedUrl(){
return unVisitedUrl;
}
public static void addVisitedUrl(String url){
visitedUrl.add(url);
}
public static void removeVisitedUrl(String url){
visitedUrl.remove(url);
}
public static String unVisitedUrlDeQueue(){
return unVisitedUrl.deQueue();
}
//确定每个url只访问过一次
public static void addUnvisitedUrl(String url){
if(url !=null&&!url.trim().equals("")&&!visitedUrl.contains(url)&&!unVisitedUrl.contains(url))
unVisitedUrl.enQueue(url);
}
public static int getVisitedUrlNum(){
return visitedUrl.size();
}
public static boolean unVisitedUrlsEmpty(){
return unVisitedUrl.isQueueEmpty();
}
}
3.Queue.java
import java.util.LinkedList;
//数据结构队列,用LinkedList来实现 T是泛型,可自定义传入的泛型
public class Queue<T> {
private LinkedList<T> queue = new LinkedList<T>();
//队列中加操作
public void enQueue(T t){
queue.add(t);
}
//删除第一个,并提取出来
public T deQueue(){
return queue.removeFirst();
}
public boolean isQueueEmpty(){
return queue.isEmpty();
}
public boolean contains(T t){
return queue.contains(t);
}
}
4.HtmlParse.java
这个我绝对要吐槽一下,我以为正则表达式很简单,结果,我擦。。。。默泪中......小黄真是学艺不精啊,搞了很久,郁闷至极中百度的帖子的url又不按正常的来,于是顺带擦一下.....嘿嘿。(吐槽勿怪,赶紧贴代码)
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class HtmlParse {
//抓取网页中的url
public static Set<String> getUrl(String url){
Set<String> set = new HashSet<String>();
//抓取url的正则表达式,小黄学艺不精,各位看官将就看吧
Pattern pattern = Pattern.compile("<\\s*a\\s*href\\s*=\\s*\"/p/([^\"]+)");
try{
Matcher matcher = pattern.matcher(InternetConnect.getRequst(url));
while(matcher.find()){
//帖子的url太简略了,还得我来加.....
set.add(matcher.group().replaceAll("<a href=\"", "http://tieba.baidu.com"));
}
}catch(Exception e){
e.printStackTrace();
}
return set;
}
//抓取网页中的邮箱
public static Set<String> getEmail(String url){
//用Set集合,去掉重复邮箱
Set<String> set = new HashSet<String>();
Pattern pattern = Pattern.compile("[\\w\\.\\-]+@([\\w\\-]+\\.)+[\\w\\-]+");
try{
Matcher matcher = pattern.matcher(InternetConnect.getRequst(url));
while(matcher.find()){
set.add(matcher.group());
}
}catch(Exception e){
e.printStackTrace();
}
return set;
}
}
5,FileSave.java
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Set;
public class FileSave {
//保存到指定txt
public static void fileSave(Set<String> set){
//读取当前时间来当做文件名
Date date =new Date();
SimpleDateFormat dateFormat =new SimpleDateFormat("yyyyMMddHHmm");
System.out.println("存放的txt名为"+dateFormat.format(date)+"email.txt");
try{
File file = new File(dateFormat.format(date)+"email.txt");
BufferedWriter writer =new BufferedWriter(new FileWriter(file));
for(String s:set){
//换行得是\r\n
writer.write(s+"\r\n");
}
writer.close();
}catch(IOException ex){
ex.printStackTrace();
}
}
}
6,InternetConnect
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
/*
* 连接网页,下载网页
* get通信
*/
public class InternetConnect {
static HttpClient httpClient;
public static String getRequst(String url) throws Exception{
httpClient = new DefaultHttpClient();
try{
//使用get方式连接,得到回应,如果状态码为200则...自己看代码
HttpGet get = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(get);
if(httpResponse.getStatusLine().getStatusCode()==200){
//以字符串形式获取网页源码
String result = EntityUtils.toString(httpResponse.getEntity());
return result;
}
}catch(Exception e){
e.printStackTrace();
return"worry";}finally{
//妈妈曾经告诉我..好吧,是老师,画虚线的方法都是老的要被淘汰的,但这个是我以前做项目时辛苦收藏的,于是坚持用。
httpClient.getConnectionManager().shutdown();
}
return null;
}
}
好啦,代码就是上面这些。
我写这篇文章主要参考了:https://www.ibm.com/developerworks/cn/opensource/os-cn-crawler/
这是我花大量时间唯一找到的详细写了爬虫的而且令人满意的博文,在此十分感谢作者,欢迎大家去看看
其实嘛,我这个只是1.0,后续可扩展的功能十分多,比如给所爬取的邮箱发邮件啊(慎用,有被骂的风险);抓取友情贴吧的邮箱啊等等......如果继续坚持下去的话,肯定能写出一个复杂高超的爬虫的,哈哈哈
好勒,大致本文结束了,这是小黄写的第一篇博客,再加上水平有限,有很多不足之处望大家指出批判,很希望与大家能进行讨论,只有分享与讨论才能更好的进步嘛 。下一步,学着把源代码传到GitHub上(好吧,小黄弱爆了,连GitHub都没用过),还望大家能指导。