import requests import hashlib from bs4 import BeautifulSoup from datetime import datetime # 最简版本 - 获取所有产品的Bug def get_all_bugs(): """获取所有产品的Bug""" base_url = "http://39.170.26.156:8888" username = "qiaoxinjiu" password = "Qiao123456" print("获取禅道Bug列表") print("=" * 50) # 登录 session = requests.Session() password_md5 = hashlib.md5(password.encode()).hexdigest() login_resp = session.post(f"{base_url}/api.php/v1/tokens", json={"account": username, "password": password_md5}, headers={'Content-Type': 'application/json'}) if login_resp.status_code not in [200, 201]: print("登录失败") return token = login_resp.json().get('token') session.headers.update({'Token': token}) print("✅ 登录成功") # 获取所有产品 products_resp = session.get(f"{base_url}/api.php/v1/products") if products_resp.status_code != 200: print("获取产品失败") return products = products_resp.json().get('products', []) print(f"📦 共 {len(products)} 个产品") all_bugs = [] # 遍历每个产品获取Bug for product in products: product_id = product.get('id') product_name = product.get('name') if product_id != 2: continue print(f"\n获取产品 '{product_name}' 的Bug...") # 你的禅道实例没有开 REST bug 列表接口,只能爬取 HTML 页面 bugs_url = f"{base_url}/bug-browse-{product_id}.html" bugs_resp = session.get(bugs_url, params={'product': product_id, 'limit': 50}) if bugs_resp.status_code == 200: try: html = bugs_resp.text soup = BeautifulSoup(html, "html.parser") # 找 bug 列表的主 table(尽量精确一点) table = ( soup.find("table", id="bugList") or soup.find("table", class_="table") or soup.find("table") ) if not table: print(" ❌ 未找到 bug 列表 table") # print(html[:500]) # 调试时可以打开 return # 解析表头,建立「列名 -> 索引」映射 header_tr = table.find("tr") if not header_tr: print(" ❌ 未找到表头行") return header_map = {} for idx, th in enumerate(header_tr.find_all(["th", "td"])): text = th.get_text(strip=True) if not text: continue if "ID" == text or text == "编号": header_map["id"] = idx elif "标题" in text: header_map["title"] = idx elif "状态" in text: header_map["status"] = idx elif "严重" in text: header_map["severity"] = idx elif "优先" in text or "优先级" in text: header_map["pri"] = idx elif "指派" in text: header_map["assignedTo"] = idx elif "创建" in text or "打开" in text: # 如:创建日期 / 打开时间 header_map["openedDate"] = idx bugs = [] # 遍历表体行 for tr in table.find_all("tr")[1:]: tds = tr.find_all("td") if not tds: continue # id bug_id = tr.get("data-id") if not bug_id and "id" in header_map and header_map["id"] < len(tds): bug_id = tds[header_map["id"]].get_text(strip=True) if not bug_id: continue # 标题 title = "" if "title" in header_map and header_map["title"] < len(tds): cell = tds[header_map["title"]] link = cell.find("a") title = (link or cell).get_text(strip=True) # 状态 status = "" if "status" in header_map and header_map["status"] < len(tds): status = tds[header_map["status"]].get_text(strip=True) # 严重程度(有的版本用颜色块 + title 提示) severity = "" if "severity" in header_map and header_map["severity"] < len(tds): sev_cell = tds[header_map["severity"]] severity = sev_cell.get_text(strip=True) if not severity: sev_span = sev_cell.find("span") if sev_span and sev_span.get("title"): severity = sev_span.get("title").strip() # 优先级 pri = "" if "pri" in header_map and header_map["pri"] < len(tds): pri = tds[header_map["pri"]].get_text(strip=True) # 指派给 assigned_to = "" if "assignedTo" in header_map and header_map["assignedTo"] < len(tds): assigned_to = tds[header_map["assignedTo"]].get_text(strip=True) # 创建/打开日期 opened_date = "" if "openedDate" in header_map and header_map["openedDate"] < len(tds): opened_date = tds[header_map["openedDate"]].get_text(strip=True) bug = { "id": int(bug_id) if str(bug_id).isdigit() else bug_id, "title": title, "statusName": status, "severity": severity, "pri": pri, "assignedToName": assigned_to, "openedDate": opened_date, "product_name": product_name, } bugs.append(bug) print(f" ✅ 解析出 {len(bugs)} 个Bug") all_bugs.extend(bugs) except Exception as e: print(f" ❌ 解析失败: {e}") # 调试用:打印部分 HTML 看实际结构 # print(html[:800]) else: print(f" ❌ 获取失败: {bugs_resp.status_code} - {bugs_resp.text[:100]}") # 显示所有Bug if all_bugs: # 计算统计数据 today_str = datetime.today().strftime("%Y-%m-%d") today_md_str = datetime.today().strftime("%m-%d") total_bugs = len(all_bugs) today_bugs = [ b for b in all_bugs if ( today_str in str(b.get("openedDate", "")).strip() or today_md_str in str(b.get("openedDate", "")).strip() ) ] # 认为“未关闭”的状态:不在已关闭/已解决/已取消集合中 closed_keywords = {"已关闭", "已解决", "已完成", "已取消", "closed", "resolved", "done", "cancelled"} open_bugs = [ b for b in all_bugs if not any(kw in str(b.get("statusName", "")).lower() for kw in ["closed", "resolved", "done", "cancel"]) and not any(kw in str(b.get("statusName", "")) for kw in ["已关闭", "已解决", "已完成", "已取消"]) ] print(f"\n{'=' * 80}") print(f"📊 Bug统计") print(f"{'-' * 80}") print(f" 今日新增 Bug 数: {len(today_bugs)}") print(f" 未关闭 Bug 数: {len(open_bugs)}") print(f" 总 Bug 数: {total_bugs}") print(f"{'=' * 80}") # 按ID排序 all_bugs.sort(key=lambda x: x.get('id', 0), reverse=True) for i, bug in enumerate(all_bugs[:30], 1): # 只显示前30个 print( f"{i}. [#{bug.get('id')}] {bug.get('title', '无标题')[:40]}{'...' if len(bug.get('title', '')) > 40 else ''}") print(f" 产品: {bug.get('product_name')} | 状态: {bug.get('statusName', '未知')} | " f"严重: {bug.get('severity', '未知')} | 指派: {bug.get('assignedToName', '未指派')} | " f"创建时间: {bug.get('openedDate', '')}") print(f"http://39.170.26.156:8888/bug-view-{bug.get('id')}.html") if __name__ == "__main__": # 获取所有Bug版本 get_all_bugs()