python 自动化创建 confluence 页面三

create_wiki_page.py
from configparser import ConfigParser
import subprocess
from pathlib import Path
import json
from typing import Dict, List, Optional
from datetime import datetime
from collections import defaultdict
import requests
import os
import sys
import glob
from GitLabPipeline import GitLabPipeline
confluence_url = "https://wiki.abc.com"
url = f"{confluence_url}/rest/api/content"
artifactory_url = "https://artifact.abc.com:443/artifactory/"
username = "zhangshan"
api_token = "MTc3MDkxNTUzOTk0OtLLh/239zQ0N/5nZ04UA+bTmzEm"
space_key = "APRICOT"
parent_page_id = 3395495944
# step 查询版本信息,存入version.txt
def query_artifactory(ARTIFACTORY_URL,ARTIFACTORY_TOKEN,SEARCH_PATH):
"""查询Artifactory并保存结果到path.txt"""
cmd = [
"jf", "rt", "search",
SEARCH_PATH,
"--url", ARTIFACTORY_URL,
"--user", "GAYUXIA",
"--password", ARTIFACTORY_TOKEN,
"--insecure-tls=false",
"--recursive"
]
try:
print("正在查询Artifactory...")
result = subprocess.run(
cmd,
check=True,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
Path("version.txt").write_text(result.stdout, encoding="utf-8")
print("查询结果已保存到 version.txt")
return True
except subprocess.CalledProcessError as e:
print(f"查询失败: {e.stderr}")
raise
def parse_build_paths():
# 读取数据(从 version.txt)
with open("version.txt", "r") as f:
data = json.load(f)
# 初始化结果结构:{ STAR version → build_type → category → set() }
# 这里的结构就是:{ STAR 3.0 -> dev_userdebug -> flat_build/swup -> 去重}
result = defaultdict(lambda: defaultdict(lambda: {"flat_build": set(), "swup": set()}))
def classify_path(path):
# 分类类别:flat_build 或 swup
if "flat_build" in path and path.endswith(".tar.gz"):
category = "flat_build"
elif "swup" in path:
category = "swup"
else:
return None, None, None
# 构建类型分类
if "dev_userdebug" in path or "userdebug" in path:
build_type = "dev_userdebug"
elif "dev" in path or "dev_user" in path:
build_type = "dev_user"
elif "prod-pl_user" in path or "prod-pl" in path:
build_type = "prod-pl_user"
elif "prod-rl_user" in path or "prod-rl" in path:
build_type = "prod-rl_user"
else:
return None, None, None
# 提取 STAR 版本(如 STAR3.0 或 STAR3.5)
if "STAR3.0" in path:
star_version = "STAR3.0"
elif "STAR3.5" in path:
star_version = "STAR3.5"
else:
return None, None, None
return star_version, build_type, category
# 遍历并分类
for entry in data:
path = entry.get("path", "")
star_version, build_type, category = classify_path(path)
if star_version and build_type and category:
if category == "swup":
path = "/".join(path.split("/")[:12]) # 裁剪 swup 路径
result[star_version][build_type][category].add(path)
# 转换为 JSON 可序列化结构(set → list)
clean_result = {
star: {
build: {
kind: sorted([f"https://artifact.abc.com/artifactory/{p}" for p in paths])
for kind, paths in builds.items()
}
for build, builds in star_data.items()
}
for star, star_data in result.items()
}
# 写入结果
with open("classified_builds_by_star.json", "w") as f:
json.dump(clean_result, f, indent=2)
print("分类结果已写入 classified_builds_by_star.json")
# 写入 txt 文件
with open("output.txt", "w", encoding="utf-8") as file:
print_nested_to_file(clean_result, file)
print("已成功导出到 output.txt")
return clean_result
# 导出为纯文本 txt
def print_nested_to_file(data, file, indent=0):
prefix = " " * indent
if isinstance(data, dict):
for key, value in data.items():
file.write(f"{prefix}{key}\n") # 打印当前键(带缩进)
print_nested_to_file(value, file, indent + 1) # 递归处理值(缩进+1)
elif isinstance(data, list):
for item in data:
print_nested_to_file(item, file, indent) # 递归处理列表项(保持缩进)
else:
file.write(f"{prefix}{data}\n")
def create_page():
result_data = parse_build_paths()
# 第2张表格的内容
with open("mr_table.html", "r", encoding="utf-8") as f:
mr_table_html = f.read()
# 第3张表格的内容
with open("test_table.html", "r", encoding="utf-8") as f:
test_table_html = f.read()
# 构建 <tbody> 表格行内容
table_rows = ""
# 计算整个表格总共有多少个 <tr> 行数
total_rows = sum(
sum(len(kinds) for kinds in builds.values())
for builds in result_data.values()
)
first_col_written = False # 控制是否写入第一列
for star, builds in result_data.items():
star_total_rows = sum(len(kinds) for kinds in builds.values())
star_written = False
for build_type, kinds in builds.items():
branches = list(kinds.items())
build_rowspan = len(branches)
for idx, (branch, urls) in enumerate(branches):
archive_urls = "<br>".join([f'<a href="{u}">{u}</a>' for u in urls])
table_rows += "<tr>\n"
# 第一列:仅在第一次写入,并合并所有行
if not first_col_written:
table_rows += f' <td rowspan="{total_rows}">8295_master</td>\n'
first_col_written = True
# VERSION 列
if not star_written:
table_rows += f' <td rowspan="{star_total_rows}">{star}</td>\n'
star_written = True
# BUILD TYPE 列
if idx == 0:
table_rows += f' <td rowspan="{build_rowspan}">{build_type}</td>\n'
# 其余列
table_rows += f" <td>{branch}</td>\n"
table_rows += f" <td>{archive_urls}</td>\n"
table_rows += f" <td></td>\n"
table_rows += f" <td></td>\n"
table_rows += "</tr>\n"
# 构建 HTML 表格内容
page_content = f"""
<h4>构建日期: {BUILD_DATE}</h4>
<h4>@Integration</h4>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>VARSION</th>
<th>ELECTROMECHAINCAL ARCHIECTURE</th>
<th>BUILD TYPE</th>
<th>PACKAGE TYPE</th>
<th>ARCHIVE URL</th>
<th>DECISION</th>
<th>REASON FOR EXTENDING</th>
</tr>
</thead>
<tbody>
{table_rows}
</tbody>
</table>
</div>
<h1 data-nh-numbering="" talk-marker="43" talk-page-id="3221943987" talk-page-version="5" id="Example-MRs:">MRs:</h1>
<p talk-marker="44" talk-page-id="3221943987" talk-page-version="5"><a class="confluence-userlink user-mention userlink-1" data-username="ljinrui" href="/display/~ljinrui" data-linked-resource-id="3010866811" data-linked-resource-version="1" data-linked-resource-type="userinfo" data-base-url="https://wiki.abc.com" title="" data-user-hover-bound="true">li jinrui</a> @integration</p>
{mr_table_html}
<h1 data-nh-numbering="" talk-marker="54" talk-page-id="3407581415" talk-page-version="5" id="Example-Testresults:">Test results:</h1>
<p talk-marker="55" talk-page-id="3407581415" talk-page-version="5"><strong style="letter-spacing: 0.0px;">Test Summary</strong></p>
{test_table_html}
"""
# Confluence API 请求
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_token}"
}
page_data = {
"type": "page",
"title": ESTAND,
"ancestors": [{"id": parent_page_id}],
"space": {"key": space_key},
"body": {
"storage": {
"value": page_content,
"representation": "storage"
}
}
}
response = requests.post(url, headers=headers, data=json.dumps(page_data))
if response.status_code in [200, 201]:
print("页面创建成功")
else:
print(f"页面创建失败: {response.status_code}")
print(response.text)
if __name__ == '__main__':
if len(sys.argv) < 3:
print("\033[1;31m 需要输入3个参数 \033[0m")
print("\033[1;31m 参数一:apricot_rse/i3_RSU/RSE_System_Staging_Build/20250722/E233.0/ & pipeline_url \033[0m")
print("\033[1;31m 参数二:pipeline_url \033[0m")
print("\033[1;31m 参数三:ESTAND \033[0m")
sys.exit(1) # 非零退出码表示错误
# 加载环境变量
config = ConfigParser()
config.read("/home/zhangshan/config.ini")
PRIVATE_TOKEN = config.get("gitlab","token")
GITLAB_URL = config.get("gitlab","url")
ARTIFACTORY_URL = config.get("artifact","url")
ARTIFACTORY_TOKEN = config.get("artifact","token")
# 删除 *.txt 文件
for file_path in glob.glob("*.txt"):
os.remove(file_path)
print(f"已删除 *.txt 文件: {file_path}")
SEARCH_PATH = sys.argv[1]
pipeline_url = sys.argv[2]
ESTAND = sys.argv[3]
query_artifactory(ARTIFACTORY_URL,ARTIFACTORY_TOKEN,SEARCH_PATH)
# now = datetime.now()
# build_date = now.strftime("%Y-%m-%d %H:%M:%S")
fetcher = GitLabPipeline(GITLAB_URL, PRIVATE_TOKEN)
BUILD_DATE = fetcher.get_date_from_pipeline_url(pipeline_url)
create_page()
mr_table.html
<div class="table-wrap">
<table class="wrapped relative-table confluenceTable stickyTableHeaders" style="width: 46.4643%; padding: 0px;">
<thead class="tableFloatingHeaderOriginal">
<tr>
<th scope="col" talk-marker="46" talk-page-id="3221943987" talk-page-version="5" class="confluenceTh">
MRS</th>
<th scope="col" talk-marker="47" talk-page-id="3221943987" talk-page-version="5" class="confluenceTh">MR
Desicion</th>
</tr>
</thead>
<thead class="tableFloatingHeader" style="display: none;">
<tr>
<th scope="col" talk-marker="46" talk-page-id="3221943987" talk-page-version="5" class="confluenceTh">
MRS</th>
<th scope="col" talk-marker="47" talk-page-id="3221943987" talk-page-version="5" class="confluenceTh">MR
Desicion</th>
</tr>
</thead>
<colgroup>
<col style="width: 55.4248%;" />
<col style="width: 44.5844%;" />
</colgroup>
<tbody>
<tr>
<td talk-marker="48" talk-page-id="3221943987" talk-page-version="5" class="confluenceTd"><br
talk-br="37" /></td>
<td rowspan="4" talk-marker="49" talk-page-id="3221943987" talk-page-version="5" class="confluenceTd">
<br talk-br="38" />
</td>
</tr>
<tr>
<td talk-marker="50" talk-page-id="3221943987" talk-page-version="5" class="confluenceTd"><br
talk-br="39" /></td>
</tr>
<tr>
<td talk-marker="51" talk-page-id="3221943987" talk-page-version="5" class="confluenceTd"><br
talk-br="40" /></td>
</tr>
<tr>
<td talk-marker="52" talk-page-id="3221943987" talk-page-version="5" class="confluenceTd"><br
talk-br="41" /></td>
</tr>
</tbody>
</table>
</div>
test_table.html
<div class="table-wrap">
<table class="relative-table wrapped confluenceTable" style="width: 904.0px;" border="1">
<colgroup class="">
<col class="" style="width: 385.0px;" />
<col class="" style="width: 518.0px;" />
</colgroup>
<tbody class="">
<tr class="">
<td style="text-align: left;vertical-align: top;" talk-marker="56" talk-page-id="3407581415"
talk-page-version="5" class="confluenceTd"><br talk-br="43" /></td>
<td style="text-align: left;vertical-align: top;" talk-marker="57" talk-page-id="3407581415"
talk-page-version="5" class="confluenceTd"><br talk-br="44" /></td>
</tr>
</tbody>
</table>
</div>
GitlabPipelineClient.py
import requests
from datetime import datetime
from urllib.parse import urlparse, unquote
class GitlabPipelineClient:
def __init__(self, pipeline_url: str, private_token: str):
"""
GitLab 客户端:自动解析 pipeline_url,简化调用
:param pipeline_url: 完整的 pipeline 页面 URL,例如:
https://git.abc.com/group/project/-/pipelines/123456
:param private_token: GitLab 私有令牌(需要 read_api 权限)
"""
self.pipeline_url = pipeline_url
self.private_token = private_token
self.headers = {"PRIVATE-TOKEN": private_token}
# 解析 URL
self.base_url, self.project_path, self.pipeline_id = self._parse_pipeline_url(pipeline_url)
def _parse_pipeline_url(self, url: str):
"""
解析 pipeline URL,提取 base_url、project_path 和 pipeline_id
"""
parsed = urlparse(url)
path_parts = parsed.path.strip("/").split("/")
if "-/pipelines" not in url:
raise ValueError("URL 不是合法的 GitLab pipeline 链接")
try:
# 提取 pipeline_id(最后一个部分)
pipeline_id = int(path_parts[-1])
# 提取项目路径(去掉最后的 ['-', 'pipelines', '{id}'] 三段)
project_path_parts = path_parts[:-3]
project_path = "/".join(project_path_parts)
base_url = f"{parsed.scheme}://{parsed.netloc}"
except Exception:
raise ValueError("无法解析 pipeline URL")
return base_url, unquote(project_path), pipeline_id
def _get(self, endpoint: str) -> dict:
"""
发送 GET 请求
"""
url = f"{self.base_url}{endpoint}"
response = requests.get(url, headers=self.headers)
if response.status_code == 200:
return response.json()
else:
raise Exception(f"请求失败: {response.status_code}\n{response.text}")
def get_pipeline_info(self) -> dict:
"""
获取 pipeline 的详细信息
"""
from urllib.parse import quote
encoded_project = quote(self.project_path, safe="")
endpoint = f"/api/v4/projects/{encoded_project}/pipelines/{self.pipeline_id}"
return self._get(endpoint)
def get_pipeline_date(self) -> str:
"""
获取 pipeline 的构建日期(如 "2025-07-24")
"""
info = self.get_pipeline_info()
created_at = info.get("created_at")
if not created_at:
raise ValueError("pipeline 中没有 created_at 字段")
dt = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
return dt.date().isoformat()
python 自动化创建 confluence 页面三的更多相关文章
- appium+python自动化46-安装app三种方式
前言 adb安装 1.在app自动化之前,首先手机上有要被测试的app,如何把电脑本地上的app安装到手机上呢?可以在运行自动化代码前,在cmd输入adb指令,把电脑app安装到手机上 adb ins ...
- selenium+python自动化102-登录页面滑动解锁(ActionChains)
前言 登录页面会遇到滑动解锁,滑动解锁的目的就是为了防止别人用代码登录(也就是为了防止你自动化登录),有些滑动解锁是需要去拼图这种会难一点. 有些直接拖到最最右侧就可以了,本篇讲下使用 seleniu ...
- Python 自动化脚本学习(三)
函数 例子 def hello(): print("hello" + "world"); 有参数的函数 def hello(name): print(" ...
- python自动化工具之pywinauto(一个实例)结合pyuserinput
以下是pywinauto使用指南.这个窗口句柄可以在Spy++中查看 (Microsoft Spy++(查看窗口句柄) 10.00.30319 官方最新绿色版) python自动化工具之pywinau ...
- python selenium自动化点击页面链接测试
python selenium自动化点击页面链接测试 需求:现在有一个网站的页面,我希望用python自动化的测试点击这个页面上所有的在本窗口跳转,并且是本站内的链接,前往到链接页面之后在通过后退返回 ...
- RobotFramework + Python 自动化入门 三 (Web自动化)
在<RobotFramwork + Python 自动化入门 一>中,完成了一个Robot环境搭建及测试脚本的创建和执行. 在<RobotFramwork + Python 自动化入 ...
- javascript 中数组的创建 添加 与将数组转换成字符串 页面三种提交请求的方式
创建js数组 var array=new Array(); Java中创建数组 private String[] array=new String[3]; 两个完全不同的,js中是可变长度的 添加内容 ...
- Selenium2+python自动化39-关于面试的题
前言 最近看到群里有小伙伴贴出一组面试题,最近又是跳槽黄金季节,小编忍不住抽出一点时间总结了下, 回答不妥的地方欢迎各位高手拍砖指点. 一.selenium中如何判断元素是否存在? 首先selen ...
- Docker+jenkins 运行 python 自动化
一.实现思路 在 Linux 服务器安装 docker 创建 jenkins 容器 根据自动化项目依赖包构建 python 镜像(构建自动化 python 环境) 运行新的 python 容器,执行 ...
- Selenium Web 自动化 - 项目实战(三)
Selenium Web 自动化 - 项目实战(三) 2016-08-10 目录 1 关键字驱动概述2 框架更改总览3 框架更改详解 3.1 解析新增页面目录 3.2 解析新增测试用例目录 3. ...
随机推荐
- P6790 [SNOI2020] 生成树 题解
感觉很多题解都说的不是很清楚?如何将三操作与二操作合并起来一起处理好像都没有提到.(也有可能是我太菜了,看了半天才懂) 思路 考虑这个图一定是一个广义串并联图.为什么呢? 广义串并联图的定义是不存在一 ...
- Vue3 学习-初识体验-helloworld
在数据分析中有一个最重要的一环就是数据可视化, 数据报表的开发. 从我从业这几年的经历上看, 经历了从业务系统导表格数据, 到Excel+PPT, 再是开源报表工具, 再是主流商业BI产品(低/零代码 ...
- 开发一组交易信号--K线与10均线的关系
K线上穿/下穿10日均线,如图所示: 类似于,之前写的基于聚宽平台写的一个典型的双均线策略思想类似,当K线上穿10日均线时,发出买入信号,当K先下穿10日均线时,发出卖出信号. 比较当前的收盘价和MA ...
- K-th Symbol in Grammar——LeetCode进阶路
原题链接https://leetcode.com/problems/k-th-symbol-in-grammar/ 题目描述 On the first row, we write a 0. Now i ...
- The Eclipse executable launcher was unable to locate its companion shared library
win10,笔者是安装eclipse2018.03的情况下,想安装java2019EE遇到的路径问题 1.解决方法 找到配置文件 打开,用记事本打开的话会糊成一行,建议用其他方式打开,例如笔者所用的N ...
- 明明是同一条SQL,为什么有时候走索引a,有时候却走索引b ?
前言 想象你是一家餐厅的服务员,面前有两个菜单: 菜单A:按菜品分类排列(前菜.主菜.甜点) 菜单B:按价格从低到高排列 当顾客说:"我要最便宜的川菜". 你会: 先用菜单B找到所 ...
- MVVM - Model和ViewModel的创建和配置
MVVM-Model和ViewModel的创建和配置 本文同时为b站WPF课程的笔记,相关示例代码 简介 MVVM:Model-View-ViewModel,是一种软件架构的模式.通过引入一个中间层V ...
- python基础—基本数据类型—数字,字符串,列表,元组,字典
1.运算符 (1)基本运算符 + 加法 - 减法 * 乘法 / 除法 ** 幂 // 取整(除法) % 取余(除法) (2)判断某个东西是否在某个东西里面包含 in no ...
- 《Java Web程序设计——MyEclipse的安装、配置》
Java Web程序设计--MyEclipse的安装.配置 具体安装.配置过程请参考下面的博客 MyEclipse安装.配置.测试 -- 博客园 原博客中所需文件均存放于百度网盘中 如下载速度较慢,可 ...
- 如何从Docker image提取 Dockerfile
参考链接:https://github.com/cucker0/dockerimage2df 参考链接:https://github.com/cucker0/docker/blob/main/md/由 ...