csdn博客导出为markdown格式

更优质的阅读体验

背景

不想继续在粪坑网站写博客了,于是自己用Vuepress搭了一个静态博客,配合github pages和服务器使用效果还挺不错。但之前写的博客基本全在CSDN,想导出来,人工一篇一篇复制就太累了,于是用go写了个爬虫自动把自己csdn上的所有文章的markdown源码导出并迁移到新站点上。这也应该是我CSDN上的最后一篇博客

源码和可执行文件

思路

  • 个人主页https://blog.csdn.net/Apale_8可以获取博客列表
  • 编辑页可以查看markdown源码
  • 导出为统一的格式(标题、写作时间、分类、标签和内容等信息)
  • 转换为VuePress的格式

博客列表

可以观察到博客列表是动态加载的,因此一定有接口
在这里插入图片描述
f12查看网络,先清空之前的所有请求,然后向下滚动,可以看到一个这样的接口
在这里插入图片描述
在这里插入图片描述

参数很简单,page和size,所以直接以page=1,size取一个>=自己博客数量的数字即可获取到所有博客的链接

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

const listApi = `https://blog.csdn.net/community/home-api/v1/get-business-list?page=%d&size=%d&businessType=lately&noMore=false&username=%s`

func getArticleList(username string) []Meta {
	resp, err := http.Get(fmt.Sprintf(listApi, 1, blogNum, username))
	if err := err; err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	var respData ListRespData
	b, err := io.ReadAll(resp.Body)
	if err := err; err != nil {
		panic(err)
	}
	if err := json.Unmarshal(b, &respData); err != nil {
		panic(err)
	}
	fmt.Printf("读取博客列表成功, 总共: %d 篇\n", len(respData.Data.List))
	return respData.Data.List
}

type ListRespData struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Data    Data   `json:"data"`
}

type Meta struct {
	Type         string `json:"type"`
	FormatTime   string `json:"formatTime"`
	Title        string `json:"title"`
	Description  string `json:"description"`
	HasOriginal  bool   `json:"hasOriginal"`
	DiggCount    int    `json:"diggCount"`
	CommentCount int    `json:"commentCount"`
	PostTime     int64  `json:"postTime"`
	CreateTime   int64  `json:"createTime"`
	URL          string `json:"url"`
	ArticleType  int    `json:"articleType"`
	ViewCount    int    `json:"viewCount"`
	Rtype        string `json:"rtype"`
}
type Data struct {
	List  []Meta      `json:"list"`
	Total interface{} `json:"total"`
}

(代码里resp的几个结构体直接拷贝网页请求用工具生成即可)

博客内容

博客内容要从编辑页面获取,因此需要登录

先f12从请求里面复制一下cookie备用
在这里插入图片描述

然后在编辑页面按f12找找接口,是可以找到getArticle这个接口的
https://bizapi.csdn.net/blog-console-api/v3/editor/getArticle?id=119971640&model_type=

但遗憾的是这个接口做了签名认证,读懂前端js的签名逻辑对我还是有点困难,遂放弃

柳暗花明

github搜了一圈,发现有人用另一个不需要签名的接口爬过csdn markdown,应该是一个旧版的接口,但还能用
https://blog-console-api.csdn.net/v1/editor/getArticle?id=%s

于是成功地爬到了博客内容

需要注意的是,富文本编辑器和markdown编辑器的内容,在不同的字段,需要判断下

const (
	articleApi = `https://blog-console-api.csdn.net/v1/editor/getArticle?id=%s`
)

func getArticle(meta Meta) Article {
	articleData := getArticleContent(meta)
	content := articleData.Markdowncontent
	contentType := "markdown"
	if content == "" { // 如果没有markdown内容, 就是用富文本编辑器编写的
		content = articleData.Content
		contentType = "HTML"
	}

	return Article{
		Content:     content,
		Title:       articleData.Title,
		CreateTime:  meta.CreateTime,
		FormatTime:  meta.FormatTime,
		Categories:  articleData.Categories,
		Tags:        strings.Split(articleData.Tags, ","),
		ContentType: contentType,
	}
}

func getArticleContent(meta Meta) ArticleData {
	client := &http.Client{}
	req, err := http.NewRequest("GET", fmt.Sprintf(articleApi, meta.URL[strings.LastIndex(meta.URL, "/")+1:]), nil)
	req.Header.Set("cookie", cookie)
	resp, err := client.Do(req)
	if err := err; err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	var respData ArticleRespData
	if err := json.NewDecoder(resp.Body).Decode(&respData); err != nil {
		panic(err)
	}
	return respData.Data
}

type ArticleRespData struct {
	Code int         `json:"code"`
	Msg  string      `json:"msg"`
	Data ArticleData `json:"data"`
}
type ArticleData struct {
	ArticleID        string        `json:"article_id"`
	Title            string        `json:"title"`
	Description      string        `json:"description"`
	Content          string        `json:"content"`
	Markdowncontent  string        `json:"markdowncontent"`
	Tags             string        `json:"tags"`
	Categories       string        `json:"categories"`
	Type             string        `json:"type"`
	Status           int           `json:"status"`
	ReadType         string        `json:"read_type"`
	Reason           string        `json:"reason"`
	ResourceURL      string        `json:"resource_url"`
	OriginalLink     string        `json:"original_link"`
	AuthorizedStatus bool          `json:"authorized_status"`
	CheckOriginal    bool          `json:"check_original"`
	EditorType       int           `json:"editor_type"`
	Plan             []interface{} `json:"plan"`
	VoteID           int           `json:"vote_id"`
	ScheduledTime    int           `json:"scheduled_time"`
	Level            string        `json:"level"`
	CoverType        string        `json:"cover_type"`
	CoverImages      []interface{} `json:"cover_images"`
}

type Article struct {
	Title       string
	CreateTime  int64
	FormatTime  string
	Categories  string
	Tags        []string
	ContentType string
	Content     string // 有markdown则存markdown,没有则存html
}

转换格式

VuePress里面格式是这样的
在这里插入图片描述

简单转一下即可

输出的文件名用了随机串,因为中文名称在编译的时候有点问题

func toMarkdownForVuePress(a Article, path string) {
	if path[:len(path)-1] != "/" {
		path += "/"
	}
	content := fmt.Sprintf(vuePressFormat, a.Title, strings.ReplaceAll(a.FormatTime, ".", "-"), genTags(a.Tags...), genTags(a.Categories), a.Content)

	os.WriteFile(path+uuid.New()+".md", []byte(content), 0o644) // 写入文件, uuid作为文件名,因为中文名可能有问题
}
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值