Web版RSS阅读器(四)——定制自己的Rss解析库myrsslib4j

最后更新于:2022-04-01 20:40:20

      在上篇博文《[Web版RSS阅读器(三)——解析在线Rss订阅](http://blog.csdn.net/xiaoxian8023/article/details/9811833)》中,已经提到了遇到的问题,这里再详细说一下。       在解析rss格式的订阅时,遇到的最主要的问题是,出现了“Server returned HTTP response code: 403 for URL: http://xxxxxx”的错误,[百度一下](http://www.baidu.com/s?wd=Server+returned+HTTP+response+code%3A+403+for+URL)就知道,这是在网站访问中很常见的一个错误,服务器理解客户的请求,但拒绝处理它。即拒绝访问!接着查资料,得知某些服务器(比如CSDN博客)拒绝java作为客户端进行对其的访问,所以在解析时,会抛异常。      不让访问怎么办,别怕,我们上有政策,下有对策。通过设置User-Agent来欺骗服务器,从而访问服务器。     ~~~ connection.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)"); //用UA伪装访问连接对象 ~~~      但是折腾了半天,发现只有修改rsslib4j.jar才能给连接对象设置UA。只好找源码修改一下了,N久之后,在Google Code上猎取到一个开源的项目newrsslib4j,它是在rsslib4j的基础上修改而来的,项目开源主页:[http://code.google.com/p/newrsslib4j/](http://code.google.com/p/newrsslib4j/)。满怀欣喜的下载下来,结果发现,依旧有403的问题。一狠心,自己来做一个rsslib,然后就checkout了newrsslib4j的源码,自己动手改动。      1. 修改403 forbidden问题。      修改org.gnu.stealthp.rsslib包中的RssParser类的setXmlResource()方法,给URLConnection对象,添加UA。 ~~~ /** * Set rss resource by URL * @param ur the remote url * @throws RSSException */ public void setXmlResource(URL ur) throws RSSException{ try{ URLConnection con = u.openConnection(); //----------------------------- //添加时间:2013-08-14 21:00:17 //人员:@龙轩 //博客:http://blog.csdn.net/xiaoxian8023 //添加内容:由于服务器屏蔽java作为客户端访问rss,所以设置User-Agent con.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)"); //----------------------------- con.setReadTimeout(10000); String charset = Charset.guess(ur); is = new InputSource (new UnicodeReader(con.getInputStream(),charset)); if (con.getContentLength() == -1 && is == null){ this.fixZeroLength(); } }catch(IOException e){ throw new RSSException("RSSParser::setXmlResource fails: "+e.getMessage()); } } ~~~      修改org.mozilla.intl.chardet包中的Charset类的guess()方法,注释掉原来的InputStream对象,创建URLConnection,设置User-Agent,通过URLConnection对象创建InputStream : ~~~ //judge from url public static String guess(URL url) throws IOException { //----------------------------- //修改时间:2013-08-14 21:00:17 //人员:@龙轩 //博客:http://blog.csdn.net/xiaoxian8023 //修改内容:注释InputStream,创建URLConnection,设置User-Agent,通过URLConnection对象创建InputStream //InputStream in = url.openStream(); URLConnection con = url.openConnection(); con.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)"); InputStream in = con.getInputStream(); //----------------------------- return guess(in); } ~~~      2. 添加获取文章摘要的方法      测试以后,发现Rss格式的订阅,居然没有提供一个获取内容摘要的方法。所以,继续修改。修改org.gnu.stealthp.rsslib包中的RssObject类,添加一个getSummary()方法,用来获取内容摘要: ~~~ //----------------------------------- //添加时间:2013-08-14 19:32:15 //人员:@龙轩 //添加内容:添加getSummary()方法,返回文章摘要信息 /** * Get the element's summary * @return the summary */ public String getSummary(){ String summary = getDescription(); if (summary.length() >= 300) { summary = summary.substring(0, 300); } String regEx_html = "\\s|<[^>]+>|&\\w{1,5};"; // 定义HTML标签和特殊字符的正则表达式 Pattern pattern = Pattern.compile(regEx_html,Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(summary); summary = matcher.replaceAll(""); // 过滤script标签 if (summary.length() >= 100) { summary = summary.substring(0, 100); } summary = summary + "..."; return summary; } //添加结束----------------------------------------------- ~~~      3. 解决解析不完全问题      网易博客可以很好的解析出博客的link,language等节点,但是csdn、某些新闻资讯网站则不行。花了很长的时间去找问题,终于在不经意间,发现网易的博客节点的排列顺序与别的不一样,link,language等节点排在image节点上面的,而csdn则是在image的下面(如图),程序在解析时,先解析channel下的节点,写入到channel中,读取到image,则标记解析channel的变量为false,然后开始解析image节点下的内容。解析完毕以后,又要去解析channel节点下的link时,channel标记已经为false了,不能再继续解析,所以总是返回null。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c53c290a429.jpg) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c53c291a6d4.jpg)      修改方案也很简单,就是所有标记解析channel为false的节点,在解析完毕该节点后,重新标记解析channel为true,这样就可以继续解析channel节点下的其他值 。具体修改操作:查看org.gnu.stealthp.rsslib包的RSSHandler类的startElement()方法,看看谁执行了 reading_chan = false; 这句代码。然后在endElement()方法中,重新设置 reading_chan = true; 即可: ~~~ /** * Receive notification of the end of an element * @param uri The Namespace URI, or the empty string if the element has no Namespace URI or if Namespace processing is not being performed. * @param localName The local name (without prefix), or the empty string if Namespace processing is not being performed * @param qName The qualified name (with prefix), or the empty string if qualified names are not available */ public void endElement(String uri, String localName, String qName){ String data = buff.toString().trim(); if (qName.equals(current_tag)){ data = buff.toString().trim(); buff = new StringBuffer(); } if (reading_chan) processChannel(qName,data); if (reading_item) processItem(qName,data); if (reading_image) processImage(qName,data); if (reading_input) processTextInput(qName,data); if (tagIsEqual(qName,CHANNEL_TAG)){ reading_chan = false; chan.setSyndicationModule(sy); } if (tagIsEqual(qName,ITEM_TAG)){ reading_item = false; //----------------------------------------- //添加时间:2013-08-14 21:00:17 //人员:@龙轩 //博客:http://blog.csdn.net/xiaoxian8023 //添加内容:重新允许解析channel reading_chan = true; //添加结束--------------------------------- chan.addItem(itm); } if (tagIsEqual(qName,IMAGE_TAG)){ reading_image = false; //----------------------------------------- //添加时间:2013-08-14 21:00:17 //人员:@龙轩 //博客:http://blog.csdn.net/xiaoxian8023 //添加内容:重新允许解析channel reading_chan = true; //添加结束--------------------------------- chan.setRSSImage(img); } if (tagIsEqual(qName,SEQ_TAG)){ reading_seq = false; chan.addRSSSequence(seq); } if (tagIsEqual(qName,TEXTINPUT_TAG)){ reading_input = false; //----------------------------------------- //添加时间:2013-08-14 21:00:17 //人员:@龙轩 //博客:http://blog.csdn.net/xiaoxian8023 //添加内容:重新允许解析channel reading_chan = true; //添加结束--------------------------------- chan.setRSSTextInput(input); } } ~~~      至此,修改的就差不多了,打开一下org.javali.util.test包的RssNewsFetcher类,修改一下测试列表和方法: ~~~ package org.javali.util.test; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import org.gnu.stealthp.rsslib.RSSChannel; import org.gnu.stealthp.rsslib.RSSException; import org.gnu.stealthp.rsslib.RSSHandler; import org.gnu.stealthp.rsslib.RSSItem; import org.gnu.stealthp.rsslib.RSSParser; public class RssNewsFetcher { /** * rss订阅列表 */ private final static String[] rssArr = new String[] { //"http://news.baidu.com/n?cmd=1&class=civilnews&tn=rss", "http://xiaoxian100.blog.163.com/rss", "http://blog.csdn.net/xiaoxian8023/rss/list" }; /** * 测试解析rss订阅 * @throws IOException */ public void testFetchRssNews() throws IOException { for (int i = 0; i < rssArr.length; i++) { try { //获取rss订阅地址 URL url = new URL(rssArr[i]); RSSHandler handler = new RSSHandler(); //解析rss RSSParser.parseXmlFile(url, handler, false); //获取Rss频道信息 RSSChannel ch = handler.getRSSChannel(); System.out.println("---------------------------------------------"); System.out.println("博客标题:" + ch.getTitle()); System.out.println("博客链接:" + ch.getLink()); System.out.println("博客描述:" + ch.getDescription()); System.out.println("博客语言:" + ch.getLanguage()); System.out.println("发布时间:" + ch.getPubDate()); System.out.println("图片地址:" +ch.getRSSImage().getUrl()); System.out.println("图片指向:" +ch.getRSSImage().getLink()); System.out.println("共有文章数目为:" + ch.getItems().size()); // for(i=0;i ';