用HtmlParser 写个简单的 news爬虫

    有一段时间没写博客了,这几天回到学校我同学要赶着交毕业设计,让我帮他写个爬虫,专门抓搜狐的新闻,我用过爬虫,但是从来没有自己写过爬虫,于是Google了一下,找到了一篇不错的文章: 使用 HttpClient 和 HtmlParser 实现简易爬虫 .  参考里面的代码,自己写了个简易的搜狐新闻爬虫。

    爬虫的主要工做就是到搜狐的新闻首页上去抓取新闻,然后将新闻添加到数据库中。
代码其实很简单的:
LinkParser.java
import  com.sohu.SohuNews;
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;

/**
 *  这个类是用来搜集新闻链接地址的。将符合正则表达式的URL添加到URL数组中。
 * 
@author guanminglin
 
*/
public   class  LinkParser {
    
//  获取一个网站上的链接,filter 用来过滤链接

    
public   static  Set < String >  extracLinks(String url, LinkFilter filter) {

        Set
< String >  links  =   new  HashSet < String > ();
        
try  {
            Parser parser 
=   new  Parser(url);
            parser.setEncoding(
" gb2312 " );
            
//  过滤 <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;
    }

    
public   void  doParser(String url) {
        SohuNews news 
=   new  SohuNews();
        Set
< String >  links  =  LinkParser.extracLinks(
                url, 
new  LinkFilter() {
            
// 提取以  http://news.sohu.com 开头的链接

            
public   boolean  accept(String url) {
                
if  (url.matches( " http://news.sohu.com/[\\d]+/n[\\d]+.shtml " )) {
                    
return   true ;
                } 
else  {
                    
return   false ;
                }
            }
        });
        
// 循环迭代出连接,然后提取该连接中的新闻。
         for  (String link : links) {
            System.out.println(link);
            news.parser(link); 
// 解析连接
            
        }
    }

    
// 测试主页新闻,可以得到主页上所有符合要求的网页地址,并进行访问。
     public   static   void  main(String[] args) {
        String url 
=   " http://news.sohu.com/ " ;
        LinkParser parser 
=   new  LinkParser();
        parser.doParser(url);

    }
}

上面这段带码比较简单,就是用来提取 http://news.sohu.com  上面的新闻连接 ,格式类似这样: http://news.sohu.com/20090518/n264012864.shtml
所以写了一小段的正则表达式来匹配他:
Set < String >  links  =  LinkParser.extracLinks(
                url, 
new  LinkFilter() {
            
// 提取以  http://news.sohu.com  开头的链接

            
public   boolean  accept(String url) {
                
if  (url.matches( " http://news.sohu.com/[\\d]+/n[\\d]+.shtml " )) {
                    
return   true ;
                } 
else  {
                    
return   false ;
                }
            }
        });

还有一个核心类就是用来解析搜狐新闻的类,该类用于重网页中提取出新闻,然后将新闻添加到数据库中。代码中还用到了一个NewsBean
这段代码就不贴出来了,很简单的POJO 代码。核心代码都在下面。
SohuNews.java
import  com.sohu.bean.NewsBean;
import  com.sohu.db.ConnectionManager;
import  java.util.ArrayList;
import  java.util.List;
import  java.util.logging.Level;
import  java.util.logging.Logger;
import  org.htmlparser.NodeFilter;
import  org.htmlparser.Parser;
import  org.htmlparser.beans.StringBean;
import  org.htmlparser.filters.AndFilter;
import  org.htmlparser.filters.HasAttributeFilter;
import  org.htmlparser.filters.TagNameFilter;
import  org.htmlparser.tags.Div;
import  org.htmlparser.tags.HeadingTag;
import  org.htmlparser.tags.Span;
import  org.htmlparser.util.NodeList;
import  org.htmlparser.util.ParserException;

import  java.sql.PreparedStatement;
import  java.sql.SQLException;

/**
 * 用于对搜狐网站上的新闻进行抓取
 * 
@author  guanminglin <[email protected]>
 
*/
public   class  SohuNews {

    
private  Parser parser  =   null ;    // 用于分析网页的分析器。
     private  List newsList  =   new  ArrayList();     // 暂存新闻的List;
     private  NewsBean bean  =   new  NewsBean();
    
private  ConnectionManager manager  =   null ;     // 数据库连接管理器。
     private  PreparedStatement pstmt  =   null ;

    
public  SohuNews() {
    }

    
/**
     * 获得一条完整的新闻。
     * 
@param  newsBean
     * 
@return
     
*/
    
public  List getNewsList( final  NewsBean newsBean) {
        List list 
=   new  ArrayList();
        String newstitle 
=  newsBean.getNewsTitle();
        String newsauthor 
=  newsBean.getNewsAuthor();
        String newscontent 
=  newsBean.getNewsContent();
        String newsdate 
=  newsBean.getNewsDate();
        list.add(newstitle);
        list.add(newsauthor);
        list.add(newscontent);
        list.add(newsdate);
        
return  list;
    }

    
/**
     *  设置新闻对象,让新闻对象里有新闻数据
     * 
@param  newsTitle 新闻标题
     * 
@param  newsauthor  新闻作者
     * 
@param  newsContent 新闻内容
     * 
@param  newsDate  新闻日期
     * 
@param  url  新闻链接
     
*/
    
public   void  setNews(String newsTitle, String newsauthor, String newsContent, String newsDate, String url) {
        bean.setNewsTitle(newsTitle);
        bean.setNewsAuthor(newsauthor);
        bean.setNewsContent(newsContent);
        bean.setNewsDate(newsDate);
        bean.setNewsURL(url);
    }

    
/**
     * 该方法用于将新闻添加到数据库中。
     
*/
    
protected   void  newsToDataBase() {

        
// 建立一个线程用来执行将新闻插入到数据库中。
        Thread thread  =   new  Thread( new  Runnable() {

            
public   void  run() {
                
boolean  sucess  =  saveToDB(bean);
                
if  (sucess  !=   false ) {
                    System.out.println(
" 插入数据失败 " );
                }
            }
        });
        thread.start();
    }

    
/**
     * 将新闻插入到数据库中
     * 
@param  bean
     * 
@return
     
*/
    
public   boolean  saveToDB(NewsBean bean) {
        
boolean  flag  =   true ;
        String sql 
=   " insert into news(newstitle,newsauthor,newscontent,newsurl,newsdate) values(?,?,?,?,?) " ;
        manager 
=   new  ConnectionManager();
        String titleLength 
=  bean.getNewsTitle();
        
if  (titleLength.length()  >   60 ) {   // 标题太长的新闻不要。
             return  flag;
        }
        
try  {
            pstmt 
=  manager.getConnection().prepareStatement(sql);
            pstmt.setString(
1 , bean.getNewsTitle());
            pstmt.setString(
2 , bean.getNewsAuthor());
            pstmt.setString(
3 , bean.getNewsContent());
            pstmt.setString(
4 , bean.getNewsURL());
            pstmt.setString(
5 , bean.getNewsDate());
            flag 
=  pstmt.execute();

        } 
catch  (SQLException ex) {
            Logger.getLogger(SohuNews.
class .getName()).log(Level.SEVERE,  null , ex);
        } 
finally  {
            
try  {
                pstmt.close();
                manager.close();
            } 
catch  (SQLException ex) {
                Logger.getLogger(SohuNews.
class .getName()).log(Level.SEVERE,  null , ex);
            }

        }
        
return  flag;
    }

    
/**
     * 获得新闻的标题
     * 
@param  titleFilter
     * 
@param  parser
     * 
@return
     
*/
    
private  String getTitle(NodeFilter titleFilter, Parser parser) {
        String titleName 
=   "" ;
        
try  {

            NodeList titleNodeList 
=  (NodeList) parser.parse(titleFilter);
            
for  ( int  i  =   0 ; i  <  titleNodeList.size(); i ++ ) {
                HeadingTag title 
=  (HeadingTag) titleNodeList.elementAt(i);
                titleName 
=  title.getStringText();
            }

        } 
catch  (ParserException ex) {
            Logger.getLogger(SohuNews.
class .getName()).log(Level.SEVERE,  null , ex);
        }
        
return  titleName;
    }

    
/**
     * 获得新闻的责任编辑,也就是作者。
     * 
@param  newsauthorFilter
     * 
@param  parser
     * 
@return
     
*/
    
private  String getNewsAuthor(NodeFilter newsauthorFilter, Parser parser) {
        String newsAuthor 
=   "" ;
        
try  {
            NodeList authorList 
=  (NodeList) parser.parse(newsauthorFilter);
            
for  ( int  i  =   0 ; i  <  authorList.size(); i ++ ) {
                Div authorSpan 
=  (Div) authorList.elementAt(i);
                newsAuthor 
=  authorSpan.getStringText();
            }

        } 
catch  (ParserException ex) {
            Logger.getLogger(SohuNews.
class .getName()).log(Level.SEVERE,  null , ex);
        }
        
return  newsAuthor;

    }

    
/*
     * 获得新闻的日期
     
*/
    
private  String getNewsDate(NodeFilter dateFilter, Parser parser) {
        String newsDate 
=   null ;
        
try  {
            NodeList dateList 
=  (NodeList) parser.parse(dateFilter);
            
for  ( int  i  =   0 ; i  <  dateList.size(); i ++ ) {
                Span dateTag 
=  (Span) dateList.elementAt(i);
                newsDate 
=  dateTag.getStringText();
            }
        } 
catch  (ParserException ex) {
            Logger.getLogger(SohuNews.
class .getName()).log(Level.SEVERE,  null , ex);
        }

        
return  newsDate;
    }

    
/**
     * 获取新闻的内容
     * 
@param  newsContentFilter
     * 
@param  parser
     * 
@return   content 新闻内容
     
*/
    
private  String getNewsContent(NodeFilter newsContentFilter, Parser parser) {
        String content 
=   null ;
        StringBuilder builder 
=   new  StringBuilder();


        
try  {
            NodeList newsContentList 
=  (NodeList) parser.parse(newsContentFilter);
            
for  ( int  i  =   0 ; i  <  newsContentList.size(); i ++ ) {
                Div newsContenTag 
=  (Div) newsContentList.elementAt(i);
                builder 
=  builder.append(newsContenTag.getStringText());
            }
            content 
=  builder.toString();   // 转换为String 类型。
             if  (content  !=   null ) {
                parser.reset();
                parser 
=  Parser.createParser(content,  " gb2312 " );
                StringBean sb 
=   new  StringBean();
                sb.setCollapse(
true );
                parser.visitAllNodesWith(sb);
                content 
=  sb.getStrings();
//                 String s = "\";} else{ document.getElementById('TurnAD444').innerHTML = \"\";} } showTurnAD444(intTurnAD444); }catch(e){}";
               
                content 
=  content.replaceAll( " \\\ " . * [a - z]. * \\} " "" );
             
                content 
=  content.replace( " [我来说两句] " "" );


            } 
else  {
               System.out.println(
" 没有得到新闻内容! " );
            }

        } 
catch  (ParserException ex) {
            Logger.getLogger(SohuNews.
class .getName()).log(Level.SEVERE,  null , ex);
        }

        
return  content;
    }

    
/**
     * 根据提供的URL,获取此URL对应网页所有的纯文本信息,次方法得到的信息不是很纯,
     *常常会得到我们不想要的数据。不过如果你只是想得到某个URL 里的所有纯文本信息,该方法还是很好用的。
     * 
@param  url 提供的URL链接
     * 
@return  RL对应网页的纯文本信息
     * 
@throws  ParserException
     * 
@deprecated  该方法被 getNewsContent()替代。
     
*/
    @Deprecated
    
public  String getText(String url)  throws  ParserException {
        StringBean sb 
=   new  StringBean();

        
// 设置不需要得到页面所包含的链接信息
        sb.setLinks( false );
        
// 设置将不间断空格由正规空格所替代
        sb.setReplaceNonBreakingSpaces( true );
        
// 设置将一序列空格由一个单一空格所代替
        sb.setCollapse( true );
        
// 传入要解析的URL
        sb.setURL(url);

        
// 返回解析后的网页纯文本信息
         return  sb.getStrings();
    }

    
/**
     * 对新闻URL进行解析提取新闻,同时将新闻插入到数据库中。
     * 
@param  content
     
*/
    
public   void  parser(String url) {
        
try  {
            parser 
=   new  Parser(url);
            NodeFilter titleFilter 
=   new  TagNameFilter( " h1 " );
            NodeFilter contentFilter 
=   new  AndFilter( new  TagNameFilter( " div " ),  new  HasAttributeFilter( " id " " sohu_content " ));
            NodeFilter newsdateFilter 
=   new  AndFilter( new  TagNameFilter( " span " ),  new  HasAttributeFilter( " class " " c " ));
            NodeFilter newsauthorFilter 
=   new  AndFilter( new  TagNameFilter( " div " ),  new  HasAttributeFilter( " class " " editUsr " ));
            String newsTitle 
=  getTitle(titleFilter, parser);
            parser.reset();   
// 记得每次用完parser后,要重置一次parser。要不然就得不到我们想要的内容了。
            String newsContent  =  getNewsContent(contentFilter, parser);
            System.out.println(newsContent);   
// 输出新 闻的内容,查看 是否符合要求
            parser.reset();
            String newsDate 
=  getNewsDate(newsdateFilter, parser);
            parser.reset();
            String newsauthor 
=  getNewsAuthor(newsauthorFilter, parser);

            
// 先设置新闻对象,让新闻对象里有新闻内容。
             setN              ews(newsTitle, newsauthor, newsContent, newsDate, url);
// 将新闻添加到数据中。
             this .newsToDataBase();
            
        } 
catch  (ParserException ex) {
            Logger.getLogger(SohuNews.
class .getName()).log(Level.SEVERE,  null , ex);
        }
    }

    
// 单个文件测试网页
     public   static   void  main(String[] args) {
        SohuNews news 
=   new  SohuNews();
        news.parser(
" http://news.sohu.com/20090518/n264012864.shtml " );   
    }
}

存放新闻的数据库用的是MySql 建表语句如下:(其实不用数据库也可以的,在SohuNews类中注释掉那行红色的代码就可以了,所有得到的新闻都会在后台打印的。)

CREATE   DATABASE   IF   NOT   EXISTS  sohunews;
USE  sohunews;

--
--
 Definition of table `news`
--

DROP   TABLE   IF   EXISTS  `news`;
CREATE   TABLE  `news` (
  `newsid` 
int ( 11 NOT   NULL  auto_increment,
  `newstitle` 
varchar ( 60 NOT   NULL ,
  `newsauthor` 
varchar ( 20 NOT   NULL ,
  `newscontent` 
text   NOT   NULL ,
  `newsurl` 
char ( 130 NOT   NULL ,
  `newsdate` 
varchar ( 24 NOT   NULL ,
  
PRIMARY   KEY   (`newsid`)
) ENGINE
= InnoDB   DEFAULT  CHARSET = utf8;

以上的代码写的很粗糙,项目中使用到了HtmlParser工具包,如果需要可以到http://sourceforge.net/projects/htmlparser 网站上下载。如果有需要这个

这篇文章只是一篇抛砖引玉的文章,希望懂爬虫的你能够给点意见,大家交流交流!!

项目源代码: SohuNews


你可能感兴趣的:(用HtmlParser 写个简单的 news爬虫)