首页 > 编程语言 >Go编程实战:博客备份

Go编程实战:博客备份

时间:2023-02-26 09:45:12浏览次数:67  
标签:xml string err 备份 编程 go Go com

在 “博客备份” 一文中,编写了两个 python 脚本,然后使用一个命令行将博客备份流程串联起来。这里,我们将使用 Go 来实现。

不太熟悉 Go 语言的读者,可以阅读之前写的两篇文章:“Go语言初尝”,“Go 面向对象简明教程”。

整体设计

设计五个 go 函数,分别实现如下功能:

  • GetFiles: 从命令行参数获取博文备份文件路径。
  • ReadXml: 读取博客 xml 文件内容,返回博文对象。
  • GetMdLinks:从博文对象中取出博文链接对象数组。
  • WriteMarkdown: 根据博文链接对象,下载 HTML 文件,并转换成 markdown 文件。
  • SequentialRun: 使用串行方式,将以上函数串联成一个完整的流程。

为了避免重复在文中粘贴代码,完整程序在文后给出,读者可以复制出来,对照解读来看。

文件内容

为了调试程序,我们仅选取两篇博文内容组成 XML 文件。

/tmp/cnblogs_backup.xml

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"
    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
    xmlns:slash="http://purl.org/rss/1.0/modules/slash/">
    <channel>
        <title>博客园-编程大观园</title>
        <link>https://www.cnblogs.com/lovesqcc/</link>
        <description>Enjoy programming, Enjoy life ~~~ 设计,诗歌与爱</description>
        <language>zh-cn</language>
        <lastBuildDate>Tue, 07 Feb 2023 14:39:24 GMT</lastBuildDate>
        <pubDate>Tue, 07 Feb 2023 14:39:24 GMT</pubDate>
        <ttl>60</ttl>
        <item>
            <title>专业精进之路:构建扎实而有实效的编程知识体系</title>
            <link>http://www.cnblogs.com/lovesqcc/archive/2023/02/01/17084292.html</link>
            <dc:creator>琴水玉</dc:creator>
            <author>琴水玉</author>
            <pubDate>Wed, 01 Feb 2023 14:05:00 GMT</pubDate>
            <guid>http://www.cnblogs.com/lovesqcc/archive/2023/02/01/17084292.html</guid>
            <description>
                <![CDATA[ blog content ]]>
            </description>
        </item>
        <item>
            <title>知乎一答:什么样的知识是有价值的</title>
            <link>http://www.cnblogs.com/lovesqcc/archive/2023/01/29/17072238.html</link>
            <dc:creator>琴水玉</dc:creator>
            <author>琴水玉</author>
            <pubDate>Sun, 29 Jan 2023 03:29:00 GMT</pubDate>
            <guid>http://www.cnblogs.com/lovesqcc/archive/2023/01/29/17072238.html</guid>
            <description>
                <![CDATA[ blog content  ]]>
            </description>
        </item>
    </channel>
</rss>

GetFiles

GetFiles 的作用是从命令行参数获取博客备份文件。支持读取多个博客备份文件。也可以用于测试。比如测试文件不存在的情形。

ReadXML

解析 XML 的函数见: ReadXml。这里的重点是,要建立与 XML 内容结构对应的 struct 。

要点如下:

  1. XMLName xml.Name 要定义的元素是包含整个 XML 内容的顶层元素(除开 xml 元素外)。比如,这个 xml 文件的顶层元素是 rss,那么需要定义:XMLName xml.Name xml:"rss"。其下是这个 XML 嵌套的具体元素内容。
type BlogRss struct {
   XMLName xml.Name `xml:"rss"`
   Channel *BlogChannel `xml:channel`
}
  1. 根据嵌套内容结构,定义对应的嵌套对象:BlogRss => BlogChannel => BlogItem。 每个 struct 对象的属性就是这个对象的子元素。属性首字母大写,是为了能够直接引用对象的字段。
  2. `xml:"item"` 不要写成 `xml:item` 。 item 要用双引号包起来,不然会解析不出来。

这个主要就是结构体遍历操作。对于大量博文来说,直接传入结构体对象切片是开销较大的,可以改成结构体切片指针类型。这里存在一个优化点,读者可以自行尝试下。

WriteMarkdown

这个函数根据指定链接,下载 HTML 文件,使用 goquery 解析出 HTML 博文内容块,然后使用 html-to-markdown 来生成 Markdown 文件。

SequentialRun

这个函数的作用就是串联逻辑以上逻辑单元,构建完整流程。

这里展示了程序模块化的优势。当每个逻辑单元设计成短小而正交的,那么整个流程看起来会很清晰。对于编程初学者,这一点尤为重要。

报错及解决

Go初学者容易犯的错误

  1. 习惯于写 links = xxx。 报错:undefined: links。 应该写成 links := xxx 。 Go 的赋值语句是 := 。
  2. 未使用的变量。报错:b declared and not used。 Go 不允许变量定义了但未使用,否则报编译错误。
  3. 变量声明顺序写反。报错:syntax error: unexpected ], expected operand。这是因为,make([]string, 32) 写成了 make(string[], 32)。变量声明顺序是 变量名 变量类型。
  4. 变量名和方法名大写,表示变量和方法的包可见权限。很多习惯于写 C 和 Java 的人需要一个适应过程。
  5. 丢掉返回值。append(links, item.Link) 。报错: append(links, item.Link) (value of type []string) is not used。这是因为 append 返回一个切片,而这个切片被丢弃了。需要写成: links = append(links, item.Link)。
  6. 不允许导入未使用的包。 报错:"github.com/JohannesKaufmann/html-to-markdown" imported as md and not used
  7. 多重赋值错误。 报错:Go assignment mismatch: 1 variable but converter.ConvertString returns 2 values。Go 的很多方法都定义了多个返回值,因为要包含错误信息 err。需要写成 markdown, err := converter.ConvertString(postbody.Text())。
  8. 方法签名的变量定义写错。比如 func SequentialRunMultiple(files []string) 写成了 func SequentialRunMultiple(files) 或者写成了 func SequentialRunMultiple(string[] files) 或者写成了 func SequentialRunMultiple([]string files)。这几种我都犯过了。

Go 的语法可以说是一个“大杂烩”。虽然融合了很多语言的优点,但感觉缺乏一致性。为了编译省事,把很多负担都转移给了开发者。虽说部分做法初衷是好的。作为一位熟悉多门编程语言的多年开发者,我也感觉不太适应,很容易写错。需要一点时间扭转过来。语言使用的不适应感,会阻滞这门语言的快速普及化。将来再出现一门更易于使用的编程语言,这门语言虽然有先发优势,也会被埋没成为小众语言,即使是大牛操刀而成。

依赖报错及解决

与 python 相比, Go 的依赖管理也令人不满意。python 只要安装了 pip,使用 pip install 安装模块,就可以直接使用了。而 Go 报各种问题,且报错不友好,需要去了解详细信息,对初学者不友好。

引入 goquery 后执行 go run blog_backup.go 报错:

go run blog_backup.go
blog_backup.go:9:5: no required module provides package github.com/PuerkitoBio/goquery: go.mod file not found in current directory or any parent directory; see 'go help modules'

安装 goquery,执行如下命令报错:

➜  basic go get -u github.com/PuerkitoBio/goquery
go: go.mod file not found in current directory or any parent directory.
	'go get' is no longer supported outside a module.
	To build and install a command, use 'go install' with a version,
	like 'go install example.com/cmd@latest'
	For more information, see https://golang.org/doc/go-get-install-deprecation
	or run 'go help get' or 'go help install'.

执行 如下命令又报错:

➜  basic go install github.com/PuerkitoBio/goquery@latest 
go: downloading github.com/PuerkitoBio/goquery v1.8.1
go: downloading github.com/andybalholm/cascadia v1.3.1
go: downloading golang.org/x/net v0.7.0
package github.com/PuerkitoBio/goquery is not a main package

解决办法:退回到 go 项目根目录 gostudy,执行:

go mod init gostudy
go get github.com/PuerkitoBio/goquery
go get github.com/JohannesKaufmann/html-to-markdown

完整程序

执行:

go run blog_backup.go /tmp/cnblogs_backup.xml
或者
go build blog_backup.go
./blog_backup /tmp/cnblogs_backup.xml

blog_backup.go

package main

import (
    "fmt"
    "os"
    "strings"
    "encoding/xml"
    "io/ioutil"
    "net/http"
    "github.com/PuerkitoBio/goquery"
    "github.com/JohannesKaufmann/html-to-markdown"
)

type BlogRss struct {
   XMLName xml.Name `xml:"rss"`
   Channel *BlogChannel `xml:channel`
}

type BlogChannel struct {
   XMLName xml.Name `xml:"channel"`
   Title string `xml:"title"`
   Link  string `xml:"link"`
   Description string `xml:"description"`
   Language string `xml:"language"`
   LastBuildDate string `xml:"lastBuildDate"`
   PubDate string `xml:"pubDate"`
   Ttl     int   `xml:"ttl"`
   Items    []BlogItem  `xml:"item"`
}

type BlogItem struct {
    Title string `xml:"title"`
    Link  string `xml:"link"`
    Creator string `xml:"dc:creator"`
    Author  string `xml:"author"`
    PubDate string `xml:"pubDate"`
    guid    string `xml:"guid"`
    Description string `xml:description`
}

type MarkdownFile struct {
    Title string
    Link  string
}

func ReadXml(fpath string) (*BlogRss, error) {
    fp,err := os.Open(fpath)
    if err != nil {
        fmt.Printf("error open file: %s error: %v", fp, err)
        return nil, err
    }
    defer fp.Close()

    data, err := ioutil.ReadAll(fp)
    if err != nil {
        fmt.Printf("error read file: %s error: %v", fpath, err)
        return nil, err
    }
 
    blogRss := BlogRss{}
    err = xml.Unmarshal(data, &blogRss)
    if err != nil {
        fmt.Printf("error unmarshal xml data: %v error: %v", data, err)
        return nil, err
    }
    blogchannelp := blogRss.Channel
    fmt.Printf("%+v\n", *blogchannelp)
    blogitems := (*blogchannelp).Items
    for _, item := range blogitems {
        fmt.Printf("%+v\n", item)
    }
    return &blogRss, nil 
}

func GetMdLinks(blogRss *BlogRss) []MarkdownFile {
    blogchannelp := blogRss.Channel
    blogitems := (*blogchannelp).Items
    mdlinks := make([]MarkdownFile, 0)
    for _, item := range blogitems {
        mdlinks = append(mdlinks, MarkdownFile{Title: item.Title, Link: item.Link})
    }
    return mdlinks
    
}

func WriteMarkdown(mdlink MarkdownFile) {
    urllink := mdlink.Link
    filename := mdlink.Title
    resp, err := http.Get(urllink)
    if err != nil {
        fmt.Printf("error get url: %s error: %v", urllink, err)
    }
    
    doc, err := goquery.NewDocumentFromReader(resp.Body)
    if err != nil {
        fmt.Printf("err: %v", err)
    }

    postbody := doc.Find("#cnblogs_post_body")
    fmt.Printf("%s\n", postbody.Text())

    converter := md.NewConverter("", true, nil)
    markdown, err := converter.ConvertString(postbody.Text())
    if err != nil {
        fmt.Printf("err parse html: %v", err)
    }

    ioutil.WriteFile(filename + ".md", []byte(markdown), 0666)

    resp.Body.Close()
}

func SequentialRunMultiple(files []string) {
    for _, f := range files {
        SequentialRun(f)
    }
}

func SequentialRun(fpath string) {
    blogRssp, err := ReadXml(fpath)
    if err != nil {
       os.Exit(2)
    }
    mdlinks := GetMdLinks(blogRssp)
    for _, mdlink := range mdlinks {
        linktrimed := strings.Trim(mdlink.Link, " ")
        if linktrimed == "" {
            continue
        } 
        WriteMarkdown(mdlink)
    }
}

func GetFiles() []string {
    fpaths := make([]string, 0)

    for _, arg := range os.Args[1:] {
        argtrimed := strings.Trim(arg, " ") 
        if argtrimed == "" {
            continue
        } 
        fpaths = append(fpaths, argtrimed)
    }
    fmt.Println(cap(fpaths))
    fmt.Println(fpaths)
    return fpaths
}

func main() {
    SequentialRunMultiple(GetFiles())
}

参考文献


标签:xml,string,err,备份,编程,go,Go,com
From: https://www.cnblogs.com/lovesqcc/p/17156149.html

相关文章

  • Windows黑客编程之功能技术(下)
    描述利用CreateProcess和匿名管道,获取远程命令执行的结果使用MoveFileEx和批处理脚本,实现文件自删除远程CMD关键在于捕获命令的输出结果创建匿名管道,一端写,一端读......
  • Windows黑客编程之功能技术(中)
    描述利用WM_DEVICECHANGE消息,进行u盘插拔监控利用ReadDirectoryChangesW函数,进行文件监控利用hook原始输入设备,进行按键监控记录u盘监控DialogBoxParam:在显示对话......
  • Windows黑客编程之功能技术(上)
    描述利用进程快照CreateToolhelp32Snapshot,进行进程、线程、进程模块的遍历利用FindFirstFile、FindNextFile,进行文件目录的遍历进程快照的遍历遍历进程BOOLEnumPr......
  • 计算机编程语言概述
    计算机语言是什么语言:是人与人之间用于沟通的一种方式。例如:中国人与中国人用普通话沟通。而中国人要和英国人交流,可以使用英语或普通话。计算机编程语言:就是人与计算机......
  • python-djanggo 实现读取excel 表格在网页中展示
    1.准备读取数据放到项目文件夹下   2.熟悉表结构    3.准备处理依赖库pipinstall-ihttps://pypi.tuna.tsinghua.edu.cn/simplepandasopenpyxl  ......
  • Golang微服务(二)
    Golang微服务(二)目录Golang微服务(二)注册中心选型consul环境consul常用API服务的增删查、健康检查gRPC的健康检查服务的负载均衡(相同服务多实例注册)配置中心nacos环境nacos......
  • Golang基于Mysql分布式锁实现集群主备
    背景集群中如果需要主备,可以基于Redis、zk的分布式锁等实现,本文将介绍如何利用Mysql分布式锁进行实现。原理数据库中包含数据字段(此处为Master的主机名)、版本号和上......
  • gorm日志输出到文件
    日志原理分析gorm手册上写着,如果需要自定义logger,则需要实现如下接口:typeInterfaceinterface{LogMode(LogLevel)InterfaceInfo(context.Context,string,......
  • 【编程入门】应用市场(php版)
    目标为编程初学者打造入门学习项目,使用各种主流编程语言来实现。让想学编程的,一个都不落下。上述基本涵盖了当前编程开发所有主流语言。左侧为前端版本:安卓、iOS、鸿蒙......
  • MongoDB在银行海量历史订单交易数据查询中的应用(Spring boot + Bee)
    MongoDB在银行海量历史订单交易数据查询中的应用(Springboot+Bee)近年来,随着各种便捷支付方式的普及,银行账户交易数据呈现爆炸式增长,同时数据模型也在不断变化,传统关......