最新要闻
- 《使命召唤》前景动荡
- 世界新资讯:上海一高校推出高启强同款猪脚面:师生直呼“舌尖上的《狂飙》”
- 乌苏啤酒大促:立减64元 折合3元/瓶到手
- 信息:女子考研期间生娃初试395分 回应外界好奇:多亏家人替自己分担很多
- 每日焦点!高德、百度地图红绿灯读秒很神奇 接入交管平台?真相并非如此
- 【天天新要闻】《我们的日子》里,不要忽视这些法律问题
- 天天资讯:俄州“毒火车”引发环境灾难后 美国又一货运列车脱轨
- 中兴通
- 世界动态:你昨晚关注的那个福利姬 可能是假的
- 世界即时看!国产新能源疯狂内卷!哈弗H6 PHEV官降1.5万 配置全系顶配
- 【世界报资讯】iPhone 15 Pro Max渲染图出炉:对比14 Pro Max边框更窄、机身更厚
- 世界观热点:薪资4K-5K!公司招聘财务要求做饭被吐槽像保姆
- 天天百事通!男子长期高血糖导致视网膜病变:不可逆
- 热头条丨不愧是万元机皇!酷安网友给三星Galaxy S23 Ultra打最高分
- 当前聚焦:《蚁人3》上映9天中国内地票房破2亿 网友:回到小众也挺好
- 世界微资讯!如何给公众号投稿赚钱_怎样给公众号投稿赚钱
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
全球热点!Go编程实战:博客备份
学习一门新的编程语言的最佳途径就是用它来编写一个实用程序。
在 “博客备份” 一文中,编写了两个 python 脚本,然后使用一个命令行将博客备份流程串联起来。这里,我们将使用 Go 来实现。学习一门新的编程语言的最佳途径就是用它来编写一个实用程序,尽可能用到大部分语言特性。
(资料图片)
不太熟悉 Go 语言的读者,可以阅读之前写的两篇文章:“Go语言初尝”,“Go 面向对象简明教程”。
整体设计
设计五个 go 函数,分别实现如下功能:
- GetFiles: 从命令行参数获取博文备份文件路径。
- ReadXml: 读取博客备份文件内容(XML格式),返回博文对象。
- GetMdLinks:从博文对象中取出博文链接对象数组。
- WriteMarkdown: 根据博文链接对象,下载 HTML 文件,并转换成 markdown 文件。
- SequentialRun: 使用串行方式,将以上函数串联成一个完整的流程。
为了避免重复在文中粘贴代码,先给出完整程序,读者可以先概览一遍,复制出来,对照解读来看。程序解读会比较简洁,不太明白的地方可以留言哈。
文件内容
为了调试程序,我们仅选取两篇博文内容组成 XML 文件。
/tmp/cnblogs_backup.xml
博客园-编程大观园 https://www.cnblogs.com/lovesqcc/ Enjoy programming, Enjoy life ~~~ 设计,诗歌与爱 zh-cn Tue, 07 Feb 2023 14:39:24 GMT Tue, 07 Feb 2023 14:39:24 GMT 60 -
专业精进之路:构建扎实而有实效的编程知识体系 http://www.cnblogs.com/lovesqcc/archive/2023/02/01/17084292.html 琴水玉 琴水玉 Wed, 01 Feb 2023 14:05:00 GMT http://www.cnblogs.com/lovesqcc/archive/2023/02/01/17084292.html -
知乎一答:什么样的知识是有价值的 http://www.cnblogs.com/lovesqcc/archive/2023/01/29/17072238.html 琴水玉 琴水玉 Sun, 29 Jan 2023 03:29:00 GMT http://www.cnblogs.com/lovesqcc/archive/2023/01/29/17072238.html
完整程序
执行:
go run blog_backup.go /tmp/cnblogs_backup.xml或者go build blog_backup.go./blog_backup /tmp/cnblogs_backup.xml
blog_backup.go
package mainimport ( "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 GetMdLinksPtr(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 { SequentialRun2(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 SequentialRun2(fpath string) { blogRssp, err := ReadXml(fpath) if err != nil { os.Exit(2) } mdlinksptr := GetMdLinksPtr(blogRssp) for i:=0 ; i
GetFiles
GetFiles 的作用是从命令行参数获取博客备份文件。支持读取多个博客备份文件。也可以用于测试。比如测试文件不存在的情形。
这里有一个习惯用法。掌握习惯用法,初学者可以很快上手一门新语言。_ 表示用不到,可以忽略这个值。习惯性打印日志,有利于快速排错。
送给编程初学者一句忠告:打几行日志很廉价,但你的时间价值千金。
for _, arg := range os.Args[1:] {}
ReadXML
解析 XML 的函数见: ReadXml。这里的重点是,要建立与 XML 内容结构对应的 struct 。
要点如下:
- 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`}
- 根据嵌套内容结构,定义对应的嵌套对象:BlogRss => BlogChannel => BlogItem。 每个 struct 对象的属性就是这个对象的子元素。属性首字母大写,是为了能够直接引用对象的字段。
- `xml:"item"` 不要写成 `xml:item` 。 item 要用双引号包起来,不然会解析不出来。
GetMdLinks
这个主要就是结构体遍历操作。对于大量博文来说,直接传入结构体对象切片是开销较大的,可以改成结构体切片指针类型。相应地,可替换成 GetMdLinksPtr 和 SequentialRun2 函数。
注意,go 数组指针不支持指针运算。因此,使用数组指针遍历数组,没有采用 range 方式。
WriteMarkdown
这个函数根据指定链接,下载 HTML 文件,使用 goquery 解析出 HTML 博文内容块,然后使用 html-to-markdown 来生成 Markdown 文件。
基本就是搜索网上相关程序,拼接而成。因此,知识信息搜索能力还是很重要滴。
SequentialRun
这个函数的作用就是串联逻辑以上逻辑单元,构建完整流程。
这里展示了程序模块化的优势。当每个逻辑单元设计成短小而正交的,那么整个流程看起来会很清晰。对于编程初学者,这一点尤为重要。
报错及解决
Go初学者容易犯的错误
- 习惯于写 links = xxx。 报错:undefined: links。 应该写成 links := xxx 。 Go 的赋值语句是 := 。
- 未使用的变量。报错:b declared and not used。 Go 不允许变量定义了但未使用,否则报编译错误。
- 变量声明顺序写反。报错:syntax error: unexpected ], expected operand。这是因为,make([]string, 32) 写成了 make(string[], 32)。变量声明顺序是 变量名 变量类型。
- 变量名和方法名大写,表示变量和方法的包可见权限。很多习惯于写 C 和 Java 的人需要一个适应过程。
- 丢掉返回值。append(links, item.Link) 。报错: append(links, item.Link) (value of type []string) is not used。这是因为 append 返回一个切片,而这个切片被丢弃了。需要写成: links = append(links, item.Link)。
- 不允许导入未使用的包。 报错:"github.com/JohannesKaufmann/html-to-markdown" imported as md and not used
- 多重赋值错误。 报错:Go assignment mismatch: 1 variable but converter.ConvertString returns 2 values。Go 的很多方法都定义了多个返回值,因为要包含错误信息 err。需要写成 markdown, err := converter.ConvertString(postbody.Text())。
- 方法签名的变量定义写错。比如 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.goblog_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/goquerygo: 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-deprecationor run "go help get" or "go help install".
执行 如下命令又报错:
➜ basic go install github.com/PuerkitoBio/goquery@latest go: downloading github.com/PuerkitoBio/goquery v1.8.1go: downloading github.com/andybalholm/cascadia v1.3.1go: downloading golang.org/x/net v0.7.0package github.com/PuerkitoBio/goquery is not a main package
解决办法:退回到 go 项目根目录 gostudy,执行:
go mod init gostudygo get github.com/PuerkitoBio/goquerygo get github.com/JohannesKaufmann/html-to-markdown
参考文献
- The Go Programming Language
- Go XML处理
- go解析xml的三种方式
- 深度解密Go语言之Slice
- Golang文件写入的四种方式
全球热点!Go编程实战:博客备份
Markdown简明教程
《使命召唤》前景动荡
世界新资讯:上海一高校推出高启强同款猪脚面:师生直呼“舌尖上的《狂飙》”
乌苏啤酒大促:立减64元 折合3元/瓶到手
信息:女子考研期间生娃初试395分 回应外界好奇:多亏家人替自己分担很多
每日焦点!高德、百度地图红绿灯读秒很神奇 接入交管平台?真相并非如此
【天天新要闻】《我们的日子》里,不要忽视这些法律问题
天天资讯:俄州“毒火车”引发环境灾难后 美国又一货运列车脱轨
中兴通
全球热讯:读Java性能权威指南(第2版)笔记02_ Java SE API技巧上
世界动态:你昨晚关注的那个福利姬 可能是假的
世界即时看!国产新能源疯狂内卷!哈弗H6 PHEV官降1.5万 配置全系顶配
【世界报资讯】iPhone 15 Pro Max渲染图出炉:对比14 Pro Max边框更窄、机身更厚
对接水仙后台(支持AndLua+、FA、FA2、AIDE lua、Simple Lua等)
【全球报资讯】Golang基于Mysql分布式锁实现集群主备
世界观热点:薪资4K-5K!公司招聘财务要求做饭被吐槽像保姆
天天百事通!男子长期高血糖导致视网膜病变:不可逆
热头条丨不愧是万元机皇!酷安网友给三星Galaxy S23 Ultra打最高分
当前聚焦:《蚁人3》上映9天中国内地票房破2亿 网友:回到小众也挺好
世界微资讯!如何给公众号投稿赚钱_怎样给公众号投稿赚钱
双亲委派机制
天天微动态丨中国教师队伍建设研究/京师教师教育论丛
当前视讯!即将让核污水倒入大海!日本港口大量有毒海胆聚集 或出现爆发式增长
三星降低QD-OLED面板成本!让电视更具竞争力
世界关注:努比亚Z50新版下周首销:骁龙8 Gen2旗舰焊门员 性价比无敌
最新:python实现客户端和服务端的UDP相互通信
【报资讯】hbuilderx打正式包所需的私钥证书的创建方法
全球新动态:2.【go-kit教程】go-kit启动http服务
室内单目深度估计-4
最新:kaggle中训练得到的output太大该怎么下载?
世界热消息:2消息,中超新贵签约32岁国脚,5中超外援上诉国际足联
环球新动态:超市宣称1元纸币将退出历史引热议 网友直呼太突然:官方回应不属实
视点!女子患异食癖3年吃上百块粉饼:体检身体无异常
天天热点!刷题疑问
环球速读:史上最好的真全面屏手机!努比亚Z50 Ultra上架接受预约
天天精选!禁止自带食材 关停300家店 海底捞从巨亏41亿到盈利13亿
天天讯息:day04-原生的API&注解方式
【环球新要闻】Git使用
美食博主三亚买3888元海鲜被好心人提醒多花1700:当事人心累
热消息:秋裤先别着急脱!“春捂”到底该“捂”哪儿?
前沿资讯!2023年安卓之光!小米13 Ultra手机壳曝光:背部造型抢眼
餐馆接到网吧10个外卖订单 结果被刷9个差评 店主:下次亲自送餐
天天微速讯:门店2299元 GXG男士羊毛大衣0.8折清仓大促:实付199元!
世界热资讯!乐堡苏打气泡酒12罐到手19.9元:低糖0脂无负担
威马员工在线讨薪:被恶心到了、恶心的事还有更多
广州塞车登“热搜”?“甜蜜的烦恼”重回一线城市,中国经济活力加快恢复
【Tire树】高效统计字符串
80、90后泪目 国产暗黑《赵云传重制版》试玩
1岁男童误食降糖药成植物人:愿康复顺利
环球速看:中央人民广播电台民族节目中心
Ubuntu安装Zabbix6.0
秒睡令人羡慕?医生提醒:可能是种睡眠障碍
《流浪地球2》科幻成真?武汉国博用特效“加建”太空电梯
今头条!【element UI】在 el-select 与 el-tree 结合组件
环球热文:python教程:模块的搜索路径
Python中模块的四种方式
《原子之心》种族主义漫画引争议:涉嫌歧视黑人!官方道歉
世界速读:我国载人航天将对国际开放 多国航天员希望参与中国空间站
每日热讯!驱动拖后腿 Intel显卡被低估:2000元档A770理论可刚RTX 3070
受贿、泄露内幕信息!湖北原副省长曹广晶被公诉
天天快看点丨下周发售!《卧龙:苍天陨落》新预告发布:PC/主机通吃
最新消息:EQ电动车中国表现欠佳 奔驰CEO:打价格战不是好事
环球热点!101岁老人每天赶2场麻将 医生:身体状态70岁
What's past is prologue
全球新资讯:车商不收特斯拉 新车一个月亏7万:新能源二手车都不受待见 厂商频降价
H5N1禽流感致死事件引世卫关注:事发柬埔寨、9年来首次
环球热点评!下单就送鲜蔬汤 海福盛冻干粥5杯大促:券后仅20.9元!
“真香定律”稳定发挥 迈凯伦全新SUV效果图曝光:预计售价282万
世界今日讯!关于修订《中国高尔夫球协会赛事活动管理办法》的通知
环球微头条丨中越边境民族文化艺术考察研究
每日热点:女子在门缝发现针头 是大妈拿注射器推大量不明液体:整栋楼遭殃 网友气愤
地球中心到底有什么?科学家发现竟是一个超大铁球 跟书里讲得不一样
NET6接口项目基础框架项目
Java 8 Lambda 方法引用 简记
全球看热讯:(数据库系统概论|王珊)第七章数据库设计-第三节:概念结构设计
Blender插件:水滴生成器(Droplet Generator)
环球速递!《分布式技术原理与算法解析》学习笔记Day22
消炎止血、除口臭!中药黄芩牙膏大促:3支不到20块(送牙刷)
【世界聚看点】《博德之门3》Xbox版遭遇技术难题
天天快看点丨激光溶脂和吸脂的区别_激光溶脂和吸脂怎么区分
每人1600元!北京要发消费券了:买手机、PC、无人机等都能用
为博流量 女子与前夫摆拍家暴并直播谎称报警:结果被官方通报
当前快看:吸水好干得快:洁丽雅新疆棉毛巾4.98元发车
【Java】ArrayList线程不安全的坑
世界观察:不点拍照按键也能拍照!荣耀Magic 5全新技术曝光:堪比变魔术
全球观热点:自信满满!萌娃觉得自己掌握了英语的窍门 张口让网友沉默
当前播报:电动车里什么最耗电?13类电器功率排名来了!出乎意料
当前关注:义乌市百货有限责任公司
【新要闻】饰组词语接龙_饰组词语
运算符重载
世界要闻:猛男专属 吉利几何E超吸粉特别版上市:9.98万元
全球热点评!《三体》被改编成剧本杀 三体宇宙索赔300万
热资讯!算法正在谋杀新闻 各种日报带头摆烂 十亿中国网友却为此狂欢
联想小新Pro 14天青蓝来了:13代酷睿 1TB硬盘
天天日报丨国产架构的龙芯CPU可以跑QQ了!底层重写、与x86/Arm肩并肩
当前滚动:全商汇深圳控股集团有限公司_全商汇
世界资讯:Eureka高可用集群服务端和客户端配置
今日精选:NEMU PA 1 实验报告
Spark系列 - (4) Spark任务调度