oschina openapi 应用:博客搬家

http://my.oschina.net/oscfox/blog/194507

本文介绍基于osc openapi 以及 webmagic爬虫 开发的oschina博客搬家应用。 本应用作为oschina openAPI的一个实用demo,向各位OSCer 展示OSC openAPI 如何使用,以及可以实现什么功能。

一、功能说明本程序支持将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.存储

博客链接:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
  * 爬虫获取的博客列表
  * @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 ;
     }
     
}

博客列表:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
  * 爬虫获取的博客列表
  * @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接口作抓取后续处理。然后以下一行代码就可以开始抓取

1
Spider.create(pageProcessor).addUrl(url).addPipeline( new  BlogPipeline(user)).run();

继承的pageProcessor主要是重写process 方法,根据不同博客网站标签逻辑抓取内容。然后对博客里有代码的部分(主要是pre标签里的)转换为osc博客的代码类型。方法很简单,只是简单替换一下标签属性而已。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/**
  * 博客爬虫逻辑
  * @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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
  * 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
  * 成功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还得判断一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
     /**
  * //根据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 只支持get 方法,传入的参数是需要先从OSC openAP创建应用经审核的应用信息。创建应用很简单,填写一些信息就行了,这里就不介绍了,主要注意回调地址别写错就好。通过审核后在http://www.oschina.net/openapi/client 这个地址会看到:

分别对应oauth2_authorize要求的参数






client_idtruestringOAuth2客户ID
response_typetruestring返回数据类型code
redirect_uritruestring回调地址
statefalsestring可选参数



回调后获取code值

https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz

然后根据code 调用oauth2_token获得access_token,我用httpclient 模拟post请求的方式来调用oauth2_token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
  * 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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
  * 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/oscfox/MoveBlog


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值