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. ...
随机推荐
- JavaScript编程实践:打造优雅健壮的代码
@charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...
- RocketMQ源码详解(消息存储、Consumer)
消息存储 消息存储核心类 private final MessageStoreConfig messageStoreConfig; //消息配置属性 private final CommitLog c ...
- WPF 用 DrawingBrush实现图形的平铺
WPF平铺图像,一般用到 DrawingBrush 来实现对图形.图形的平铺,查阅了 微软官方文档的 DrawingBrush 属性 创建一个 DrawingBrush,其中: TileMode=& ...
- 开源ERP系统 Odoo 18 介绍
开源ERP系统 Odoo 18 介绍 1. ERP 简介 企业资源计划(ERP,Enterprise Resource Planning)是一种集成软件系统,旨在帮助企业管理和优化业务流程.ERP 系 ...
- Linux grep查询关键词首次或者最近一次出现的地方
有的时候,我们需要在应用日志中搜索关键词前后的的报错信息,但是由于应用的日志很多,我们只想查询关键词第一次或者最后一次出现的地方的前后几行日志,这时,就可以使用grep和管道命令处理了. 更多 ...
- sqlite:No module named _sqlite3
执行代码报错:"sqlite:No module named _sqlite3" 执行环境说明 某台服务器上执行DrissionPage相关程序报错,本机没有问题. 解决说明 本机 ...
- 搭建邮局-3.安装邮局前端roundcube和测试
目录 搭建邮局-1.安装hMailserver和配置邮局 https://www.cnblogs.com/daen/p/16040202.html 搭建邮局-2.添加域名和域名解析 https://w ...
- CSharp中的文件操作
在C#中,可以使用System.IO命名空间中的类来进行Windows文件操作.这些类提供了丰富的方法来处理文件和目录,包括创建.复制.删除.移动文件和目录,以及读取和写入文件等功能. 常用文件操作方 ...
- Flume+Kafka获取MySQL数据
摘要 MySQL被广泛用于海量业务的存储数据库,在大数据时代,我们亟需对其中的海量数据进行分析,但在MySQL之上进行大数据分析显然是不现实的,这会影响业务系统的运行稳定.如果我们要实时地分析这些数据 ...
- java 分批次读取大文件的三种方法
1. java 读取大文件的困难java 读取文件的一般操作是将文件数据全部读取到内存中,然后再对数据进行操作.例如 Path path = Paths.get("file path&quo ...