http://my.oschina.net/oscfox/blog/194507
一、功能说明:本程序支持将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"
)){
return
null
;
}
return
new
CnBlogPageProcesser(url);
}
else
if
(url.contains(
"blog.csdn.net"
)){
return
null
;
}
return
new
CsdnBlogPageProcesser(url);
}
else
if
(url.contains(
"blog.51cto.com"
)){
return
null
;
}
return
new
CtoBlogPageProcesser(url);
}
else
if
(url.contains(
"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
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