惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
云风的 BLOG
云风的 BLOG
B
Blog
博客园 - Franky
I
InfoQ
A
About on SuperTechFans
博客园_首页
L
LangChain Blog
量子位
腾讯CDC
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
美团技术团队
V
V2EX
Apple Machine Learning Research
Apple Machine Learning Research
雷峰网
雷峰网
MongoDB | Blog
MongoDB | Blog
Microsoft Azure Blog
Microsoft Azure Blog
月光博客
月光博客
T
The Blog of Author Tim Ferriss
P
Proofpoint News Feed
G
Google Developers Blog
Last Week in AI
Last Week in AI

热爱生活与梦想

迁移LeanCloud的Waline评论数据到TiDB/Neon 25年江浙地区暑假自驾游之感慨 使用阿里云灵码继续完成构建 SSL 证书管理器 亲手给儿子做的棒棒糖摩天轮六一礼物 使用腾讯云 CodeBuddy 构建 SSL 证书管理器 修复代码块默认高度相关缺陷 HugoNexT4.7.2 新功能和升级提示 改善代码块折叠和选中功能 支持Github风格的警告样式 新增音乐短代码功能支持 给文章添加摘要和过期提示功能 回顾24年的过去总结 修复Mathjax行内显示公式的问题 给Hugo文章增加阅读更多跳转的锚点定位功能 Win10隐匿的VPN设置和L2TP连接常见错误 Go语言中“糟糕”的日期时间格式化设计 DaoCloud道客提供的免费Docker镜像代理服务 感谢万能淘宝让自己吃到喜欢的美食 WSL运行时遇到未知异常错误无法使用 隐藏的换行符导致Base64加密解密失败 MySQL自带客户端直接免密登录操作 Linux系统中删除目录软链接的注意项 Java程序调用外网API时CA问题 如何不关机重启WSL2恢复虚拟服务 在Windows上安装Podman容器平台做虚拟化 记一次无法弹出移动硬盘的记录 重新激活Github的2FA认证 Linux中使用tar压缩命令排除文件 继续小米手环的健康生活之旅 一次意外的“坠机”引发了我对手机保护壳的深思
为友情链接添加自动检测脚本
凡梦星尘 · 2024-02-25 · via 热爱生活与梦想

友情链接是博客的重要组成部之一,很多博主日常也会时常进行链接的交换,即是发展自己的互联网友人圈子,也算得上是为自己的博客引流。不过在如今信息大爆炸的时代,也渐渐有些博主实在“有些乏力”来维护自己的站点,其中的缘由可能有诸多,最终便是出现我们偶尔会遇到的情况,某个站点突然之间可能就无法访问。为此便有想法给自己的站点添加个友情链接检测脚本,剔除掉那些已经失效的友情链接,让用户访问时的跳转体验更好加。

考虑到我们的博客站点是生成静态文件的部署方式,所以友情链接的检测频率也不会特别高,就是在每次发表新文章构建博客站点的时候检测一次。并结合Github Actions的CI流程可以实现自动化的处理,大致的流程如下:

graph TD; node1([fa:fa-play 开始 ]) --> node2[/站点内容更新or配置调整/] --> |fa:fa-upload Git提交&推送&触发GithubAction| node3[[fa:fa-scroll 调用脚本验证友链可访问性]] --> |fa:fa-trash-can-arrow-up 剔除无法访问的链接| node4[/fa:fa-list-check 生成新的友链配置文件/] --> node5[fa:fa-blog 构建构静态文件] --> node6([fa:fa-stop 结束 ]);

根据Hugo NexT主题中有关于友情链接的配置信息,我们可以获取到友链的具体URL地址,直接使用Get方法探查其可访问性,然后调整友链的状态标识,重新生成新的友情链接配置文件,具体的脚本代码可参考如下:

  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
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"sync"
	"time"

	"gopkg.in/yaml.v3"
)

var (
	timeOut int
)

type FLink struct {
	Name     string `yaml:"name"`
	Desc     string `yaml:"desc"`
	Avatar   string `yaml:"avatar"`
	Link     string `yaml:"link"`
	Visible  bool   `yaml:"visible"`
	Skip     bool   `yaml:"skip"`
	Attempts int    `yaml:"-"`
}

type FLinkGroup struct {
	Title string  `yaml:"FLinksTitle"`
	Desc  string  `yaml:"FLinksDesc"`
	List  []FLink `yaml:"FLinksList"`
}

func main() {
	// 读取命令行参数
	filePath := flag.String("f", "", "友情链接的Yaml文件路径")
	flag.IntVar(&timeOut, "t", 3, "检测网站的超时时长")
	help := flag.Bool("h", false, "显示帮助信息")
	flag.Parse()

	// 验证文件路径是否为空
	if *help || *filePath == "" {
		flag.Usage()
		return
	}

	// 读取YAML文件
	data, err := ioutil.ReadFile(*filePath)
	if err != nil {
		fmt.Println("读取文件失败:", err)
		return
	}

	// 解析YAML文件
	var groups []FLinkGroup
	err = yaml.Unmarshal(data, &groups)
	if err != nil {
		fmt.Println("解析YAML文件失败:", err)
		return
	}

	// 统计站点数量
	totalLinks := 0
	for _, group := range groups {
		totalLinks += len(group.List)
	}

	// 创建并发控制的通道
	ch := make(chan FLink)
	wg := sync.WaitGroup{}

	// 启动并发检测
	for groupIndex, group := range groups {
		for linkIndex := range group.List {
			wg.Add(1)
			go checkLink(&groups[groupIndex].List[linkIndex], ch, &wg)
		}
	}

	// 启动结果处理
	go func() {
		wg.Wait()
		close(ch)
	}()

	// 统计结果
	successCount := 0
	failureCount := 0
	failureLinks := make(map[string]string)
	startTime := time.Now()

	for link := range ch {
		if link.Visible {
			successCount++
		} else {
			failureCount++
			failureLinks[link.Name] = link.Link
		}

		// 输出检测进度
		finishLinks := successCount + failureCount
		progress := float64(finishLinks) / float64(totalLinks) * 100
		remainingTime := time.Since(startTime).Seconds() / progress * (100 - progress)
		fmt.Printf("正在检测:%d/%d,检测进度:%.2f%%,预计剩余时间:%.2f秒\n", finishLinks, totalLinks, progress, remainingTime)
	}

	// 输出统计信息
	fmt.Println("\n站点检测统计信息")
	fmt.Println(strings.Repeat("-", 30))
	fmt.Printf("总共检测站点:%-d\n", totalLinks)
	fmt.Printf("正常访问站点:%-d\n", successCount)
	fmt.Printf("失去连接站点:%-d\n", failureCount)
	fmt.Printf("检测总共耗时:%-.2f秒\n", time.Since(startTime).Seconds())
	fmt.Println(strings.Repeat("-", 30))
	fmt.Println("")

	// 输出连接失败的站点名称和地址
	if failureLinks != nil && len(failureLinks) > 0 {
		fmt.Println("连接失败的站点列表")
		fmt.Println(strings.Repeat("-", 50))

		for name, link := range failureLinks {
			fmt.Printf("%s:%s\n", name, link)
		}
		fmt.Println(strings.Repeat("-", 50))
		fmt.Println("")
	}

	// 将结果保存到原文件
	resultData, err := yaml.Marshal(groups)
	if err != nil {
		fmt.Println("保存结果到文件失败:", err)
		return
	}

	err = ioutil.WriteFile(*filePath, resultData, 0644)
	if err != nil {
		fmt.Println("保存结果到文件失败:", err)
		return
	}

	fmt.Printf("结果已保存到%s文件\n", *filePath)
}

func checkLink(link *FLink, ch chan<- FLink, wg *sync.WaitGroup) {
	defer wg.Done()

	if link.Skip {
		link.Visible = true
	} else {
		// 默认重试3次
		for i := 0; i < 3; i++ {
			// 等待时间逐次增加
			if i > 1 {
				time.Sleep(time.Duration(i) * time.Second)
			}

			// 发起HTTP请求
			//fmt.Printf("正在检测%s....\n", link.Link)
			client := http.Client{Timeout: time.Duration(timeOut) * time.Second}
			resp, err := client.Get(link.Link)
			if err == nil && resp.StatusCode == http.StatusOK {
				link.Visible = true
				ch <- *link
				return
			}
		}

		// 所有重试都失败,设置visible为false
		link.Visible = false
	}

	ch <- *link
}

在站点的根目录下创建.github文件夹,接着分别创建名为workflowsscripts 的子目录,并将上述提供的友情链接检测脚本保存到scripts目录下,同时在workflows目录下创建名为deploy.yml的文件,将生成静态站点文件相关步骤配置在此文件,配置代码参考如下: