一、新增博客类型:新增了wordpress个人博客 xml文件上传导入oschina的功能。登陆osc后只有点击右下wordpress图标即可选择从自己wordpress站点导出的xml文件上传。再点击抓取就可以获取文件中的博客列表,可选地导入osc即可。
二、程序结构调整:本次更新最主要是将各个博客网站的抓取规则写到同一个配置文件中,将抓取逻辑封装在同一个类里面,然后根据不同的url从配置文件中读取不同网站的抓取规则(读取xml用dom4j)。主要是为了方便扩展,也让结构更加清晰一点。
/**
* 博客爬虫抓取逻辑
* @author oscfox
* @date 20140124
*/
public class BlogPageProcessor implements PageProcessor{
public class LinkXpath{
public String linksXpath; //链接列表过滤表达式
public String titlesXpath; //title列表过滤表达式
}
public class ArticleXpath{
public String contentXpath; //内容过滤表达式
public String titleXpath; //title过滤表达式
public String tagsXpath; //tags过滤表达式
}
private Site site = new Site();
private String url;
private String blogFlag; //博客url的内容标志域
private List<String> codeBeginRex; //代码过滤正则表达式
private List<String> codeEndRex; //代码过滤正则表达式
private List<LinkXpath> linkXpaths; //获取链接表达式
private List<ArticleXpath> articleXpaths; //获取文件表达式
private List<String> PagelinksRex; //类别页列表过滤表达式
private Hashtable<String, String> codeHashtable; //代码class映射关系
private SpiderConfigTool spiderConfig;
public BlogPageProcessor(String url) throws Exception{
if(url.endsWith("/")){
url = url.substring(0, url.length()-1);
}
this.url=url;
String spiderName=""; //切割域名 :类似:csdn.net, 51cto.com, cnblogs.com, iteye.com
Pattern p=Pattern.compile("\\.([a-zA-Z0-9]+\\.[a-zA-Z]+)");
Matcher m=p.matcher(url);
if(m.find()){
spiderName = m.group(1);
} else {
throw new Exception("不支持的网站!");
}
spiderConfig = new SpiderConfigTool(spiderName);
init();
}
/**
* 初始化
*/
private void init(){
String domain = spiderConfig.getSpiderNode().selectSingleNode("domain").getText();
site = Site.me().setDomain(domain);
String charset = spiderConfig.getSpiderNode().selectSingleNode("charset").getText();
site.setCharset(charset);
site.setSleepTime(1);
blogFlag = spiderConfig.getSpiderNode().selectSingleNode("blog-flag").getText();
initPageRex();
initCodeRex();
initLinkXpath();
initArticleXpath();
initCodeHash();
}
/**
* 初始化 代码替换正则
*/
@SuppressWarnings("unchecked")
private void initCodeRex(){
codeBeginRex = new ArrayList<String>(); //代码过滤正则表达式
codeEndRex = new ArrayList<String>(); //代码过滤正则表达式
List<Node> list = spiderConfig.getSpiderNode().selectNodes("code-begin-rex");
for(Node n:list){
codeBeginRex.add(n.getText());
}
list = spiderConfig.getSpiderNode().selectNodes("code-end-rex");
for(Node n:list){
codeEndRex.add(n.getText());
}
}
/**
* 初始化 分页链接
*/
@SuppressWarnings("unchecked")
private void initPageRex(){
PagelinksRex = new ArrayList<String>();
//page-links-rex
List<Node> list = spiderConfig.getSpiderNode().selectNodes("page-links-rex");
for(Node pagelink:list){
String page = pagelink.getText();
String string=url.replaceAll("\\.", "\\\\\\.");
String temString= string+page;
PagelinksRex.add(temString);
}
}
/**
* 初始化 获取链接列表xpath
*/
@SuppressWarnings("unchecked")
private void initLinkXpath(){
linkXpaths = new ArrayList<LinkXpath>(); //获取链接表达式
List<Node> list = spiderConfig.getSpiderNode().selectNodes("link-xpath");
for(Node node : list){
String link = node.selectSingleNode("links-xpath").getText();
String title = node.selectSingleNode("titles-xpath").getText();
LinkXpath linkXpath = new LinkXpath();
linkXpath.linksXpath=link;
linkXpath.titlesXpath=title;
linkXpaths.add(linkXpath);
}
}
/**
* 初始化 文章规则
*/
@SuppressWarnings("unchecked")
private void initArticleXpath(){
articleXpaths = new ArrayList<ArticleXpath>(); //获取文件表达式
List<Node> list = spiderConfig.getSpiderNode().selectNodes("article-xpath");
for(Node node : list){
String content = node.selectSingleNode("content-xpath").getText();
String title = node.selectSingleNode("title-xpath").getText();
String tags = node.selectSingleNode("tags-xpath").getText();
ArticleXpath articleXpath = new ArticleXpath();
articleXpath.contentXpath=content;
articleXpath.titleXpath=title;
articleXpath.tagsXpath = tags;
articleXpaths.add(articleXpath);
}
}
/**
* 初始化代码类型映射
*/
@SuppressWarnings("unchecked")
private void initCodeHash(){
codeHashtable = new Hashtable<String, String>();
List<Node> list = spiderConfig.getSpiderNode().selectNodes("code-hashtable");
for(Node node : list){
String key = node.selectSingleNode("key").getText();
String osc = node.selectSingleNode("osc").getText();
codeHashtable.put(key, osc);
}
}
/**
* 抓取博客内容等,并将博客内容中有代码的部分转换为oschina博客代码格式
*/
@Override
public void process(Page page) {
Pattern p=Pattern.compile(blogFlag);
Matcher m=p.matcher(url);
boolean result=m.find();
if(result){
getPage(page);
page.putField("getlinks", false);
} else {
getLinks(page);
page.putField("getlinks", true);
}
}
/**
* 抓取链接列表
* @param page
*/
private void getLinks(Page page) {
List<String> links = page.getHtml().xpath(linkXpaths.get(0).linksXpath).all();
List<String> titles = page.getHtml().xpath(linkXpaths.get(0).titlesXpath).all();
for(int i=1; i < linkXpaths.size() && titles.size() == 0; ++i){
links = page.getHtml().xpath(linkXpaths.get(i).linksXpath).all();
titles = page.getHtml().xpath(linkXpaths.get(i).titlesXpath).all();
}
page.putField("titles", titles);
page.putField("links", links);
List<String> Pagelinks = page.getHtml().links().regex(PagelinksRex.get(0)).all();
for(int i=1; i < PagelinksRex.size() && Pagelinks.size() == 0; ++i){
Pagelinks = page.getHtml().links().regex(PagelinksRex.get(i)).all();
}
page.addTargetRequests(Pagelinks);
}
/**
* 抓取博客内容
* @param page
*/
private void getPage(Page page){
String title = page.getHtml().xpath(articleXpaths.get(0).titleXpath).toString();
String content = page.getHtml().xpath(articleXpaths.get(0).contentXpath).toString();
String tags = page.getHtml().xpath(articleXpaths.get(0).tagsXpath).all().toString();
for(int i=1; i < articleXpaths.size() && null == title; ++i){
title = page.getHtml().xpath(articleXpaths.get(i).titleXpath).toString();
content = page.getHtml().xpath(articleXpaths.get(i).contentXpath).toString();
tags = page.getHtml().xpath(articleXpaths.get(i).tagsXpath).all().toString();
}
if(StringUtils.isBlank(content) || StringUtils.isBlank(title)){
return;
}
if(!StringUtils.isBlank(tags)){
tags = tags.substring(tags.indexOf("[")+1,tags.indexOf("]"));
}
OscBlogReplacer oscReplacer= new OscBlogReplacer(codeHashtable); //设置工具类映射关系
String oscContent = oscReplacer.replace(codeBeginRex, codeEndRex, content); //处理代码格式
page.putField("content", oscContent);
page.putField("title", title);
page.putField("tags", tags);
}
@Override
public Site getSite() {
return site;
}
}
部分xml文件:
<!-- CSND -->
<spider-cofig>
<domain>blog.csdn.net</domain>
<charset>utf-8</charset>
<name>csdn.net</name> <!--//博客域名: 用于匹配博客spider配置 -->
<blog-flag>/article/details/</blog-flag> <!--//博客url的内容标志域 -->
<link-xpath> <!--//获取链接表达式 -->
<links-xpath><![CDATA[//div[@class='list_item article_item']/div[@class='article_title']/h3/span/a/@href]]></links-xpath>
<titles-xpath><![CDATA[//div[@class='list_item article_item']/div[@class='article_title']/h3/span/a/text()]]></titles-xpath>
</link-xpath>
<article-xpath> <!--//获取文章表达式 -->
<content-xpath><![CDATA[//div[@class='article_content']/html()]]></content-xpath>
<title-xpath><![CDATA[//div[@class='details']/div[@class='article_title']/h3/span/a/text()]]></title-xpath>
<tags-xpath><![CDATA[//div[@class='tag2box']/a/text()]]></tags-xpath>
</article-xpath>
<code-begin-rex><![CDATA[<pre.*?class=\"(.+?)\".*?>]]></code-begin-rex> <!--//代码过滤正则表达式-头 -->
<code-begin-rex><![CDATA[<textarea.*?class=\"(.+?)\".*?>]]></code-begin-rex>
<code-end-rex><![CDATA[</textarea>]]></code-end-rex> <!--//代码过滤正则表达式-尾 -->
<!--//列表页url过滤表达式 -->
<page-links-rex><![CDATA[/article/list/\d+]]></page-links-rex>
<!--//代码class映射关系 -->
<code-hashtable>
<key>csharp</key>
<osc>c#</osc>
</code-hashtable>
<code-hashtable>
<key>javascript</key>
<osc>js</osc>
</code-hashtable>
<code-hashtable>
<key>objc</key>
<osc>cpp</osc>
</code-hashtable>
</spider-cofig>
四、源码:
程序已更新到git
更多代码请看git地址:http://git.oschina.net/yashin/MoveBlog
欢迎各位OSCer 提交代码或BUG或提出宝贵意见。
由于本人资历尚浅,如有不足,敬请各位不吝赐教。
PS: 如各位OSCer 有需要从上述博客网站之外的站点搬家博客,可以给我留言,尽量满足各位的要求增加上去。
或者更欢迎您提交git 上的 pull Requests
如抓取的内容或者列表有误,请留言提供您要抓取的链接并简单说明bug,我会尽快给您答复。