前面说到,我写了一个能自动爬取对方网站,生成适合 Hexo 友链配置格式的脚本。

既然说到能添加友链的脚本,那我就来说一下我这个脚本的原理是什么。

为什么我们的浏览器能够精准地渲染一个网站的图标、网站的标题还有网站的个人介绍呢?这是因为实际上他们出现的位置特别地固定!

位置 描述
<title></title>og:title 网站的标题
og:imagetwitter:image 网站的图标
descriptionog:description 网站的介绍

我们可以根据这些固定的位置,写一个简简单单的脚本,来用正则表达式和爬虫,爬取对应网站的首页,转换成我们需要的配置格式。

1
2
3
4
- name: 网站的标题
link: 网站的链接
avatar: 网站的图标
descr: 网站的介绍

原理是不是听起来很简单?所以说我根据这个,找 AI 生成了一个 Python 脚本:

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
import re
import urllib.request
from urllib.parse import urlparse
import html

def get_webpage_content(url):
"""获取网页内容"""
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
req = urllib.request.Request(url, headers=headers)
response = urllib.request.urlopen(req, timeout=10)
content = response.read().decode('utf-8', errors='ignore')
return content
except Exception as e:
print(f"获取网页内容失败: {e}")
return None

def extract_title(html_content):
"""提取网页标题"""
# 从<title>标签提取
title_match = re.search(r'<title[^>]*>(.*?)</title>', html_content, re.IGNORECASE | re.DOTALL)
if title_match:
title = title_match.group(1).strip()
# 清理标题中的多余空格和换行
title = re.sub(r'\s+', ' ', title)
return html.unescape(title)

# 尝试从og:title提取
og_title_match = re.search(r'<meta[^>]*property=["\']og:title["\'][^>]*content=["\']([^"\']+)["\']',
html_content, re.IGNORECASE)
if og_title_match:
return html.unescape(og_title_match.group(1).strip())

return ""

def extract_og_image(html_content):
"""提取Open Graph图片"""
# 查找og:image
og_image_match = re.search(r'<meta[^>]*property=["\']og:image["\'][^>]*content=["\']([^"\']+)["\']',
html_content, re.IGNORECASE)
if og_image_match:
image_url = og_image_match.group(1).strip()
return html.unescape(image_url)

# 查找twitter:image
twitter_image_match = re.search(r'<meta[^>]*property=["\']twitter:image["\'][^>]*content=["\']([^"\']+)["\']',
html_content, re.IGNORECASE)
if twitter_image_match:
image_url = twitter_image_match.group(1).strip()
return html.unescape(image_url)

return ""

def extract_description(html_content):
"""提取描述"""
# 查找meta description
desc_match = re.search(r'<meta[^>]*name=["\']description["\'][^>]*content=["\']([^"\']+)["\']',
html_content, re.IGNORECASE)
if desc_match:
desc = desc_match.group(1).strip()
# 清理描述中的多余空格和换行
desc = re.sub(r'\s+', ' ', desc)
return html.unescape(desc)

# 查找og:description
og_desc_match = re.search(r'<meta[^>]*property=["\']og:description["\'][^>]*content=["\']([^"\']+)["\']',
html_content, re.IGNORECASE)
if og_desc_match:
desc = og_desc_match.group(1).strip()
desc = re.sub(r'\s+', ' ', desc)
return html.unescape(desc)

return ""

def format_yaml_entry(name, link, avatar, descr):
"""格式化YAML条目"""
# 清理数据
name = name.strip() if name else ""
avatar = avatar.strip() if avatar else ""
descr = descr.strip() if descr else ""

# 构建YAML格式
yaml_entry = f"- name: {name}\n"
yaml_entry += f" link: {link}\n"
yaml_entry += f" avatar: {avatar}\n"
yaml_entry += f" descr: {descr}"

return yaml_entry

def main():
if len(sys.argv) != 2:
print("使用方法: python3 get_links.py <URL>")
print("示例: python3 get_links.py https://xiashuangjv123.github.io/")
sys.exit(1)

url = sys.argv[1].strip()

# 验证URL格式
parsed_url = urlparse(url)
if not parsed_url.scheme or not parsed_url.netloc:
print("错误: 无效的URL格式")
sys.exit(1)

print(f"正在处理: {url}")

# 获取网页内容
html_content = get_webpage_content(url)
if not html_content:
print("无法获取网页内容")
sys.exit(1)

# 提取信息
name = extract_title(html_content)
avatar = extract_og_image(html_content)
descr = extract_description(html_content)

# 如果og:image是相对路径,转换为绝对路径
if avatar and not avatar.startswith(('http://', 'https://')):
if avatar.startswith('/'):
# 相对根路径
base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
avatar = base_url + avatar
else:
# 相对当前路径
base_path = parsed_url.path
if base_path.endswith('/'):
base_url = f"{parsed_url.scheme}://{parsed_url.netloc}{base_path}"
else:
base_url = f"{parsed_url.scheme}://{parsed_url.netloc}{base_path.rsplit('/', 1)[0]}/"
avatar = base_url + avatar

# 生成YAML格式
yaml_output = format_yaml_entry(name, url, avatar, descr)

print("\n生成的YAML格式:")
print("=" * 50)
print(yaml_output)
print("=" * 50)

# 复制到剪贴板(可选功能)
try:
import subprocess
# 对于macOS
subprocess.run("pbcopy", universal_newlines=True, input=yaml_output)
print("✓ 已复制到剪贴板")
except:
try:
# 对于Windows
import win32clipboard
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardText(yaml_output)
win32clipboard.CloseClipboard()
print("✓ 已复制到剪贴板")
except:
print("⚠ 无法自动复制到剪贴板,请手动复制上面的内容")

if __name__ == "__main__":
main()

保存为 get_links.py

这样每次收到评论有友链时,我就可以运行 python3 get_links.py 网站链接 生成网站配置了。