
用 Python 批量获取宝塔面板站点 SSL 信息
用 Python 批量获取宝塔面板站点 SSL 信息并写入数据库
在日常运维中,我们有多个 宝塔面板 管理不同环境、不同业务的站点。
宝塔面板虽然有 SSL 证书 信息,但没有一个统一的 API 入口查看全部面板的证书有效期、过期时间等信息。
如果站点多、面板多,人工逐个检查就非常麻烦,而且容易漏掉。
因此我写了一个 Python 脚本,批量调用宝塔 API 获取站点信息,包括:
- 站点名称
- 面板名称
- 面板 URL
- SSL 证书过期时间(
ssl_not_after
) - SSL 剩余天数(
ssl_end_days
)
并将这些数据统一写入到数据库表中,方便后续前后端统一展示、做到快过期证书的预警
宝塔 API 背景
宝塔面板的开放 API 提供了 data?action=getData&table=sites
接口,可以获取站点列表。
访问时需要:
- 面板 URL
- API 密钥
- 时间戳签名(MD5 加密)
本脚本封装了 签名计算 和 API 分页处理,并支持 HTTPS 证书信息 解析。
数据库设计
数据库表 bt_sites
字段示例:
字段名 | 类型 | 说明 |
---|---|---|
site_name | varchar | 站点名称 |
panel_name | varchar | 面板别名 |
panel_url | varchar | 面板地址 |
ssl_not_after | datetime | SSL 证书到期时间 |
ssl_end_days | int | SSL 证书剩余天数 |
is_deleted | tinyint | 逻辑删除标记 |
created_at | datetime | 创建时间 |
updated_at | datetime | 更新时间 |
脚本逻辑中:
- 先将当前面板所有站点
is_deleted
标记为 1(逻辑删除) - 再插入或更新新获取到的站点数据(
is_deleted
重置为 0)
脚本实现
#!/bin/python3
import time, hashlib, sys, os, json
import pymysql
from datetime import datetime
class bt_api:
def __init__(self, bt_panel, bt_key):
self.__BT_PANEL = bt_panel
self.__BT_KEY = bt_key
def get_sites(self):
url = self.__BT_PANEL + '/data?action=getData'
p_data = self.__get_key_data()
p_data['table'] = 'sites'
p_data['tojs'] = 'test'
all_data = []
page = 1
while True:
p_data['p'] = page
result = self.__http_post_cookie(url, p_data)
try:
data = json.loads(result)
if not isinstance(data, dict):
return {"data": all_data}
all_data.extend(data.get("data", []))
if "page" not in data or "Pnext" not in data["page"]:
break
page += 1
time.sleep(0.5)
except json.JSONDecodeError:
return {"data": all_data}
return {"data": all_data}
def __get_md5(self, s):
return hashlib.md5(s.encode('utf-8')).hexdigest()
def __get_key_data(self):
now_time = int(time.time())
return {
'request_token': self.__get_md5(str(now_time) + self.__get_md5(self.__BT_KEY)),
'request_time': now_time
}
def __http_post_cookie(self, url, p_data, timeout=1800):
cookie_file = './' + self.__get_md5(self.__BT_PANEL) + '.cookie'
import urllib.request, ssl, http.cookiejar
cookie_obj = http.cookiejar.MozillaCookieJar(cookie_file)
if os.path.exists(cookie_file):
cookie_obj.load(cookie_file, ignore_discard=True, ignore_expires=True)
context = ssl._create_unverified_context() if url.startswith('https') else None
handler = urllib.request.HTTPCookieProcessor(cookie_obj)
data = urllib.parse.urlencode(p_data).encode('utf-8')
req = urllib.request.Request(url, data)
opener = urllib.request.build_opener(
urllib.request.HTTPSHandler(context=context) if context else urllib.request.HTTPHandler(),
handler
)
try:
response = opener.open(req, timeout=timeout)
cookie_obj.save(ignore_discard=True, ignore_expires=True)
result = response.read()
return result.decode('utf-8') if isinstance(result, bytes) else result
except urllib.request.HTTPError:
return ""
# 数据库配置
DB_CONFIG = {
"host": "ip",
"port": "*",
"user": "*",
"password": "*",
"database": "*",
"charset": "utf8mb4"
}
# 宝塔面板列表(省略部分)
bt_panels = [
{
"name": "自定义名称1",
"url": "http://url:port",
"key": "api-key"
},
{
"name": "自定义名称2",
"url": "http://url:port",
"key": "api-key"
}
# ... 其他面板配置 ...
]
def save_to_db(data_list, panel_url):
conn = pymysql.connect(**DB_CONFIG)
cursor = conn.cursor()
now = datetime.now()
# 先标记删除
cursor.execute("UPDATE bt_sites SET is_deleted = 1, updated_at=%s WHERE panel_url=%s", (now, panel_url))
# 插入或更新
insert_sql = """
INSERT INTO bt_sites (site_name, panel_name, panel_url, ssl_not_after, ssl_end_days, is_deleted, created_at, updated_at)
VALUES (%s, %s, %s, %s, %s, 0, %s, %s)
ON DUPLICATE KEY UPDATE
ssl_not_after=VALUES(ssl_not_after),
ssl_end_days=VALUES(ssl_end_days),
is_deleted=0,
updated_at=VALUES(updated_at)
"""
for item in data_list:
cursor.execute(insert_sql, (
item["site_name"],
item["panel_name"],
item["panel_url"],
item.get("ssl_not_after", None),
item.get("ssl_end_days", None),
now, now
))
conn.commit()
cursor.close()
conn.close()
def main():
# 清除 cookie
for f in os.listdir('.'):
if f.endswith('.cookie'):
os.remove(f)
for panel in bt_panels:
print(f"拉取 {panel['name']} 中...")
api = bt_api(panel['url'], panel['key'])
try:
sites = api.get_sites()
results = []
for site in sites.get("data", []):
ssl = site.get("ssl", {})
if isinstance(ssl, int):
ssl = {}
results.append({
"site_name": site.get("name"),
"panel_name": panel["name"],
"panel_url": panel["url"],
"ssl_not_after": ssl.get("notAfter", None),
"ssl_end_days": int(ssl.get("endtime")) if ssl.get("endtime") and str(ssl.get("endtime")).isdigit() else None,
})
save_to_db(results, panel["url"])
except Exception as e:
print(f"处理面板 {panel['name']} 失败: {e}")
time.sleep(1)
print("所有数据处理完毕。")
if __name__ == '__main__':
main()
使用方法
- 开启宝塔 API
- 登录宝塔 → 面板设置 → 开启 API 接口
- 添加 IP 白名单(脚本运行服务器 IP)
- 获取 API Key
- 配置面板信息
- 在
bt_panels
中添加多个面板的name
、url
、key
- 在
- 配置数据库
- 修改
DB_CONFIG
连接参数 - 创建
bt_sites
表(提前建好唯一键约束,比如UNIQUE(panel_url, site_name)
- 修改
- 后续可在前端查询数据库,做证书快到期提醒
- 也可以配合 Prometheus Alert 做自动告警
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果