星期二, 10月 10, 2006

Blog備份

最近由於無名小站Mypage搞得許多人不爽,加上速度不夠快,因此就有GSLin的備份程式產生。但我比較喜歡有source code的東西,要改也比較快。

由於我並不是Blog的重度使用者,也沒有使用好友功能,因此不需要密碼就能看到整個Blog,也就是可以直接備份。剛好這幾天連假,就花了點時間稍微想一下如何備份Blog。本來想寫個能夠通用的備份程式,但這必須要研究Blog的protocol(應該是用XML-RPC),也不能夠真正通用;而且這樣做需要帳號及密碼,如果想備別人的Blog就不適用(其實我想備份獨孤木的Blog)。

我想做的是備份平時看得到的Blog,剛好Xuite的tag相當清楚,而且每一篇都有標示上一則與下一則,很容易用程式備份,因此選擇為實作目標。若是Wretch或Blogger,必須從首頁找到每一篇的Link。

初步的做法如下:
  1. 先使用Blog首頁,例如:http://blog.xuite.net/efchang/network,找到目前最後一篇的url,tag是div,class為articleSide 例如: <div class="articleSide"><a href="/efchang/network/8458800">今天中午跟酒肉朋友的朋友們去吃艾米諾</a>
  2. 開啟最後一篇,內容的tag同樣是div,class是blogbody,前一篇class是selectbar,依此方法將所有的內容都讀完,就可以遞迴讀進前一篇。到最後一篇(應該說第一篇?)所讀到的第一個link是首頁,因此知道已經結束。
  3. 內容要還原就得要Blog Engine支援,所以最好比照GSLin,將備份檔案存成MT匯入格式或是RSS。
程式語言當然要使用能跨平台,沒有編碼問題,我的首選是Java。使用HTML Parser能夠簡單抓出每個tag的內容,利用css class就可以判斷目前是那一個區塊。
今天是連假最後一天,而我未來幾個月都很忙,看來這個程式沒有時間完成,希望有備份需求的人能夠繼續幫我完成,做好的必須要寄給我一份。License為GPL v3,使用HTML Parser 1.6版,以下為XuiteExtractor.java

import org.htmlparser.Node;
import org.htmlparser.NodeFilter;
import org.htmlparser.Parser;
import org.htmlparser.tags.BodyTag;
import org.htmlparser.tags.Div;
import org.htmlparser.tags.LinkTag;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;
import java.util.ArrayList;

/**
*
* @author 鳥毅
* @version 1.0
*/
public class XuiteExtractor {
ArrayList <blogpage> blogList = new ArrayList <blogpage>();

public static void main(String[] args) {
try {
XuiteExtractor xe = new XuiteExtractor();
xe.extract("http://blog.xuite.net/efchang/network");
} catch (ParserException e) {
e.printStackTrace();
}
}

public void extract(String blogentry) throws ParserException {
if (blogentry.endsWith("/")) {
/*
// 不要最後的 '/'
*/
blogentry = blogentry.substring(0, blogentry.length() - 1);
}

String lastArticleURL = findLastArticle(blogentry);
BlogPage bp = getBlogContent(lastArticleURL, blogentry);
while (!bp.previousUrl.equals(blogentry)) {
bp = getBlogContent(bp.previousUrl, blogentry);
blogList.add(bp);
System.out.println("目前執行到:" + blogList.size() + " "+ bp.previousUrl);
}

System.out.println("共:" + blogList.size()+"篇");
}

private BlogPage getBlogContent(String url, String blogentry) throws ParserException {
BlogPage blogPage = new BlogPage();
String bodyHtml = getBodyHtml(url); /*
// div class="blogbody" */
blogPage.content = getDivContent(bodyHtml, "blogbody");
/* // 前一則
// div class="selectbar" */
blogPage.previousUrl = blogentry + "/"+findFirstLinkURL(getDivContent(bodyHtml,
"selectbar"));
return blogPage;
}

private String findLastArticle(String blogentry) throws ParserException {
/* // div class="articleSide" */
return "http://blog.xuite.net"+ findFirstLinkURL(getDivContent(getBodyHtml(blogentry),
"articleSide"));
}

public String findFirstLinkURL(String stringHtml) throws ParserException {
String httpurl = null;
Parser p = new Parser();
p.setInputHTML(stringHtml);
NodeList linkList = p.extractAllNodesThatMatch(new NodeFilter() {
private static final long serialVersionUID = 1L;

public boolean accept(Node node) {
return node instanceof LinkTag;
}
});
LinkTag linkTag = (LinkTag) linkList.elementAt(0);
httpurl = linkTag.getLink();
return httpurl;
}

public String getBodyHtml(String url) throws ParserException {
String bodyhtml;
Parser parser = new Parser(url);
NodeList body = parser.extractAllNodesThatMatch(new NodeFilter() {
private static final long serialVersionUID = 1L;

public boolean accept(Node node) {
return node instanceof BodyTag;
}
});

BodyTag bodyTag = (BodyTag) body.elementAt(0);
bodyhtml = bodyTag.getChildrenHTML();
return bodyhtml;
}

public String getDivContent(String stringHtml, String classname)
throws ParserException {
String content = null;
Parser parser = new Parser();
parser.setInputHTML(stringHtml);

NodeList divList = parser.extractAllNodesThatMatch(new NodeFilter() {
private static final long serialVersionUID = 1L;

public boolean accept(Node node) {
return node instanceof Div;
}
});

for (int i = 0; i < divList.size(); i++) {
String divHtml = divList.elementAt(i).getText();

if (divHtml.equals("div class=\"" + classname + "\"")) {
content = divList.elementAt(i).toHtml();
break;
}
}
return content;
}

class BlogPage {
String content;
String previousUrl;
/*
//剩下的自己補
*/
}
}
// end of XuiteExtractor.java

參考資料:
http://blogmarks.net/marks/tag/xml-rpc
http://code.google.com/apis/gdata/blogger.html
http://www.xmlrpc.com/

2 則留言:

小熊子 提到...

請問這個程式有完稿了嗎?

我的 xuite 想要搬家,想說剛好有前人種樹…:p

鳥毅 提到...

停工很久了,我初步的成果本來放在sourceforge,但它的cvs很難連,後來放在google code twblogbackup
目前要用svn下載,我UI還沒完成,你自己去改程式吧,用BlogExtractor這支