PHP在客户访问 get_full_url() 所驻位置时生成 完整的URL

本文介绍了一个用于获取当前页面完整URL的PHP函数,包括协议、主机名、端口及请求路径等信息,并提供了多个来源作为参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

该函数结合多方参考组合而出,如有不妥请批评指正,谢谢!

	//获取当前完整URL
	function get_full_url(){
		$request_url='http';
			if(
				isset($_SERVER["HTTPS"])&&$_SERVER["HTTPS"] == "on"
				||isset($_SERVER['SERVER_PORT'])&&$_SERVER['SERVER_PORT']=='443'
			)$request_url.='s';

			$request_url.='://';

			if(isset($_SERVER['HTTP_X_FORWARDED_HOST'])&&!empty($_SERVER['HTTP_X_FORWARDED_HOST']))
				$request_url.=$_SERVER['HTTP_X_FORWARDED_HOST'];
			elseif(isset($_SERVER['HTTP_HOST'])&&!empty($_SERVER['HTTP_HOST']))
				$request_url.=$_SERVER['HTTP_HOST'];
			else
				$request_url.=$_SERVER['SERVER_NAME'].':'.$_SERVER["SERVER_PORT"];

			if(isset($_SERVER['REQUEST_URI']))
				$request_url.=$_SERVER['REQUEST_URI'];
			else {
				if(isset($_SERVER['PHP_SELF']))
					$request_url .= $_SERVER['PHP_SELF'];
				else $request_url .=$_SERVER['SCRIPT_NAME'];
				$request_url .= '?';
				if(isset($_SERVER['argv'][0]))
					$request_url .= $_SERVER['argv'][0];
				elseif(isset($_SERVER['QUERY_STRING']))
					$request_url .=$_SERVER['QUERY_STRING'];
				elseif(isset($_SERVER['PATH_INFO']))
						$request_url .=$_SERVER['PATH_INFO'];
			}
		return $request_url;
	}
参考出处1: http://blog.youkuaiyun.com/fz04003/article/details/6115247

参考出处2:http://www.cnblogs.com/A-Song/archive/2011/12/14/2288215.html

参考出处3:http://www.jb51.net/article/77198.htm

参考出处4:http://www.ludou.org/get_current_page_url.html

import sys import json import csv import os import time import re import requests import concurrent.futures from urllib.parse import urlparse, urljoin from requests.exceptions import RequestException from PyQt5.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTabWidget, QLabel, QLineEdit, QPushButton, QTextEdit, QTableWidget, QTableWidgetItem, QTreeWidget, QTreeWidgetItem, QHeaderView, QFileDialog, QMessageBox, QSplitter, QSpinBox, QAction, QDialog, QFormLayout, QDialogButtonBox, QProgressBar, QGroupBox ) from PyQt5.QtCore import Qt, QThread, pyqtSignal from PyQt5.QtGui import QIcon # 忽略SSL证书验证的警告信息 requests.packages.urllib3.disable_warnings() class FingerprintManager: def __init__(self): self.fingerprints = [] self.default_file_path = os.path.join(os.path.expanduser("~"), "cms_fingerprints.json") if not self.load_from_default_file(): self.load_default_fingerprints() def load_default_fingerprints(self): # 优化默认指纹库,确保正则表达式正确 self.fingerprints = [ { "cms": "WordPress", "version": "", "confidence": 0, "http_headers": [ {"header": "X-Powered-By", "pattern": "PHP/.*", "score": 10, "type": "general"} ], "html_content": [ {"pattern": "<meta name=\"generator\" content=\"WordPress ([\\d.]+)\"", "score": 150, "type": "core", "version_group": 1}, {"pattern": "wp-content/themes/([^/]+)", "score": 80, "type": "specific"}, {"pattern": "wp-includes/js/wp-util.js", "score": 90, "type": "specific"} ], "url_paths": [ {"path": "/wp-admin", "score": 80, "type": "specific"}, {"path": "/wp-login.php", "score": 100, "type": "core"} ] }, { "cms": "示例站点", "version": "", "confidence": 0, "html_content": [ {"pattern": "恭喜, 站点创建成功!", "score": 120, "type": "core"}, {"pattern": "<h3>这是默认index.html,本页面由系统自动生成</h3>", "score": 100, "type": "core"} ], "url_paths": [] }, { "cms": "Nginx", "version": "", "confidence": 0, "http_headers": [ {"header": "Server", "pattern": "nginx/([\\d.]+)", "score": 90, "type": "core", "version_group": 1} ], "html_content": [ {"pattern": "If you see this page, the nginx web server is successfully installed", "score": 120, "type": "core"} ] }, { "cms": "Drupal", "version": "", "html_content": [ {"pattern": "<meta name=\"generator\" content=\"Drupal ([\\d.]+)\"", "score": 150, "type": "core", "version_group": 1}, {"pattern": "sites/default/files", "score": 70, "type": "specific"} ], "url_paths": [ {"path": "/sites/all", "score": 80, "type": "specific"} ] }, { "cms": "ThinkPHP", "version": "", "html_content": [ {"pattern": "think\\\\Exception", "score": 100, "type": "core"}, {"pattern": "app\\\\controller", "score": 80, "type": "specific"} ] }, { "cms": "Yii", "version": "", "html_content": [ {"pattern": "yii\\\\base\\\\Exception", "score": 100, "type": "core"}, {"pattern": "yii\\\\web\\\\HttpException", "score": 90, "type": "specific"} ] }, { "cms": "Phalcon", "version": "", "html_content": [ {"pattern": "Phalcon\\\\Exception", "score": 100, "type": "core"} ] }, { "cms": "FuelPHP", "version": "", "html_content": [ {"pattern": "Fuel\\\\Exception", "score": 100, "type": "core"} ] }, { "cms": "Habari", "version": "", "html_content": [ {"pattern": "Habari\\\\Core\\\\Exception", "score": 100, "type": "core"} ] }, { "cms": "帝国CMS", "version": "", "html_content": [ {"pattern": "ecmsinfo\\(", "score": 100, "type": "core"} ] } ] self.save_to_default_file() def load_from_default_file(self): try: if os.path.exists(self.default_file_path): with open(self.default_file_path, 'r', encoding='utf-8') as f: loaded_data = json.load(f) valid_fingerprints = [] for fp in loaded_data: if self._is_valid_fingerprint(fp): cleaned_fp = self._clean_fingerprint(fp) valid_fingerprints.append(cleaned_fp) else: print(f"跳过无效指纹: {fp}") self.fingerprints = valid_fingerprints return True return False except Exception as e: print(f"从默认文件加载指纹失败: {e}") return False def _clean_fingerprint(self, fp): """清理指纹中的正则表达式,修复常见错误""" for header in fp.get('http_headers', []): if 'pattern' in header: header['pattern'] = self._fix_regex_pattern(header['pattern']) for html in fp.get('html_content', []): if 'pattern' in html: html['pattern'] = self._fix_regex_pattern(html['pattern']) for url in fp.get('url_paths', []): if 'pattern' in url: url['pattern'] = self._fix_regex_pattern(url['pattern']) return fp def _fix_regex_pattern(self, pattern): """修复常见的正则表达式错误""" if not pattern: return "" # 修复未转义的反斜杠 fixed = re.sub(r'(?<!\\)\\(?!["\\/])', r'\\\\', pattern) # 修复未闭合的括号 open_count = fixed.count('(') close_count = fixed.count(')') if open_count > close_count: fixed += ')' * (open_count - close_count) # 修复不完整的字符类 if '[' in fixed and ']' not in fixed: fixed += ']' return fixed def _is_valid_fingerprint(self, fp): required_fields = ["cms"] for field in required_fields: if field not in fp: return False if not fp["cms"].strip(): return False for key in ["http_headers", "html_content", "url_paths"]: if key not in fp: fp[key] = [] return True def save_to_default_file(self): try: dir_path = os.path.dirname(self.default_file_path) if not os.path.exists(dir_path): os.makedirs(dir_path) with open(self.default_file_path, 'w', encoding='utf-8') as f: json.dump(self.fingerprints, f, indent=4, ensure_ascii=False) return True except Exception as e: print(f"保存指纹到默认文件失败: {e}") return False def add_fingerprint(self, fingerprint): if self._is_valid_fingerprint(fingerprint): cleaned = self._clean_fingerprint(fingerprint) self.fingerprints.append(cleaned) self.save_to_default_file() return True print(f"无法添加无效指纹: {fingerprint}") return False def remove_fingerprint(self, index): if 0 <= index < len(self.fingerprints): self.fingerprints.pop(index) self.save_to_default_file() def update_fingerprint(self, index, fingerprint): if 0 <= index < len(self.fingerprints) and self._is_valid_fingerprint(fingerprint): cleaned = self._clean_fingerprint(fingerprint) self.fingerprints[index] = cleaned self.save_to_default_file() return True return False def clear_fingerprints(self): self.fingerprints = [] self.save_to_default_file() return True def restore_default_fingerprints(self): self.load_default_fingerprints() return True def get_fingerprints(self): return self.fingerprints def export_fingerprints(self, filename): try: with open(filename, 'w', encoding='utf-8') as f: json.dump(self.fingerprints, f, indent=4, ensure_ascii=False) return True except Exception as e: print(f"导出失败: {e}") return False def import_fingerprints(self, filename): try: with open(filename, 'r', encoding='utf-8') as f: imported_data = json.load(f) valid_fingerprints = [] for fp in imported_data: if self._is_valid_fingerprint(fp): cleaned = self._clean_fingerprint(fp) valid_fingerprints.append(cleaned) else: print(f"导入跳过无效指纹: {fp}") if valid_fingerprints: self.fingerprints = valid_fingerprints self.save_to_default_file() return True print("导入的指纹全部无效") return False except Exception as e: print(f"导入失败: {e}") return False class DetectionWorker(QThread): progress_signal = pyqtSignal(int, int, str) result_signal = pyqtSignal(dict) log_signal = pyqtSignal(str) finished_signal = pyqtSignal() def __init__(self, urls, fingerprints, max_threads=10, retry_count=2): super().__init__() self.urls = urls self.fingerprints = fingerprints self.max_threads = max_threads self.running = True self.retry_count = retry_count self.timeout = 15 # 超间(秒) # 缓存响应以提高性能 self.response_cache = {} def run(self): self.log_signal.emit("开始检测...") total = len(self.urls) for i, url in enumerate(self.urls): if not self.running: break self.progress_signal.emit(i+1, total, url) result = self.detect_cms(url) self.result_signal.emit(result) self.log_signal.emit("检测完成!") self.finished_signal.emit() def stop(self): self.running = False def preprocess_html(self, html): """优化HTML预处理:保留标签结构,不过度压缩""" processed = re.sub(r'\n\s+', '\n', html) processed = re.sub(r'>\s+<', '><', processed) return processed.strip() def escape_special_chars(self, pattern): """安全转义正则特殊字符""" if not pattern: return "" safe_pattern = re.sub(r'\\(?![\\.*+?^${}()|[\]sSdDwWtnbfvr])', r'\\\\', pattern) return safe_pattern def validate_regex(self, pattern): """验证正则表达式是否有效""" if not pattern: return True, pattern try: re.compile(pattern) return True, pattern except re.error as e: fixed = pattern if "bad escape" in str(e): fixed = re.sub(r'(?<!\\)\\(?!["\\/])', r'\\\\', pattern) elif "unterminated subpattern" in str(e): open_count = pattern.count('(') close_count = pattern.count(')') if open_count > close_count: fixed = pattern + ')' * (open_count - close_count) try: re.compile(fixed) self.log_signal.emit(f"自动修复正则表达式: {pattern} -> {fixed}") return True, fixed except re.error: return False, pattern def extract_version(self, content, pattern, group_idx): """从匹配结果中提取版本号""" if not pattern or group_idx is None: return "" try: match = re.search(pattern, content, re.IGNORECASE) if match and len(match.groups()) >= group_idx: return match.group(group_idx).strip() except re.error as e: self.log_signal.emit(f"版本提取正则错误 {pattern}: {str(e)}") return "" def fetch_url_content(self, url): """带重试机制的URL内容获取""" # 检查缓存 if url in self.response_cache: return self.response_cache[url] headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9' } for attempt in range(self.retry_count + 1): try: response = requests.get( url, headers=headers, allow_redirects=True, verify=False, timeout=self.timeout ) response.encoding = response.apparent_encoding # 缓存响应 self.response_cache[url] = response return response except RequestException as e: self.log_signal.emit(f"请求尝试 {attempt+1} 失败: {str(e)}") if attempt >= self.retry_count: return None time.sleep(1) return None def build_full_url(self, base_url, path): """构建完整URL""" if not path.startswith('/'): path = '/' + path parsed = urlparse(base_url) return f"{parsed.scheme}://{parsed.netloc}{path}" def check_url_path(self, base_url, path, pattern, item_score, weight): """检查URL路径特征 - 主动访问并验证""" full_url = self.build_full_url(base_url, path) feature_desc = f"URL路径: {full_url}" # 尝试获取响应 response = self.fetch_url_content(full_url) if response and response.status_code == 200: # 如果有正则模式,检查内容 if pattern: is_valid, fixed_pattern = self.validate_regex(pattern) if is_valid: try: if re.search(fixed_pattern, response.text, re.IGNORECASE): return True, feature_desc, item_score * weight except re.error as e: self.log_signal.emit(f"URL路径正则错误: {str(e)}") # 如果没有正则模式,只要状态200就算匹配 else: return True, feature_desc, item_score * weight return False, feature_desc, 0 def detect_cms(self, url): original_url = url if not url.startswith(('http://', 'https://')): urls_to_try = [f'http://{url}', f'https://{url}'] else: urls_to_try = [url] response = None for test_url in urls_to_try: response = self.fetch_url_content(test_url) if response: url = test_url break if not response: return { "url": original_url, "status": -1, "results": [{"cms": "无法访问", "version": "", "confidence": 0, "judgment_basis": ["无法建立连接"]}], "primary": {"cms": "无法访问", "version": "", "confidence": 0} } status_code = response.status_code headers = response.headers html_content = response.text final_url = response.url processed_html = self.preprocess_html(html_content) self.log_signal.emit(f"获取内容: {final_url} (状态码: {status_code})") cms_matches = [] min_score_threshold = 50 for cms in self.fingerprints: total_score = 0 version = "" # 记录详细的判断依据 judgment_basis = [] matched_features = [] unmatched_features = [] # 1. 匹配HTTP头特征 for header_item in cms.get('http_headers', []): header_name = header_item.get('header', '').lower() pattern = header_item.get('pattern', '') item_score = header_item.get('score', 0) feature_type = header_item.get('type', 'general') if not header_name or not pattern: continue is_valid, fixed_pattern = self.validate_regex(pattern) if not is_valid: self.log_signal.emit(f"跳过无效HTTP头正则: {pattern}") continue weight = 2 if feature_type == 'core' else 1 adjusted_score = item_score * weight feature_desc = f"HTTP头[{header_name}]匹配模式[{fixed_pattern}]" if header_name in headers: header_value = str(headers[header_name]) try: if re.search(fixed_pattern, header_value, re.IGNORECASE): total_score += adjusted_score matched_features.append(f"{feature_desc} (+{adjusted_score})") judgment_basis.append(f"✓ {feature_desc},匹配成功,加{adjusted_score}分") if 'version_group' in header_item: version = self.extract_version( header_value, fixed_pattern, header_item['version_group'] ) or version else: unmatched_features.append(f"{feature_desc} (未匹配)") judgment_basis.append(f"✗ {feature_desc},未匹配") except re.error as e: self.log_signal.emit(f"HTTP头正则执行错误 {fixed_pattern}: {str(e)}") else: unmatched_features.append(f"{feature_desc} (Header不存在)") judgment_basis.append(f"✗ {feature_desc},Header不存在") # 2. 匹配HTML内容特征 for html_item in cms.get('html_content', []): pattern = html_item.get('pattern', '').strip() item_score = html_item.get('score', 0) feature_type = html_item.get('type', 'general') if not pattern: continue is_valid, fixed_pattern = self.validate_regex(pattern) if not is_valid: self.log_signal.emit(f"跳过无效HTML正则: {pattern}") continue weight = 2.5 if feature_type == 'core' else (1.5 if feature_type == 'specific' else 1) adjusted_score = int(item_score * weight) feature_desc = f"HTML内容匹配模式[{fixed_pattern[:50]}{'...' if len(fixed_pattern)>50 else ''}]" try: if '<' in fixed_pattern and '>' in fixed_pattern: escaped_pattern = self.escape_special_chars(fixed_pattern) flexible_pattern = re.sub(r'\s+', r'\\s+', escaped_pattern) match_found = re.search(flexible_pattern, processed_html, re.IGNORECASE | re.DOTALL) else: match_found = re.search(fixed_pattern, processed_html, re.IGNORECASE | re.DOTALL) if match_found: total_score += adjusted_score matched_features.append(f"{feature_desc} (+{adjusted_score})") judgment_basis.append(f"✓ {feature_desc},匹配成功,加{adjusted_score}分") if 'version_group' in html_item: version = self.extract_version( processed_html, fixed_pattern, html_item['version_group'] ) or version else: unmatched_features.append(f"{feature_desc} (未匹配)") judgment_basis.append(f"✗ {feature_desc},未匹配") except re.error as e: self.log_signal.emit(f"HTML正则执行错误 {fixed_pattern}: {str(e)}") # 3. 匹配URL路径特征 - 使用线程池并发处理 url_path_tasks = [] with concurrent.futures.ThreadPoolExecutor(max_workers=min(5, self.max_threads)) as executor: for url_item in cms.get('url_paths', []): path = url_item.get('path', '') pattern = url_item.get('pattern', '') item_score = url_item.get('score', 0) feature_type = url_item.get('type', 'general') if not path: continue weight = 2 if feature_type == 'core' else 1 adjusted_score = item_score * weight # 提交任务到线程池 task = executor.submit( self.check_url_path, final_url, path, pattern, item_score, weight ) url_path_tasks.append((task, adjusted_score, path)) # 处理URL路径特征结果 for task, adjusted_score, path in url_path_tasks: try: matched, desc, score = task.result() if matched: total_score += score matched_features.append(f"{desc} (+{score})") judgment_basis.append(f"✓ {desc},访问成功,加{score}分") else: unmatched_features.append(f"{desc} (访问失败或未匹配)") judgment_basis.append(f"✗ {desc},访问失败或未匹配") except Exception as e: self.log_signal.emit(f"URL路径检查出错: {str(e)}") # 计算置信度 max_possible = sum( (h.get('score', 0) * (2 if h.get('type') == 'core' else 1)) for h in cms.get('http_headers', []) ) + sum( (h.get('score', 0) * (2.5 if h.get('type') == 'core' else 1)) for h in cms.get('html_content', []) ) + sum( (u.get('score', 0) * (2 if u.get('type') == 'core' else 1)) for u in cms.get('url_paths', []) ) confidence = min(100, int((total_score / max_possible) * 100)) if max_possible > 0 else 0 # 汇总判断依据 if matched_features: judgment_basis.insert(0, f"匹配到{len(matched_features)}个特征,总分{total_score}") else: judgment_basis.insert(0, f"未匹配到任何特征,总分0") if total_score >= min_score_threshold: cms_matches.append({ "cms": cms['cms'], "version": version or cms.get('version', ''), "score": total_score, "confidence": confidence, "judgment_basis": judgment_basis, # 存储详细判断依据 "features": matched_features }) cms_matches.sort(key=lambda x: (-x['confidence'], -x['score'])) filtered_results = [] if cms_matches: max_score = cms_matches[0]['score'] for match in cms_matches: if match['score'] >= max_score * 0.8 or match['confidence'] >= 70: filtered_results.append(match) # 如果没有匹配到任何结果,添加一个默认结果并说明原因 if not filtered_results: filtered_results.append({ "cms": "未知", "version": "", "confidence": 0, "judgment_basis": ["未匹配到任何已知CMS的特征", "请检查指纹库是否完整或添加新指纹"] }) primary_result = filtered_results[0] if filtered_results else { "cms": "未知", "version": "", "confidence": 0 } return { "url": final_url, "status": status_code, "results": filtered_results, "primary": primary_result } class AddFingerprintDialog(QDialog): def __init__(self, parent=None, fingerprint=None): super().__init__(parent) self.fingerprint = fingerprint self.setWindowTitle("编辑指纹" if fingerprint else "添加指纹") self.setGeometry(300, 300, 600, 500) self.init_ui() def init_ui(self): layout = QVBoxLayout() form_layout = QFormLayout() self.cms_input = QLineEdit() self.version_input = QLineEdit() form_layout.addRow("CMS名称*:", self.cms_input) form_layout.addRow("默认版本:", self.version_input) regex_help = QLabel("正则表达式提示: 反斜杠需要输入两次(\\\\),特殊字符(如. * + ?)需要转义") regex_help.setStyleSheet("color: #2980b9; font-size: 12px;") form_layout.addRow(regex_help) type_note = QLabel("特征类型说明: core(核心特征,权重高) > specific(特定特征) > general(通用特征)") type_note.setStyleSheet("color: #666; font-size: 12px;") form_layout.addRow(type_note) layout.addLayout(form_layout) # HTTP头特征表格 http_group = QWidget() http_layout = QVBoxLayout(http_group) http_layout.addWidget(QLabel("HTTP头特征:")) self.http_table = QTableWidget(0, 4) self.http_table.setHorizontalHeaderLabels(["Header", "Pattern", "Score", "Type(core/general)"]) self.http_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) http_btn_layout = QHBoxLayout() add_http_btn = QPushButton("添加") add_http_btn.clicked.connect(lambda: self.add_row(self.http_table, ["", "", "50", "general"])) remove_http_btn = QPushButton("移除") remove_http_btn.clicked.connect(lambda: self.remove_row(self.http_table)) http_btn_layout.addWidget(add_http_btn) http_btn_layout.addWidget(remove_http_btn) http_layout.addWidget(self.http_table) http_layout.addLayout(http_btn_layout) layout.addWidget(http_group) # HTML内容特征表格 html_group = QWidget() html_layout = QVBoxLayout(html_group) html_layout.addWidget(QLabel("HTML内容特征:")) self.html_table = QTableWidget(0, 4) self.html_table.setHorizontalHeaderLabels(["Pattern", "Score", "Type(core/specific)", "版本提取组(可选)"]) self.html_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) html_btn_layout = QHBoxLayout() add_html_btn = QPushButton("添加") add_html_btn.clicked.connect(lambda: self.add_row(self.html_table, ["", "80", "specific", ""])) remove_html_btn = QPushButton("移除") remove_html_btn.clicked.connect(lambda: self.remove_row(self.html_table)) html_btn_layout.addWidget(add_html_btn) html_btn_layout.addWidget(remove_html_btn) html_layout.addWidget(self.html_table) html_layout.addLayout(html_btn_layout) layout.addWidget(html_group) # URL路径特征表格 url_group = QWidget() url_layout = QVBoxLayout(url_group) url_layout.addWidget(QLabel("URL路径特征 (将主动访问这些路径):")) self.url_table = QTableWidget(0, 4) self.url_table.setHorizontalHeaderLabels(["Path", "Pattern(可选)", "Score", "Type(core/specific)"]) self.url_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) url_btn_layout = QHBoxLayout() add_url_btn = QPushButton("添加") add_url_btn.clicked.connect(lambda: self.add_row(self.url_table, ["", "", "60", "specific"])) remove_url_btn = QPushButton("移除") remove_url_btn.clicked.connect(lambda: self.remove_row(self.url_table)) url_btn_layout.addWidget(add_url_btn) url_btn_layout.addWidget(remove_url_btn) url_layout.addWidget(self.url_table) url_layout.addLayout(url_btn_layout) layout.addWidget(url_group) # 测试正则按钮 test_btn = QPushButton("测试选中的正则表达式") test_btn.clicked.connect(self.test_selected_regex) layout.addWidget(test_btn) # 确认按钮 btn_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) btn_box.accepted.connect(self.accept) btn_box.rejected.connect(self.reject) layout.addWidget(btn_box) self.setLayout(layout) self.load_fingerprint_data() def test_selected_regex(self): current_table = None pattern = "" if self.http_table.currentRow() >= 0: current_table = self.http_table item = self.http_table.item(self.http_table.currentRow(), 1) if item: pattern = item.text() elif self.html_table.currentRow() >= 0: current_table = self.html_table item = self.html_table.item(self.html_table.currentRow(), 0) if item: pattern = item.text() if not pattern: QMessageBox.information(self, "测试结果", "请选择一个正则表达式进行测试") return try: re.compile(pattern) QMessageBox.information(self, "测试结果", f"正则表达式有效:\n{pattern}") except re.error as e: fixed = pattern if "bad escape" in str(e): fixed = re.sub(r'(?<!\\)\\(?!["\\/])', r'\\\\', pattern) elif "unterminated subpattern" in str(e): open_count = pattern.count('(') close_count = pattern.count(')') if open_count > close_count: fixed = pattern + ')' * (open_count - close_count) try: re.compile(fixed) QMessageBox.information( self, "修复成功", f"原表达式无效: {str(e)}\n修复后表达式: {fixed}" ) if current_table == self.http_table: self.http_table.item(self.http_table.currentRow(), 1).setText(fixed) else: self.html_table.item(self.html_table.currentRow(), 0).setText(fixed) except re.error as e2: QMessageBox.warning( self, "测试失败", f"正则表达式无效: {str(e2)}\n表达式: {pattern}" ) def add_row(self, table, default_values): row = table.rowCount() table.insertRow(row) for col, val in enumerate(default_values): table.setItem(row, col, QTableWidgetItem(val)) def remove_row(self, table): row = table.currentRow() if row >= 0: table.removeRow(row) def load_fingerprint_data(self): if not self.fingerprint: return self.cms_input.setText(self.fingerprint.get("cms", "")) self.version_input.setText(self.fingerprint.get("version", "")) for header in self.fingerprint.get("http_headers", []): self.add_row(self.http_table, [ header.get("header", ""), header.get("pattern", ""), str(header.get("score", 50)), header.get("type", "general") ]) for html in self.fingerprint.get("html_content", []): self.add_row(self.html_table, [ html.get("pattern", ""), str(html.get("score", 80)), html.get("type", "specific"), str(html.get("version_group", "")) if "version_group" in html else "" ]) for path in self.fingerprint.get("url_paths", []): self.add_row(self.url_table, [ path.get("path", ""), path.get("pattern", ""), str(path.get("score", 60)), path.get("type", "specific") ]) def validate_regex(self, pattern): try: if pattern: re.compile(pattern) return True except re.error as e: QMessageBox.warning(self, "正则错误", f"模式 '{pattern}' 无效: {str(e)}\n请使用测试按钮修复") return False def get_fingerprint(self): cms_name = self.cms_input.text().strip() if not cms_name: QMessageBox.warning(self, "输入错误", "CMS名称不能为空") return None for row in range(self.html_table.rowCount()): pattern_item = self.html_table.item(row, 0) if pattern_item and not self.validate_regex(pattern_item.text().strip()): return None fingerprint = { "cms": cms_name, "version": self.version_input.text().strip(), "confidence": 0, "http_headers": [], "html_content": [], "url_paths": [] } for row in range(self.http_table.rowCount()): header = self.http_table.item(row, 0).text().strip() if self.http_table.item(row, 0) else "" pattern = self.http_table.item(row, 1).text().strip() if self.http_table.item(row, 1) else "" score = int(self.http_table.item(row, 2).text() or 50) f_type = self.http_table.item(row, 3).text().strip() or "general" if header and pattern: fingerprint["http_headers"].append({ "header": header, "pattern": pattern, "score": score, "type": f_type }) for row in range(self.html_table.rowCount()): pattern = self.html_table.item(row, 0).text().strip() if self.html_table.item(row, 0) else "" score = int(self.html_table.item(row, 1).text() or 80) f_type = self.html_table.item(row, 2).text().strip() or "specific" version_group = self.html_table.item(row, 3).text().strip() if pattern: item = { "pattern": pattern, "score": score, "type": f_type } if version_group and version_group.isdigit(): item["version_group"] = int(version_group) fingerprint["html_content"].append(item) for row in range(self.url_table.rowCount()): path = self.url_table.item(row, 0).text().strip() if self.url_table.item(row, 0) else "" pattern = self.url_table.item(row, 1).text().strip() if self.url_table.item(row, 1) else "" score = int(self.url_table.item(row, 2).text() or 60) f_type = self.url_table.item(row, 3).text().strip() or "specific" if path: fingerprint["url_paths"].append({ "path": path, "pattern": pattern, "score": score, "type": f_type }) return fingerprint class JudgmentBasisDialog(QDialog): """判断依据展示对话框""" def __init__(self, parent=None, result=None): super().__init__(parent) self.result = result self.setWindowTitle(f"识别依据 - {result['url']}") self.setGeometry(400, 200, 800, 600) self.init_ui() def init_ui(self): layout = QVBoxLayout() # 基本信息 basic_info = QLabel(f""" <h3>URL: {self.result['url']}</h3> <p>状态码: {self.result['status']}</p> """) layout.addWidget(basic_info) # 识别结果 results_group = QGroupBox("识别结果汇总") results_layout = QVBoxLayout() for i, res in enumerate(self.result['results']): is_primary = (i == 0) # 第一个结果是主要结果 result_label = QLabel(f""" <p><b>{'★ ' if is_primary else ''}{res['cms']} v{res['version']}</b> 置信度: {res['confidence']}%</p> """) results_layout.addWidget(result_label) results_group.setLayout(results_layout) layout.addWidget(results_group) # 详细判断依据 basis_group = QTabWidget() for res in self.result['results']: text_edit = QTextEdit() text_edit.setReadOnly(True) # 显示所有判断依据 text_edit.setText("\n".join(res['judgment_basis'])) basis_group.addTab(text_edit, f"{res['cms']} (置信度{res['confidence']}%)") layout.addWidget(basis_group) # 关闭按钮 btn_box = QDialogButtonBox(QDialogButtonBox.Ok) btn_box.accepted.connect(self.accept) layout.addWidget(btn_box) self.setLayout(layout) class CMSDetectorApp(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("多CMS识别工具 (带判断依据)") self.setGeometry(100, 100, 1200, 800) self.fingerprint_manager = FingerprintManager() self.results = [] self.create_menu() self.init_ui() self.apply_styles() def create_menu(self): menubar = self.menuBar() file_menu = menubar.addMenu("文件") import_action = QAction("导入网站列表", self) import_action.triggered.connect(self.import_urls) file_menu.addAction(import_action) export_action = QAction("导出结果", self) export_action.triggered.connect(self.export_results) file_menu.addAction(export_action) file_menu.addSeparator() exit_action = QAction("退出", self) exit_action.setShortcut("Ctrl+Q") exit_action.triggered.connect(self.close) file_menu.addAction(exit_action) fingerprint_menu = menubar.addMenu("指纹库") add_fingerprint_action = QAction("添加指纹", self) add_fingerprint_action.triggered.connect(self.add_fingerprint) fingerprint_menu.addAction(add_fingerprint_action) import_fingerprint_action = QAction("导入指纹库", self) import_fingerprint_action.triggered.connect(self.import_fingerprints) fingerprint_menu.addAction(import_fingerprint_action) export_fingerprint_action = QAction("导出指纹库", self) export_fingerprint_action.triggered.connect(self.export_fingerprints) fingerprint_menu.addAction(export_fingerprint_action) clear_fingerprint_action = QAction("清空指纹库", self) clear_fingerprint_action.triggered.connect(self.clear_fingerprints) fingerprint_menu.addAction(clear_fingerprint_action) restore_default_action = QAction("恢复默认指纹库", self) restore_default_action.triggered.connect(self.restore_default_fingerprints) fingerprint_menu.addAction(restore_default_action) help_menu = menubar.addMenu("帮助") about_action = QAction("关于", self) about_action.triggered.connect(self.show_about) help_menu.addAction(about_action) def init_ui(self): main_widget = QWidget() main_layout = QVBoxLayout() self.tabs = QTabWidget() self.detection_tab = self.create_detection_tab() self.fingerprint_tab = self.create_fingerprint_tab() self.tabs.addTab(self.detection_tab, "网站检测") self.tabs.addTab(self.fingerprint_tab, "指纹库管理") main_layout.addWidget(self.tabs) main_widget.setLayout(main_layout) self.setCentralWidget(main_widget) self.status_bar = self.statusBar() self.status_label = QLabel("就绪") self.status_bar.addWidget(self.status_label) self.detection_thread = None def apply_styles(self): self.setStyleSheet(""" QMainWindow { background-color: #f0f0f0; } QTabWidget::pane { border: 1px solid #cccccc; background: white; } QTableWidget { background-color: white; alternate-background-color: #f8f8f8; gridline-color: #e0e0e0; } QHeaderView::section { background-color: #e0e0e0; padding: 4px; border: 1px solid #d0d0d0; } QPushButton { background-color: #4a86e8; color: white; border: none; padding: 5px 10px; border-radius: 4px; } QPushButton:hover { background-color: #3a76d8; } QPushButton:pressed { background-color: #2a66c8; } QPushButton:disabled { background-color: #a0a0a0; } QPushButton#clearBtn { background-color: #e74c3c; } QPushButton#clearBtn:hover { background-color: #c0392b; } QPushButton#restoreBtn { background-color: #27ae60; } QPushButton#restoreBtn:hover { background-color: #219653; } """) def create_detection_tab(self): tab = QWidget() layout = QVBoxLayout() # URL输入区域 control_layout = QHBoxLayout() self.url_input = QLineEdit() self.url_input.setPlaceholderText("输入网站URL (例如: example.com 或 http://example.com)") add_url_btn = QPushButton("添加URL") add_url_btn.clicked.connect(self.add_single_url) import_btn = QPushButton("导入URL列表") import_btn.clicked.connect(self.import_urls) clear_btn = QPushButton("清空列表") clear_btn.clicked.connect(self.clear_urls) control_layout.addWidget(self.url_input, 4) control_layout.addWidget(add_url_btn, 1) control_layout.addWidget(import_btn, 1) control_layout.addWidget(clear_btn, 1) layout.addLayout(control_layout) # URL列表区域 url_list_layout = QVBoxLayout() url_list_layout.addWidget(QLabel("待检测网站列表:")) self.url_list = QTextEdit() self.url_list.setPlaceholderText("每行一个URL") self.url_list.setMinimumHeight(80) url_list_layout.addWidget(self.url_list) layout.addLayout(url_list_layout) # 检测控制区域 detection_control_layout = QHBoxLayout() self.thread_spin = QSpinBox() self.thread_spin.setRange(1, 20) self.thread_spin.setValue(5) self.thread_spin.setPrefix("线程数: ") self.retry_spin = QSpinBox() self.retry_spin.setRange(0, 3) self.retry_spin.setValue(1) self.retry_spin.setPrefix("重试次数: ") self.timeout_spin = QSpinBox() self.timeout_spin.setRange(5, 60) self.timeout_spin.setValue(15) self.timeout_spin.setPrefix("超间(秒): ") self.detect_btn = QPushButton("开始检测") self.detect_btn.clicked.connect(self.start_detection) self.stop_btn = QPushButton("停止检测") self.stop_btn.clicked.connect(self.stop_detection) self.stop_btn.setEnabled(False) detection_control_layout.addWidget(self.thread_spin) detection_control_layout.addWidget(self.retry_spin) detection_control_layout.addWidget(self.timeout_spin) detection_control_layout.addStretch() detection_control_layout.addWidget(self.detect_btn) detection_control_layout.addWidget(self.stop_btn) layout.addLayout(detection_control_layout) # 进度条 self.progress_bar = QProgressBar() self.progress_bar.setRange(0, 100) self.progress_bar.setTextVisible(True) layout.addWidget(self.progress_bar) # 结果展示区域 splitter = QSplitter(Qt.Vertical) self.result_table = QTableWidget(0, 6) # 增加一列显示操作 self.result_table.setHorizontalHeaderLabels(["URL", "状态", "CMS类型", "版本", "置信度(%)", "操作"]) self.result_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch) self.result_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeToContents) self.result_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) self.result_table.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeToContents) self.result_table.horizontalHeader().setSectionResizeMode(4, QHeaderView.ResizeToContents) self.result_table.horizontalHeader().setSectionResizeMode(5, QHeaderView.ResizeToContents) self.result_table.setAlternatingRowColors(True) self.log_area = QTextEdit() self.log_area.setReadOnly(True) self.log_area.setMinimumHeight(150) splitter.addWidget(self.result_table) splitter.addWidget(self.log_area) splitter.setSizes([400, 150]) layout.addWidget(splitter, 1) tab.setLayout(layout) return tab def create_fingerprint_tab(self): tab = QWidget() layout = QVBoxLayout() btn_layout = QHBoxLayout() add_btn = QPushButton("添加指纹") add_btn.clicked.connect(self.add_fingerprint) edit_btn = QPushButton("编辑指纹") edit_btn.clicked.connect(self.edit_fingerprint) remove_btn = QPushButton("删除指纹") remove_btn.clicked.connect(self.remove_fingerprint) clear_btn = QPushButton("清空指纹库") clear_btn.setObjectName("clearBtn") clear_btn.clicked.connect(self.clear_fingerprints) restore_btn = QPushButton("恢复默认") restore_btn.setObjectName("restoreBtn") restore_btn.clicked.connect(self.restore_default_fingerprints) import_btn = QPushButton("导入指纹库") import_btn.clicked.connect(self.import_fingerprints) export_btn = QPushButton("导出指纹库") export_btn.clicked.connect(self.export_fingerprints) btn_layout.addWidget(add_btn) btn_layout.addWidget(edit_btn) btn_layout.addWidget(remove_btn) btn_layout.addWidget(clear_btn) btn_layout.addWidget(restore_btn) btn_layout.addStretch() btn_layout.addWidget(import_btn) btn_layout.addWidget(export_btn) layout.addLayout(btn_layout) self.fingerprint_tree = QTreeWidget() self.fingerprint_tree.setHeaderLabels(["CMS名称", "版本", "核心特征数", "总特征数"]) self.fingerprint_tree.setColumnWidth(0, 200) self.fingerprint_tree.setSortingEnabled(True) self.populate_fingerprint_tree() layout.addWidget(self.fingerprint_tree, 1) tab.setLayout(layout) return tab def populate_fingerprint_tree(self): self.fingerprint_tree.clear() fingerprints = self.fingerprint_manager.get_fingerprints() for i, fp in enumerate(fingerprints): try: cms_name = fp["cms"] version = fp.get("version", "") core_features = 0 total_features = 0 for h in fp.get("http_headers", []): total_features += 1 if h.get("type") == "core": core_features += 1 for h in fp.get("html_content", []): total_features += 1 if h.get("type") == "core": core_features += 1 for u in fp.get("url_paths", []): total_features += 1 if u.get("type") == "core": core_features += 1 item = QTreeWidgetItem([ cms_name, version, str(core_features), str(total_features) ]) item.setData(0, Qt.UserRole, i) self.fingerprint_tree.addTopLevelItem(item) except Exception as e: self.log(f"处理指纹出错: {e},已跳过") def add_single_url(self): url = self.url_input.text().strip() if url: current_text = self.url_list.toPlainText() new_text = current_text + (("\n" + url) if current_text else url) self.url_list.setPlainText(new_text) self.url_input.clear() def import_urls(self): file_path, _ = QFileDialog.getOpenFileName( self, "导入URL列表", "", "文本文件 (*.txt);;所有文件 (*)" ) if file_path: try: with open(file_path, 'r', encoding='utf-8') as f: urls = [line.strip() for line in f if line.strip()] self.url_list.setPlainText("\n".join(urls)) self.log(f"成功导入 {len(urls)} 个URL") except Exception as e: QMessageBox.critical(self, "导入错误", f"导入失败: {str(e)}") def clear_urls(self): self.url_list.clear() def start_detection(self): urls_text = self.url_list.toPlainText().strip() if not urls_text: QMessageBox.warning(self, "警告", "请先添加要检测的URL") return urls = [url.strip() for url in urls_text.splitlines() if url.strip()] if not urls: QMessageBox.warning(self, "警告", "没有有效的URL") return self.result_table.setRowCount(0) self.results = [] max_threads = self.thread_spin.value() retry_count = self.retry_spin.value() timeout = self.timeout_spin.value() self.detection_thread = DetectionWorker( urls, self.fingerprint_manager.get_fingerprints(), max_threads, retry_count ) self.detection_thread.timeout = timeout self.detection_thread.progress_signal.connect(self.update_progress) self.detection_thread.result_signal.connect(self.add_result) self.detection_thread.log_signal.connect(self.log) self.detection_thread.finished_signal.connect(self.detection_finished) self.detect_btn.setEnabled(False) self.stop_btn.setEnabled(True) self.progress_bar.setRange(0, len(urls)) self.progress_bar.setValue(0) self.detection_thread.start() def stop_detection(self): if self.detection_thread and self.detection_thread.isRunning(): self.detection_thread.stop() self.log("检测已停止") self.detection_finished() def detection_finished(self): self.detect_btn.setEnabled(True) self.stop_btn.setEnabled(False) self.status_label.setText("检测完成") def update_progress(self, current, total, url): self.progress_bar.setMaximum(total) self.progress_bar.setValue(current) self.status_label.setText(f"正在检测: {url} ({current}/{total})") def show_judgment_basis(self, result): """显示判断依据对话框""" dialog = JudgmentBasisDialog(self, result) dialog.exec_() def add_result(self, result): self.results.append(result) row = self.result_table.rowCount() self.result_table.insertRow(row) # URL url_item = QTableWidgetItem(result["url"]) url_item.setFlags(url_item.flags() ^ Qt.ItemIsEditable) self.result_table.setItem(row, 0, url_item) # 状态码 status = result["status"] status_item = QTableWidgetItem(str(status)) status_item.setFlags(status_item.flags() ^ Qt.ItemIsEditable) if status == 200: status_item.setForeground(Qt.darkGreen) elif 400 <= status < 500: status_item.setForeground(Qt.darkRed) elif status >= 500: status_item.setForeground(Qt.darkMagenta) self.result_table.setItem(row, 1, status_item) # CMS类型(主结果) primary = result["primary"] cms_item = QTableWidgetItem(primary["cms"]) cms_item.setFlags(cms_item.flags() ^ Qt.ItemIsEditable) self.result_table.setItem(row, 2, cms_item) # 版本 version_item = QTableWidgetItem(primary["version"]) version_item.setFlags(version_item.flags() ^ Qt.ItemIsEditable) self.result_table.setItem(row, 3, version_item) # 置信度 confidence = primary["confidence"] confidence_item = QTableWidgetItem(f"{confidence}%") confidence_item.setFlags(confidence_item.flags() ^ Qt.ItemIsEditable) if confidence >= 90: confidence_item.setForeground(Qt.darkGreen) elif confidence >= 70: confidence_item.setForeground(Qt.darkBlue) elif confidence >= 50: confidence_item.setForeground(Qt.darkOrange) else: confidence_item.setForeground(Qt.darkGray) self.result_table.setItem(row, 4, confidence_item) # 查看依据按钮 view_btn = QPushButton("查看依据") # 使用lambda表达式传递当前result view_btn.clicked.connect(lambda checked, res=result: self.show_judgment_basis(res)) self.result_table.setCellWidget(row, 5, view_btn) def add_fingerprint(self): dialog = AddFingerprintDialog(self) if dialog.exec_() == QDialog.Accepted: fingerprint = dialog.get_fingerprint() if fingerprint and self.fingerprint_manager.add_fingerprint(fingerprint): self.populate_fingerprint_tree() self.log(f"已添加指纹: {fingerprint['cms']}") def edit_fingerprint(self): selected_items = self.fingerprint_tree.selectedItems() if not selected_items: QMessageBox.warning(self, "警告", "请选择一个指纹进行编辑") return item = selected_items[0] index = item.data(0, Qt.UserRole) fingerprints = self.fingerprint_manager.get_fingerprints() if index is None or not (0 <= index < len(fingerprints)): QMessageBox.warning(self, "错误", "无效的指纹索引") return fingerprint = fingerprints[index] dialog = AddFingerprintDialog(self, fingerprint) if dialog.exec_() == QDialog.Accepted: updated = dialog.get_fingerprint() if updated and self.fingerprint_manager.update_fingerprint(index, updated): self.populate_fingerprint_tree() self.log(f"已更新指纹: {updated['cms']}") def remove_fingerprint(self): selected_items = self.fingerprint_tree.selectedItems() if not selected_items: QMessageBox.warning(self, "警告", "请选择要删除的指纹") return item = selected_items[0] cms_name = item.text(0) index = item.data(0, Qt.UserRole) reply = QMessageBox.question( self, "确认删除", f"确定要删除 '{cms_name}' 的指纹吗?", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: self.fingerprint_manager.remove_fingerprint(index) self.populate_fingerprint_tree() self.log(f"已删除指纹: {cms_name}") def clear_fingerprints(self): if not self.fingerprint_manager.get_fingerprints(): QMessageBox.information(self, "提示", "指纹库已为空") return reply = QMessageBox.question( self, "确认清空", "确定要清空所有指纹吗?此操作不可恢复!", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: self.fingerprint_manager.clear_fingerprints() self.populate_fingerprint_tree() self.log("已清空所有指纹") def restore_default_fingerprints(self): reply = QMessageBox.question( self, "确认恢复", "确定要恢复默认指纹库吗?当前指纹将被替换!", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: self.fingerprint_manager.restore_default_fingerprints() self.populate_fingerprint_tree() self.log("已恢复默认指纹库") def import_fingerprints(self): file_path, _ = QFileDialog.getOpenFileName( self, "导入指纹库", "", "JSON文件 (*.json);;所有文件 (*)" ) if file_path and self.fingerprint_manager.import_fingerprints(file_path): self.populate_fingerprint_tree() self.log(f"成功导入指纹库: {file_path}") def export_fingerprints(self): file_path, _ = QFileDialog.getSaveFileName( self, "导出指纹库", "cms_fingerprints.json", "JSON文件 (*.json)" ) if file_path and self.fingerprint_manager.export_fingerprints(file_path): self.log(f"成功导出指纹库: {file_path}") def export_results(self): if not self.results: QMessageBox.warning(self, "警告", "没有结果可导出") return file_path, _ = QFileDialog.getSaveFileName( self, "导出结果", "", "CSV文件 (*.csv);;JSON文件 (*.json)" ) if not file_path: return try: if file_path.endswith(".csv"): with open(file_path, 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) writer.writerow(["URL", "状态", "CMS类型", "版本", "置信度(%)"]) for result in self.results: primary = result["primary"] writer.writerow([ result["url"], result["status"], primary["cms"], primary["version"], primary["confidence"] ]) elif file_path.endswith(".json"): # 导出完整结果,包括判断依据 with open(file_path, 'w', encoding='utf-8') as f: json.dump(self.results, f, indent=4, ensure_ascii=False) self.log(f"结果已导出到: {file_path}") except Exception as e: QMessageBox.critical(self, "导出错误", f"导出失败: {str(e)}") def log(self, message): timestamp = time.strftime("%H:%M:%S") self.log_area.append(f"[{timestamp}] {message}") def show_about(self): about_text = """ <h2>多CMS识别工具 (带判断依据)</h2> <p>版本: 2.3.0</p> <p>功能特点:</p> <ul> <li>显示详细的识别判断依据</li> <li>URL路径特征主动访问验证</li> <li>并发检测提高效率</li> <li>核心特征加权识别,准确率高</li> <li>支持正则表达式测试和验证</li> <li>可自定义超间和重试次数</li> </ul> <p>使用说明: 点击结果中的"查看依据"按钮可查看详细的识别依据</p> """ QMessageBox.about(self, "关于", about_text) def closeEvent(self, event): if self.detection_thread and self.detection_thread.isRunning(): reply = QMessageBox.question( self, "检测中", "检测仍在进行中,确定要退出吗?", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: self.detection_thread.stop() event.accept() else: event.ignore() else: event.accept() if __name__ == "__main__": if hasattr(Qt, 'AA_EnableHighDpiScaling'): QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) if hasattr(Qt, 'AA_UseHighDpiPixmaps'): QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True) app = QApplication(sys.argv) app.setStyle("Fusion") window = CMSDetectorApp() window.show() sys.exit(app.exec_()) 修改代码提高验证效率 其他无需修改 完整输出
最新发布
07-25
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值