oschina openapi 应用:博客搬家

一、功能说明本程序支持将csdn ,cnblogs, 51cto, iteye个人博客列表下载所有博文,选择导入到该用户的oschina博客。


二、使用说明

1.进入博客搬家页面:http://move.oschina.net

2.点击左上角使用oschina账号登陆,

3.输入csdn或cnblogs或51cto或iteye个人博客列表url或者某篇博客url,

4.点击抓取,

5.点击导入。

oschina openapi 应用:博客搬家_第1张图片

oschina openapi 应用:博客搬家_第2张图片

oschina openapi 应用:博客搬家_第3张图片


三、如何实现

本程序不用数据库,只用一个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 这个地址会看到:

oschina openapi 应用:博客搬家_第4张图片分别对应oauth2_authorize要求的参数






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或提出宝贵意见。

由于本人资历尚浅,如有不足,敬请各位不吝赐教。

你可能感兴趣的:(git,oschina,webmagic,openapi,博客搬家)