通用文章采集器的设计与实现
2010-05-12, Posted in 人生百味 | 5 回复
最近头脑有点发热,想做一个采集器来采集文章,然后做一个类似垃圾站的东西,当然,使用的文章发布程序还是用wordpress,wordpress上的采集器不太多,他们大多都是通过rss采集,这也不太合适我,因为如果做为文章采集,采集的对象可能有文章内分页,所以最终还是自己写一个程序吧,自己最熟悉的还是java,那就用java写吧。
采集一堆文章,主要有如下步骤:
第一:采集规则定义
第二:按指定的规则进行文章采集
根据以上步骤,我们分开来实现。采集规则的定义实际就是你要做什么,怎么做的定义,我们这里很明确,就是指定一个列表的url地址,提取列表中所有文章的url,然后再一一采集各文章中的标题、内容,如果每篇文章有内分页,还要分别提取。所以,我们首先要做的,就是给定一个url地址,取得对应的html源码。下面是一个实现,它模拟浏览器,向url发出http请求,服务器返回的结果,就是我们需要得到的东西。
/** * 给定一个列表url,得到本url对象的html,然后其它操作在这个基础上做 * @param url * @param encoding 返回html的编码方式,如GBK,UTF─8 * @return * @throws Exception */ public String getHtml(String url, String encoding) throws Exception { String value = null; HttpClient httpclient = new DefaultHttpClient(); HttpGet httpget = new HttpGet(url); // 以下这条如果不加会发现无论你设置Accept-Charset为gbk还是utf-8,他都会默认返回gb2312(本例针对google.cn来说) httpget.setHeader("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.2)"); // 用逗号分隔显示可以同时接受多种编码 httpget.setHeader("Accept-Language", "zh-cn,zh;q=0.5"); httpget.setHeader("Accept-Charset", "GB2312,utf-8;q=0.7,*;q=0.7"); HttpResponse response = httpclient.execute(httpget); // 判断页面返回状态判断是否进行转向抓取新链接 int statusCode = response.getStatusLine().getStatusCode(); if ((statusCode == HttpStatus.SC_MOVED_PERMANENTLY) || (statusCode == HttpStatus.SC_MOVED_TEMPORARILY) || (statusCode == HttpStatus.SC_SEE_OTHER) || (statusCode == HttpStatus.SC_TEMPORARY_REDIRECT)) { // 此处重定向处理 此处还未验证 String newUri = response.getLastHeader("Location").getValue(); httpclient = new DefaultHttpClient(); httpget = new HttpGet(newUri); response = httpclient.execute(httpget); } // Get hold of the response entity HttpEntity entity = response.getEntity(); // If the response does not enclose an entity, there is no need // to bother about connection release if (entity != null) { // 将源码流保存在一个byte数组当中,因为可能需要两次用到该流, byte[] bytes = EntityUtils.toByteArray(entity); // 如果头部Content-Type中包含了编码信息,那么我们可以直接在此处获取 if(encoding != null && !"".equals(encoding.trim())){ value = new String(bytes, encoding); }else{ String charSet = EntityUtils.getContentCharSet(entity); // 如果头部中没有,那么我们需要 查看页面源码,这个方法虽然不能说完全正确,因为有些粗糙的网页编码者没有在页面中写头部编码信息 if (charSet == null || "".equals(charSet.trim())) { String regEx="(?=<meta).*?(?<=charset=[\\'|\\\"]?)([[a-z]|[A-Z]|[0-9]|-]*)"; Pattern p=Pattern.compile(regEx, Pattern.CASE_INSENSITIVE); Matcher m=p.matcher(new String(bytes)); // 默认编码转成字符串,因为我们的匹配中无中文,所以串中可能的乱码对我们没有影响 boolean result=m.find(); if (m.groupCount() == 1) { charSet = m.group(1); } } value = new String(bytes, charSet); } } httpclient.getConnectionManager().shutdown(); return value; }
注意,这里用到了Apache的HttpClient项目,由于空间有限,我这里不上传本函数所依赖的包,请大家自己到apache下载。
以淘宝的女人门户为例子,我给定http://info.taobao.com/list/lady/23/30/2330b6ed-16ce-4d0e-b7fa-58a411e1871a_1.php这个url地址,它会返回一个包含文章列表的html。有了这个html,我们接下来就是提取URL,当然,提取前先不着急,因为这个html里面的内容比较多,不能直接提取,那就把它简单化,先得到一个区域吧:
/** * 列表区域识别规则 * @param html 原始的html * @param areaRegex 所提取区域的正则表达式 * @param area 正则表达式中需要提取的区域,取值是:AREA_LIST,AREA_TITLE等等 * @return */ public String getArea(String html, String areaRegex ,String area) { String valueHtml=null; String regex = areaRegex.replaceFirst(area, "([\\\\s\\\\S]*?)"); // System.out.println(regex); Pattern pattern = Pattern.compile(regex,Pattern.MULTILINE); Matcher matcher = pattern.matcher(html); while(matcher.find()) { valueHtml = matcher.group(1).trim(); break; } return valueHtml; }
有了这个域名,我就就可以提取URL列表了:
//提取给定区域的文章列表地址, public List<String> getListUrl(String areaHtml,String regex,String withPrefix){ return this.getListUrl(areaHtml, regex, withPrefix, false, null); } /** * 提取给定区域的文章列表地址,主要用来提取文章内的分页用的 * @param areaHtml * @param regex * @param withPrefix 返回的地址是否需要加一个前缀,如21cn的新闻,提取到后是没有前缀的,得加一个 * @param hasOmit 提取文章内分页时,是否有分页是否有省略号,如淘宝的就有。 * @param pageNumRegex 提取文章内的分页正则表达式 * @return */ public List<String> getListUrl(String areaHtml,String regex,String withPrefix,boolean hasOmit,String pageNumRegex){ List<String> list = new ArrayList<String>(); regex = regex.replaceFirst(WebContent.AREA_URL, "(.*?)"); Pattern pa = Pattern.compile(regex, Pattern.MULTILINE); Matcher ma = pa.matcher(areaHtml); while (ma.find()) { if(withPrefix !=null && !"".equals(withPrefix.trim())){ list.add(withPrefix+ma.group(1).trim()); }else{ list.add(ma.group(1).trim()); } } if(hasOmit && list.size()>1){ String lasturl = list.get(list.size()-1); list.clear(); regex = "(.*?)"+pageNumRegex.replaceFirst(WebContent.PAGE_URL_NUM, "(-?\\\\d+)"); pa = Pattern.compile(regex); ma = pa.matcher(lasturl); if(ma.find()){ int num = Integer.parseInt(ma.group(2)); String prefix = ma.group(1).trim(); String baseurl = prefix+pageNumRegex; for(int i = 2; i <= num; i ++) list.add(baseurl.replaceFirst(WebContent.PAGE_URL_NUM, i+"")); } } return list; }
以上两个函数,就可以得到文章列表及文章内分页列表。实际上有这些组合,就可以提取大部分的网站了,这里给一个例子,我的http://www.extbi.com这个站点的文章就是用这个提取的。大家根据上面的讲解,好好看吧。WebContent.java
4 Comments for this entry
1 Trackback or Pingback for this entry
-
用java程序向wordpress发布文章 - Voland.com.cn
May 13th, 2010 on 09:47:44[...] apache 官方下载,使用这个例子,就可以与昨天的《通用文章采集器的设计与实现》结合,来生成自动发布的站点了,这个慎用哦,后果自负:) import [...]
May 12th, 2010 on 21:50:44
来过,踩过,不容错过!
o(∩_∩)o
May 13th, 2010 on 09:14:31
打广告来了
June 18th, 2010 on 09:06:17
别干这种事啊兄弟。呵呵。
June 20th, 2010 on 22:03:08
实际没有做这个事情了,觉得这样做无聊