title: Go語言實現CSDN文章導出
tags: CSDN,Go
author: Clown95
背景
我一直想把在CSDN上面發佈的文章保存到本地,然後遷移到簡書上,但是CSDN比較閉塞的就是不提供遷移接口。所以我只能尋找第三方工具,但是發現要麼已經失效,要麼就是使用不太友好(對我個人而言),所以我用go語言花了半小時自己擼了一個導出工具。
分析
我們先來分析如何獲取到文章信息:
1.獲取文章地址
首先我們通過文章列表獲取文章的地址。
<h4 class="">
<a href="https://blog.csdn.net/yang731227/article/details/103007106" target="_blank">
<span class="article-type type-1 float-none">原創</span> Beego脫坑(六)——使用模板獲取數據 </a>
</h4>
可以發現 每篇的文章地址 在 h4標籤 下面的a 標籤中。
2.獲取文章內容
接着我們獲取文章的具體內容,CSDN的文章類型有兩種,一種是富文本
類型,還有一種是markdown
。因此我先查看它們顯示的方式是否一樣
首先我查看的是富文本格式的文章,發現內容是在<article class="baidu_pl">
標籤裏面:
<article class="baidu_pl">
<!--忽略文章內容-->
</article>`
接着我們在找一篇markdown格式編寫的文章查看下,發現內容依然是在<article class="baidu_pl">
標籤裏面。
3.查找接口
找到文章內容在什麼標籤內還沒完事,我這人比較喜歡搞事情。因爲我希望能到把博客導成md格式,但是現在網頁上爬取的內容都是html,也就是富文本格式。因此我去尋找go語言 html轉markdown 的庫,但是不幸的是沒有找到。雖然又工具可以幫我完成這份工作,但是我比較懶希望能減少工作就減少一點。
然後我的思維又開始拐彎,如果是md格式的文章,我們在文章的編輯界面不是可以直接獲取到md文本嗎?我在編輯頁面把所有的md文章,都直接保存爲md不就能省很多功夫?
但是接下來我去查看編輯頁面源代碼的時候,沒有發現文章的內容。這時候不要慌!那麼它肯定是通過某個接口來加載數據的,查看下XHR
果然發現了一個接口,參數是文章ID。
(該接口富文本內容已失效)https://mp.csdn.net/mdeditor/getArticle?id=103007106
新的接口爲:
https://blog-console-api.csdn.net/v1/editor/getArticle?id=103007106
以此類推,我再去打開富文本文章的編輯界面,我希望它也是通過接口加載數據的,這樣在進行數據處理的時候我就能夠省很多代碼。然鵝富文本格式並沒有接口數據。
但是我這個人比較軸,我就想試下,我把接口ID改成富文本文章的ID,看看它是否能夠爲我傳來數據,果然有的時候搞開發就要有折騰的精神,我成功得到了文章內容 。
https://mp.csdn.net/mdeditor/getArticle?id=82253319
https://blog-console-api.csdn.net/v1/editor/getArticle?id=82253319
兩個數據不一樣的地方就是,如果是md文章,它 markdowncontent
裏面的內容是 md文本,如果是富文本的文章,它markdowncontent
屬性就爲空,因此我們在導出文本的時候,如果遇到markdowncontent
不爲空就獲取markdowncontent
的內容並保存爲.md ,如果遇到爲空的情況,就獲取content
內容並保存爲.html
4.整理
現在我們已知 https://blog-console-api.csdn.net/v1/editor/getArticle?id=xxxx
這個接口可以獲取到文章的信息,參數是文章ID。
所以接下來我們需要做的就是,通過爬蟲模擬csdn登錄狀態,獲取所爬的博客中每篇文章的ID,傳遞給接口,獲取文章標題和內容,並根據格式保存不同的文件。
實現代碼
使用到的第三方庫
- 使用
goconfig
庫,來加載配置信息,用來獲取博客地址、文章列表頁數和Cookie。 - 使用
goquery
進行爬蟲
model.go
package models
import (
"CSDN/utils"
"github.com/Unknwon/goconfig"
)
var ArrDetailID []string //保存文章ID
var BlogUrl string // 配置博客地址
var Cookie string // 配置Cookie
var TotalPage int // 博客文章列表總頁數
type Article struct { //用來解析json
Data struct {
// ID string `json:"id"`
Title string `json:"title"`
// Articleedittype int `json:"articleedittype"`
// Description string `json:"description"`
Content string `json:"content"`
Markdowncontent string `json:"markdowncontent"`
/*
Tags string `json:"tags"`
Categories string `json:"categories"`
Channel string `json:"channel"`
Type string `json:"type"`
Status int `json:"status"`
ReadType string `json:"readType"`
UseVipView int `json:"use_vip_view"`
UseFansView int `json:"use_fans_view"`
Reason string `json:"reason"`
ResourceURL string `json:"resource_url"`
OriginalLink string `json:"original_link"`
AuthorizedStatus bool `json:"authorized_status"`
CheckOriginal bool `json:"check_original"`
SelfRecommend bool `json:"selfRecommend"`
*/
} `json:"data"`
}
func init() {
runpath := utils.GetRunPath()
cfg, err := goconfig.LoadConfigFile(runpath + "/conf/conf.ini")
if err != nil {
panic("沒有加載到配置文件")
}
BlogUrl, err = cfg.GetValue("csdn", "blogurl")
if err != nil {
panic("blogurl錯誤")
}
Cookie, err = cfg.GetValue("csdn", "cookie")
if err != nil {
panic("cookie錯誤")
}
TotalPage, err = cfg.Int("csdn", "totalpage")
if err != nil {
panic("totalpage錯誤")
}
}
csdn.go
package transaction
import (
"CSDN/models"
"encoding/json"
"github.com/PuerkitoBio/goquery"
"io/ioutil"
"log"
"net/http"
"strings"
"time"
)
func GetHtml(url string) *http.Response {
client := &http.Client{ //要管理HTTP客戶端的頭域、重定向策略和其他設置,創建一個Client
Timeout: time.Second * 2,
}
req, err := http.NewRequest("GET", url, nil) //NewRequest使用指定的方法、網址和可選的主題創建並返回一個新的*Request。
if err != nil {
log.Println(err)
}
req.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36") //模擬瀏覽器User-Agent
req.Header.Add("Cookie", models.Cookie)
resp, err := client.Do(req) //Do方法發送請求,返回HTTP回覆
if err != nil {
log.Println(err)
}
return resp //返回網頁響應
}
func GetdetailID(resp *http.Response) {
defer resp.Body.Close()
dom, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
log.Fatalln(err)
}
dom.Find("h4").Each(func(i int, selection *goquery.Selection) {
time.Sleep(1 * time.Second) //防止訪問次數過於頻繁
detailurl, _ := selection.Find("a").Attr("href")
index := strings.LastIndex(detailurl, "/")
models.ArrDetailID = append(models.ArrDetailID, detailurl[index+1:])
})
}
func ParseArticleJson(jsonurl string) (string, string) {
resp := GetHtml(jsonurl)
defer resp.Body.Close()
resp_byte, _ := ioutil.ReadAll(resp.Body)
respHtml := string(resp_byte)
var article models.Article
json.Unmarshal([]byte(respHtml), &article)
title := article.Data.Title
content := article.Data.Content
markdown := article.Data.Markdowncontent
if markdown == "" {
return title + ".html", content
} else {
return title + ".md", markdown
}
}
utils.go
package utils
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
)
func WriteWithIoutil(name, content string) {
data := []byte(content)
if ioutil.WriteFile(name, data, 0644) == nil {
fmt.Println("導出成功:", name)
}
}
// 獲取程序當前運行路徑
func GetRunPath() string{
file, _ := exec.LookPath(os.Args[0])
path, _ := filepath.Abs(file)
index := strings.LastIndex(path, string(os.PathSeparator))
runpath :=path[:index]
return runpath
}
main.go
package main
import (
"CSDN/models"
"CSDN/transaction"
"CSDN/utils"
"fmt"
"time"
)
func main() {
fmt.Println("設置成功,開始導出blog,時間較長請等待!!")
for i := 1; i <= models.TotalPage; i++ {
time.Sleep(800) //設置延時
url := fmt.Sprintf("%s/article/list/%d", models.BlogUrl, i)
resq := transaction.GetHtml(url)
transaction.GetdetailID(resq)
}
runpath := utils.GetRunPath()
for i := 0; i < len(models.ArrDetailID); i++ {
//jsonurl := fmt.Sprintf("https://mp.csdn.net/mdeditor/getArticle?id=%s", models.ArrDetailID[i])
jsonurl := fmt.Sprintf("https://blog-console-api.csdn.net/v1/editor/getArticle?id=%s", models.ArrDetailID[i])
name, content := transaction.ParseArticleJson(jsonurl)
utils.WriteWithIoutil(runpath+"/"+name, content)
time.Sleep(1000) //設置延時
}
}
conf.ini 配置文件
[csdn]
blogurl = https://blog.csdn.net/yang731227 ;博客地址
totalpage = 7 ;博客文章列表數
cookie = ;爬取賬號的cookie
導出效果
下載
土豪通道:
- win: https://download.csdn.net/download/yang731227/12157060
- mac :https://download.csdn.net/download/yang731227/12154880
或者直接到github上面下載:
https://github.com/Clown95/CSDN-Blog-Export
如果你覺得對你有幫助給個star唄