一、功能说明:本程序支持将csdn ,cnblogs, 51cto, iteye个人博客列表下载所有博文,选择导入到该用户的oschina博客。
二、使用说明:
1.进入博客搬家页面:http://move.oschina.net
2.点击左上角使用oschina账号登陆,
3.输入csdn或cnblogs或51cto或iteye个人博客列表url或者某篇博客url,
4.点击抓取,
5.点击导入。
三、如何实现:
本程序不用数据库,只用一个Map存储用户爬取的博客信息。爬虫用git.oschina上的开源垂直爬虫:webmagic 感谢黄亿华 。登陆用oschina的openAPI认证功能。
1.存储
博客链接:
/** * 爬虫获取的博客列表 * @author oscfox * */ public class LinksList { //用户名,对应一个用户列表,如果用户为新用户则put新的列表 private static Map<String, ConcurrentHashMap<String, BlogLink>> linkMap = new ConcurrentHashMap <String,ConcurrentHashMap<String, BlogLink>>(); public static void addLinks(String user, List<BlogLink> links) { ConcurrentHashMap<String,BlogLink> linkList; if(linkMap.containsKey(user)){ linkList= linkMap.get(user); } else{ linkList = new ConcurrentHashMap<String,BlogLink>(); } //put links 去重复 for(int i=0; i<links.size(); ++i){ String key = links.get(i).getLink(); if(linkList.containsKey(key)){ //重复,不提交 continue; } linkList.put(key, links.get(i)); } linkMap.put(user, linkList); } public static void clearLinkList(String user) { linkMap.remove(user); } public static List<BlogLink> getLinkList(String user) { ConcurrentHashMap<String, BlogLink> hash; if(linkMap.containsKey(user)){ hash = linkMap.get(user); return new ArrayList<BlogLink>(hash.values()); //hash to list } return null; } }
博客列表:
/** * 爬虫获取的博客列表 * @author oscfox * */ public class BlogList { //用户名,对应一个用户列表,如果用户为新用户则put新的列表 private static Map<String, Blog> blogMap = new ConcurrentHashMap <String,Blog>(); public static void addBlog(Blog blog) { if(blogMap.containsKey(blog.getLink())){ //已存在博客,有异常,没处理 blogMap.put(blog.getLink(), blog); } else{ blogMap.put(blog.getLink(), blog); } } public static Blog getBlog(String link) { if(blogMap.containsKey(link)){ return blogMap.remove(link); } return null; } }
2.爬虫
webmagic用起来很方便,我只继承了pageProcessor 接口作为不同博客的抓取逻辑以及Pipeline接口作抓取后续处理。然后以下一行代码就可以开始抓取
Spider.create(pageProcessor).addUrl(url).addPipeline(new BlogPipeline(user)).run();
继承的pageProcessor主要是重写process 方法,根据不同博客网站标签逻辑抓取内容。然后对博客里有代码的部分(主要是pre标签里的)转换为osc博客的代码类型。方法很简单,只是简单替换一下标签属性而已。
/** * 博客爬虫逻辑 * @author oscfox * @date 20140120 */ public class BlogPageProcessor implements PageProcessor{ protected Site site = new Site(); protected String url; protected String blogFlag; //博客url的内容标志域 protected String name; //博客原url 的名字域 protected List<String> codeBeginRex = new ArrayList<String>(); //代码过滤正则表达式 protected List<String> codeEndRex = new ArrayList<String>(); //代码过滤正则表达式 protected String linksRex; //链接列表过滤表达式 protected String titlesRex; //title列表过滤表达式 protected String PagelinksRex; //类别页列表过滤表达式 protected String contentRex; //内容过滤表达式 protected String titleRex; //title过滤表达式 protected String tagsRex; //tags过滤表达式 protected Hashtable<String, String> hashtable; //代码class映射关系 /** * 抓取博客内容等,并将博客内容中有代码的部分转换为oschina博客代码格式 */ @Override public void process(Page page) { if(url.contains(blogFlag)){ 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(linksRex).all(); List<String> titles = page.getHtml().xpath(titlesRex).all(); page.putField("titles", titles); page.putField("links", links); List<String> Pagelinks = page.getHtml().links().regex(PagelinksRex).all(); page.addTargetRequests(Pagelinks); } /** * 抓取博客内容 * @param page */ private void getPage(Page page){ String title = page.getHtml().xpath(titleRex).toString(); String content = page.getHtml().$(contentRex).toString(); String tags = page.getHtml().xpath(tagsRex).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(hashtable); //设置工具类映射关系 String oscContent = oscReplacer.replace(codeBeginRex, codeEndRex, content); //处理代码格式 page.putField("content", oscContent); page.putField("title", title); page.putField("tags", tags); }
例如csdn博客抓取只需要继承BlogPageProcessor
/** * csdn博客爬虫逻辑 * @author oscfox * @date 20140114 */ public class CsdnBlogPageProcesser extends BlogPageProcessor{ public CsdnBlogPageProcesser(String url) { site = Site.me().setDomain("blog.csdn.net"); site.setSleepTime(1); blogFlag="/article/details/"; //博客原url 的名字域 codeBeginRex.add("<pre.*?class=\"(.+?)\".*?>"); //代码过滤正则表达式 //<textarea class="java" cols="50" rows="15" name="code"> codeBeginRex.add("<textarea.*?class=\"(.+?)\".*?>" ); codeEndRex.add("</textarea>"); //</textarea> linksRex="//div[@class='list_item article_item']/div[@class='article_title']/h3/span/a/@href"; //链接列表过滤表达式 titlesRex="//div[@class='list_item article_item']/div[@class='article_title']/h3/span/a/text()";//title列表过滤表达式 contentRex="div.article_content"; //内容过滤表达式 titleRex="//div[@class='details']/div[@class='article_title']/h3/span/a/text()"; //title过滤表达式 tagsRex="//div[@class='tag2box']/a/text()"; //tags过滤表达式 this.url=url; if(!url.contains(blogFlag)){ name = url.split("/")[url.split("/").length - 1]; } //http://blog.csdn.net/cxhzqhzq/article/list/2 PagelinksRex="http://blog\\.csdn\\.net/"+name+"/article/list/\\d+"; //类别页列表过滤表达式 initMap(); } @Override public void process(Page page) { super.process(page); } @Override public Site getSite() { return super.getSite(); } /** * 初始化映射关系,只初始化代码类型同样而class属性不一样的。 * 分别为:csdn, osc */ private void initMap() { hashtable = new Hashtable<String,String>(); //代码class映射关系 hashtable.put("csharp", "c#"); hashtable.put("javascript", "js"); hashtable.put("objc", "cpp"); }
Pipeline只是简单的生成blog bean 然后增加至blogList
/** * 成功blog并保存至BlogList * @author oscfox * @date */ public class BlogPipeline implements Pipeline{ private Map<String, Object> fields = new HashMap<String, Object>(); private String user; public BlogPipeline(String user){ this.user = user; } @SuppressWarnings("unchecked") @Override public void process(ResultItems resultItems, Task task) { fields = resultItems.getAll(); if((boolean)fields.get("getlinks")){ List<String> titles = (ArrayList<String>)fields.get("titles"); List<String> links = (ArrayList<String>)fields.get("links"); if(null == titles || null == links){ return; } List<BlogLink> linklist = new ArrayList<BlogLink>(); for(int i=0; i<titles.size(); ++i){ BlogLink blogLink = new BlogLink(); blogLink.setTitle(titles.get(i)); blogLink.setLink(links.get(i)); linklist.add(blogLink); } LinksList.addLinks(user, linklist); } else{ Blog oscBlog = null; try { oscBlog = new Blog(fields); oscBlog.setLink(resultItems.getRequest().getUrl()); } catch (Exception e) { //e.printStackTrace(); return ; } BlogList.addBlog(oscBlog); List<BlogLink> links=new ArrayList<BlogLink>(); BlogLink blogLink = new BlogLink(); blogLink.setLink(oscBlog.getLink()); blogLink.setTitle(oscBlog.getTitle()); links.add(blogLink); LinksList.addLinks(user, links); } } }
所以如果需要抓取更多的博客网站,只需要继承pageProcessor重写process方法就行了。当然,spider选择哪个pageProcessor还得判断一下。
/** * //根据url选择博客类型 * @param url * @return */ public static PageProcessor getBlogSitePageProcessor(String url){ if(url.contains("www.cnblogs.com")){ if(url.equals("http://www.cnblogs.com")){ return null; } return new CnBlogPageProcesser(url); }else if(url.contains("blog.csdn.net")){ if(url.equals("http://blog.csdn.net")){ return null; } return new CsdnBlogPageProcesser(url); }else if(url.contains("blog.51cto.com")){ if(url.equals("http://blog.51cto.com")){ return null; } return new CtoBlogPageProcesser(url); }else if(url.contains("iteye.com")){ if(url.equals("http://www.iteye.com")){ return null; } return new IteyeBlogPageProcesser(url); }else { return null; } }
3.OSC openApi
本程序用了4个OSC 的openApi (点击以下api名可跳转到OSC API文档):
oauth2_authorize OpenAPI 授权登录页面
oauth2_token authorization_code 方式获取 AccessToken
openapi_user 获取当前登录用户的账户信息
blog_pub 发布博客
oauth2_authorize 只支持get 方法,传入的参数是需要先从OSC openAP创建应用经审核的应用信息。创建应用很简单,填写一些信息就行了,这里就不介绍了,主要注意回调地址别写错就好。通过审核后在http://www.oschina.net/openapi/client 这个地址会看到:
|
|
|
|
|
client_id | true | string | OAuth2客户ID | |
response_type | true | string | 返回数据类型 | code |
redirect_uri | true | string | 回调地址 | |
state | false | string | 可选参数 |
|
回调后获取code值
https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz
然后根据code 调用oauth2_token获得access_token,我用httpclient 模拟post请求的方式来调用oauth2_token
/** * oschina验证api * @author oscfox * */ public class Oauth2Api { /** * 根据code获取oschina的 token * @param code * @return */ public static String getAccess_token(String code){ HttpClient client = new HttpClient(); //User-Agent client.getParams().setParameter(HttpMethodParams.USER_AGENT, "Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.1.2) Gecko/20090803"); AppConfigTool configTool = new AppConfigTool(); String token_href = configTool.getConfig("osc_host") +configTool.getConfig("oauth2_token") +"?client_secret="+configTool.getConfig("client_secret") +"&client_id="+configTool.getConfig("client_id") +"&grant_type=authorization_code" +"&redirect_uri="+configTool.getConfig("redirect_uri") +"&code="+code; HttpMethod method = new GetMethod(token_href); String responsestr = new String(); try { client.executeMethod(method); responsestr = new String(method.getResponseBodyAsString()); } catch (HttpException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } method.releaseConnection(); Gson gson = new Gson(); try { JsonData jsonData = gson.fromJson(responsestr, JsonData.class); return jsonData.getToken(); } catch (Exception e) { // TODO: handle exception } return null; }
根据access_token
/** * oschina 获取用户信息api * @author oscfox * */ public class UserApi { private static String type="json"; /** * 根据access_token 获取用户信息 * @param access_token * @return */ public static User getUser(String access_token) { HttpClient client = new HttpClient(); //User-Agent client.getParams().setParameter(HttpMethodParams.USER_AGENT, "Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.1.2) Gecko/20090803"); AppConfigTool configTool = new AppConfigTool(); PostMethod method = new PostMethod(configTool.getConfig("osc_host")+ configTool.getConfig("openapi_user")); method.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET,"utf-8"); NameValuePair access_token_ = new NameValuePair("access_token",access_token); NameValuePair type_ = new NameValuePair("type",type); method.setRequestBody(new NameValuePair[] { access_token_,type_}); String responsestr = ""; try { client.executeMethod(method); responsestr = new String(method.getResponseBodyAsString()); } catch (HttpException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } method.releaseConnection(); Gson gson = new Gson(); try { User user = gson.fromJson(responsestr, User.class); return user; } catch (Exception e) { // TODO: handle exception } return null; } }
四、源码:
更多代码请看git地址:http://git.oschina.net/yashin/MoveBlog
欢迎各位OSCer 提交代码或BUG或提出宝贵意见。
由于本人资历尚浅,如有不足,敬请各位不吝赐教。