python之关于QTimer.singleShot()的应用记录

本文介绍了一个使用PyQt5中QTimer.singleShot方法的示例,演示了如何在用户点击按钮后延迟500毫秒执行一次累加操作,并更新GUI界面上的显示。该应用展示了QTimer.singleShot在实现简单定时任务中的应用。

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

# -*- coding: utf-8 -*-
'''
关于QTimer.singleShot()的应用记录
'''
import time
import numpy as np
import matplotlib.pyplot as plt
import winsound
import sys
import sklearn
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel
from PyQt5.QtCore import QTimer, Qt

num = 0


class my_num(QWidget):
    def __init__(self):
        super(my_num, self).__init__()
        self.InitUi()

    def InitUi(self):
        self.resize(600, 400)
        self.setWindowTitle('demo')
        self.btn_1 = QPushButton('累加', self)
        self.btn_1.setGeometry(60, 120, 150, 100)
        self.btn_1.clicked.connect(self.m_add)
        self.label = QLabel("<h1>0</h1>", self)  # 设置Label的字体大小,通过html的格式设置
        self.label.setGeometry(300, 120, 300, 100)
        self.label.setAlignment(Qt.AlignCenter)
        self.mt = QTimer(self)

    def my_timer(self):
        if self.btn_1.clicked:
            self.mt.singleShot(500, self.m_add)

    def m_add(self):
        global num
        num += 1
        print('num = {}'.format(num))
        self.label.setText("<h1>{}</h1>".format(num))
        self.my_timer()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = my_num()
    w.show()
    sys.exit(app.exec_())

我曾经跨过山和大海,也穿过人山人海,我曾经拥有着的一切,转眼都飘散如烟,我曾经失落失望失掉所有方向,直到看见平凡才是唯一的答案。
——韩寒《平凡之路》

 

现在有个问题,我选择账号1进行抖音数据采集视频列表,然后切换平台的小红书的时候,发现账号1在转圈采集,好像显示抖音账号1和小红书账号1显示有点重叠,账号1-账号6都检查一下,切换平台的时候,不是要暂停采集数据,是放到后台采集数据,account_widget.py的from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QTableWidget, QTableWidgetItem, QFrame, QGridLayout, QMessageBox, QHeaderView, QAbstractItemView, QApplication, QMenu, QFileDialog, QStyle, QProgressBar,QComboBox ) from PyQt6.QtCore import Qt, QThread, pyqtSignal, QTimer from PyQt6.QtGui import QFont, QColor from tanchen_v2.core_v1.collector_threads import DouyinDataCollectorThread from tanchen_v2.ui.comment_window import CommentWindow from tanchen_v2.core_v1.loading_indicator import LoadingIndicator from tanchen_v2.core_v1.config_manager import ConfigManager import pandas as pd import json import os from tanchen_v2.core_v1.log_tc import logger class AccountWidget(QWidget): def __init__(self, index, config_manager,platform:str="douyin"): super().__init__() self.index = index self.account_index = index self.data = [] self.current_platform = platform #默认平台 self.platform = platform self.platform_configs=config_manager self.platform_data={}# 按平台存储数据:{"douyin":data,"xiaohongshu":data} self.config_manager = config_manager self.is_editing = True self.collector_thread = None self.comment_window = None self.init_ui() self.load_config() def init_ui(self): main_layout = QVBoxLayout() # 平台选择器 - 移到账号标题上方 # platform_layout = QHBoxLayout() # platform_layout.addWidget(QLabel("选择平台:")) # # self.platform_combo = QComboBox() # self.platform_combo.addItem("抖音", "douyin") # self.platform_combo.addItem("小红书", "xiaohongshu") # self.platform_combo.currentIndexChanged.connect(self.switch_platform) # platform_layout.addWidget(self.platform_combo) # platform_layout.addStretch() # main_layout.addLayout(platform_layout) # 平台选择器放在最上方 # 账号标题 title_layout = self.create_title_layout() main_layout.addLayout(title_layout) # 配置区域 config_layout = self.create_config_layout() main_layout.addLayout(config_layout) # 控制按钮 btn_layout = self.create_button_layout() main_layout.addLayout(btn_layout) # 表格容器 self.table_container = self.create_table_container() main_layout.addWidget(self.table_container, 1) # 状态栏 self.status_label = QLabel("就绪") self.status_label.setStyleSheet(""" QLabel { color: #666; font-size: 12px; padding: 5px; border-top: 1px solid #e0e0e0; } """) main_layout.addWidget(self.status_label) self.setLayout(main_layout) self.set_edit_mode(True) def update_platform(self,platform): """更新账号平台并保留配置""" # 保存当前平台配置 self.platform_configs[self.platform] = self.get_current_config() # 更新平台 self.platform = platform self.platform_config_group.setTitle(f"{platform}配置") # 加载新平台配置 self.update_platform_config_ui() def create_title_layout(self): layout = QHBoxLayout() self.title_label = QLabel(f"账号 {self.index + 1}") self.title_label.setFont(QFont("Arial", 12, QFont.Weight.Bold)) layout.addWidget(self.title_label) layout.addWidget(QLabel("备注:")) self.remark_input = QLineEdit() self.remark_input.setPlaceholderText("账号备注...") self.remark_input.setMaximumWidth(200) layout.addWidget(self.remark_input) self.edit_btn = self.create_icon_button("编辑配置", QStyle.StandardPixmap.SP_FileDialogDetailedView, "#FFC107", "#FFA000", "#FF8F00") self.edit_btn.clicked.connect(self.toggle_edit_mode) layout.addWidget(self.edit_btn) self.save_btn = self.create_icon_button("保存配置", QStyle.StandardPixmap.SP_DialogSaveButton, "#4CAF50", "#388E3C", "#2E7D32") self.save_btn.clicked.connect(self.save_config) layout.addWidget(self.save_btn) layout.addStretch() return layout def create_config_layout(self): layout = QGridLayout() layout.setColumnStretch(1, 1) # 关键字输入 layout.addWidget(QLabel("关键字:"), 0, 0) self.keyword_input = QLineEdit() self.keyword_input.setPlaceholderText("输入搜索关键字...") layout.addWidget(self.keyword_input, 0, 1) # Cookie输入 layout.addWidget(QLabel("Cookie:"), 1, 0) self.cookie_input = QLineEdit() self.cookie_input.setPlaceholderText("输入账号Cookie...") self.cookie_input.setEchoMode(QLineEdit.EchoMode.Password) layout.addWidget(self.cookie_input, 1, 1) # 显示/隐藏Cookie按钮 self.toggle_cookie_btn = QPushButton("显示") self.toggle_cookie_btn.setMaximumWidth(60) self.toggle_cookie_btn.setStyleSheet("QPushButton { padding: 3px; border-radius: 3px; }") self.toggle_cookie_btn.clicked.connect(self.toggle_cookie_visibility) layout.addWidget(self.toggle_cookie_btn, 1, 2) # 评论关键字输入 layout.addWidget(QLabel("评论关键字:"), 2, 0) self.comment_keyword_input = QLineEdit() self.comment_keyword_input.setPlaceholderText("输入评论关键字过滤...支持中文逗号、英文逗号、空格例如:测试1,测试2") layout.addWidget(self.comment_keyword_input, 2, 1) return layout def create_button_layout(self): layout = QHBoxLayout() # 开始采集按钮 self.start_btn = self.create_action_button("开始采集", QStyle.StandardPixmap.SP_MediaPlay, "#4CAF50", "#388E3C", "#2E7D32") self.start_btn.clicked.connect(self.start_collecting) layout.addWidget(self.start_btn) # 停止采集按钮 self.stop_btn = self.create_action_button("停止采集", QStyle.StandardPixmap.SP_MediaStop, "#f44336", "#D32F2F", "#B71C1C") self.stop_btn.clicked.connect(self.stop_collecting) self.stop_btn.setEnabled(False) layout.addWidget(self.stop_btn) # 获取评论按钮 self.get_comments_btn = self.create_action_button("获取评论", QStyle.StandardPixmap.SP_MessageBoxInformation, "#9C27B0", "#7B1FA2", "#4A148C") self.get_comments_btn.clicked.connect(self.get_comments) layout.addWidget(self.get_comments_btn) # 导出数据按钮 self.export_btn = self.create_action_button("导出数据", QStyle.StandardPixmap.SP_DialogSaveButton, "#2196F3", "#1976D2", "#0D47A1") self.export_btn.clicked.connect(self.export_data) layout.addWidget(self.export_btn) # 清空表格按钮 self.clear_table_btn = self.create_action_button("清空表格", QStyle.StandardPixmap.SP_TrashIcon, "#FF9800", "#F57C00", "#EF6C00") self.clear_table_btn.clicked.connect(self.clear_table_data) layout.addWidget(self.clear_table_btn) layout.addStretch() return layout def setup_table_columns(self,platform): """根据平台设置表格列""" if platform == "douyin": self.table.setColumnCount(7) self.table.setHorizontalHeaderLabels( ["序号", "作者昵称", "类型", "视频地址", "评论数量", "发布时间", "视频ID"]) # 设置列宽 self.table.setColumnWidth(0, 60) self.table.setColumnWidth(1, 150) self.table.setColumnWidth(2, 100) self.table.setColumnWidth(3, 300) self.table.setColumnWidth(4, 100) self.table.setColumnWidth(5, 150) self.table.setColumnWidth(6, 200) elif platform == "xiaohongshu": self.table.setColumnCount(8) self.table.setHorizontalHeaderLabels( ["序号", "作者昵称", "类型", "笔记链接", "点赞数", "收藏数", "发布时间", "笔记ID"]) # 设置列宽 self.table.setColumnWidth(0, 60) self.table.setColumnWidth(1, 150) self.table.setColumnWidth(2, 100) self.table.setColumnWidth(3, 300) self.table.setColumnWidth(4, 80) self.table.setColumnWidth(5, 80) self.table.setColumnWidth(6, 150) self.table.setColumnWidth(7, 200) # 添加新平台时只需在这里添加新的列配置 def create_table_container(self): container = QFrame() container.setFrameShape(QFrame.Shape.StyledPanel) container.setStyleSheet("background-color: white; border: 1px solid #E0E0E0; border-radius: 4px;") layout = QVBoxLayout(container) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) # v2数据表格 self.table = QTableWidget() self.setup_table_columns(self.current_platform) # 根据平台初始化表格列 self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Interactive) self.table.horizontalHeader().setSectionsMovable(True) self.table.verticalHeader().setVisible(False) self.table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) self.table.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) self.table.setSortingEnabled(True) self.table.setStyleSheet(""" QTableWidget { background-color: white; alternate-background-color: #f9f9f9; gridline-color: #e0e0e0; } QHeaderView::section { background-color: #f0f0f0; padding: 4px; border: 1px solid #e0e0e0; font-weight: bold; } """) self.table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.table.customContextMenuRequested.connect(self.show_context_menu) layout.addWidget(self.table) # 覆盖层用于显示加载指示器 self.overlay = QWidget(container) self.overlay.setGeometry(0, 0, container.width(), container.height()) self.overlay.setStyleSheet("background-color: rgba(255, 255, 255, 0.7);") overlay_layout = QVBoxLayout(self.overlay) overlay_layout.setAlignment(Qt.AlignmentFlag.AlignCenter) self.loading_indicator = LoadingIndicator() overlay_layout.addWidget(self.loading_indicator) self.overlay.hide() return container def create_icon_button(self, text, icon, base_color, hover_color, pressed_color): button = QPushButton(text) button.setIcon(self.style().standardIcon(icon)) text_color = "black" if base_color == "#FFC107" else "white" button.setStyleSheet(f""" QPushButton {{ background-color: {base_color}; color: {text_color}; padding: 5px; border-radius: 4px; }} QPushButton:hover {{ background-color: {hover_color}; }} QPushButton:pressed {{ background-color: {pressed_color}; }} """) return button def create_action_button(self, text, icon, base_color, hover_color, pressed_color): button = QPushButton(text) button.setIcon(self.style().standardIcon(icon)) button.setStyleSheet(f""" QPushButton {{ background-color: {base_color}; color: white; padding: 8px 16px; border-radius: 5px; font-weight: bold; }} QPushButton:hover {{ background-color: {hover_color}; }} QPushButton:pressed {{ background-color: {pressed_color}; }} QPushButton:disabled {{ background-color: {base_color}80; }} """) return button def toggle_cookie_visibility(self): if self.cookie_input.echoMode() == QLineEdit.EchoMode.Password: self.cookie_input.setEchoMode(QLineEdit.EchoMode.Normal) self.toggle_cookie_btn.setText("隐藏") else: self.cookie_input.setEchoMode(QLineEdit.EchoMode.Password) self.toggle_cookie_btn.setText("显示") def toggle_edit_mode(self): self.set_edit_mode(not self.is_editing) def set_edit_mode(self, edit_mode): self.is_editing = edit_mode self.remark_input.setReadOnly(not edit_mode) self.keyword_input.setReadOnly(not edit_mode) self.cookie_input.setReadOnly(not edit_mode) self.edit_btn.setVisible(not edit_mode) self.save_btn.setVisible(edit_mode) bg_color = "#f5f5f5" if not edit_mode else "white" self.remark_input.setStyleSheet(f"background-color: {bg_color}; border: 1px solid #e0e0e0; border-radius: 3px;") self.keyword_input.setStyleSheet( f"background-color: {bg_color}; border: 1px solid #e0e0e0; border-radius: 3px;") self.cookie_input.setStyleSheet(f"background-color: {bg_color}; border: 1px solid #e0e0e0; border-radius: 3px;") if edit_mode: self.status_label.setText("编辑模式:可以修改配置") else: self.status_label.setText("配置已锁定,点击'编辑配置'修改") def switch_platform(self,new_platform): """切换平台时的处理""" # 保存当前平台的数据 self.save_current_data() # 更新当前平台 self.current_platform = new_platform # 更新表格列 self.setup_table_columns(new_platform) # 加载新平台的配置 self.load_config() # 加载新平台的数据 self.load_platform_data() # 更新状态栏 self.status_label.setText(f"已切换到{new_platform}平台") def save_current_data(self): """保存当前平台的数据到内存""" # 从表格获取当前数据 data = [] for row in range(self.table.rowCount()): row_data = {} for col in range(self.table.columnCount()): item = self.table.item(row, col) if item: header = self.table.horizontalHeaderItem(col).text() row_data[header] = item.text() data.append(row_data) # 保存到平台数据字典 self.platform_data[self.current_platform] = data def load_platform_data(self): """加载当前平台的数据到表格""" data = self.platform_data.get(self.current_platform, []) # 清空表格 self.table.setRowCount(0) if not data: self.status_label.setText(f"{self.current_platform}平台无数据") return # 填充表格 self.table.setRowCount(len(data)) for row, item in enumerate(data): for col in range(self.table.columnCount()): header = self.table.horizontalHeaderItem(col).text() value = item.get(header, "") table_item = QTableWidgetItem(value) table_item.setFlags(Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled) self.table.setItem(row, col, table_item) self.status_label.setText(f"已加载{len(data)}条{self.current_platform}数据") def save_config(self): #v2 self.set_edit_mode(False) # 保存配置时按平台存储 config = { "remark": self.remark_input.text(), "keyword": self.keyword_input.text(), "cookie": self.cookie_input.text(), "comment_keyword": self.comment_keyword_input.text(), "platform": self.current_platform # 保存当前平台 } # 使用平台特定的键保存配置 self.config_manager.save_account_config(self.index, config, self.current_platform) self.status_label.setText("配置已保存") self.save_btn.setStyleSheet(""" QPushButton { background-color: #2E7D32; color: white; padding: 5px; border-radius: 4px; } """) QTimer.singleShot(500, self.restore_save_button_style) def restore_save_button_style(self): self.save_btn.setStyleSheet(""" QPushButton { background-color: #4CAF50; color: white; padding: 5px; border-radius: 4px; } QPushButton:hover { background-color: #388E3C; } QPushButton:pressed { background-color: #2E7D32; } """) def load_config(self): # 加载当前平台的配置 config = self.config_manager.load_account_config(self.index, self.current_platform) if config: self.remark_input.setText(config.get("remark", "")) self.keyword_input.setText(config.get("keyword", "")) self.cookie_input.setText(config.get("cookie", "")) self.comment_keyword_input.setText(config.get("comment_keyword", "")) logger.debug(f"平台:{self.current_platform}--账号{self.index + 1}加载配置成功") logger.debug(f"平台:{self.current_platform}--账号{self.index + 1}加载配置:{str(config)}") else: # 如果没有配置,初始化空值 self.remark_input.setText("") self.keyword_input.setText("") self.cookie_input.setText("") self.comment_keyword_input.setText("") logger.debug(f"平台:{ self.current_platform}--账号{self.index + 1}无配置,使用默认值") logger.debug(f"平台:{self.current_platform}--账号{self.index + 1}无配置, 加载配置:{str(config)}") def start_collecting(self): keyword = self.keyword_input.text().strip() cookie = self.cookie_input.text().strip() if not keyword: QMessageBox.warning(self, "输入错误", "请输入搜索关键字") return if not cookie: QMessageBox.warning(self, "输入错误", "请输入账号Cookie") return if self.collector_thread and self.collector_thread.isRunning(): self.collector_thread.stop() self.collector_thread.quit() self.collector_thread.wait(1000) self.overlay.show() self.loading_indicator.start() self.start_btn.setStyleSheet(""" QPushButton { background-color: #2E7D32; color: white; padding: 8px 16px; border-radius: 5px; font-weight: bold; } """) # 使用爬虫工厂创建对应平台的爬虫线程 try: from core.Crawler.crawler_factory import CrawlerFactory self.collector_thread = CrawlerFactory.create_crawler( platform=self.current_platform, account_index=self.index, config={ "keyword": keyword, "cookie": cookie } ) # 设置关键字 self.collector_thread.keyword = keyword except Exception as e: logger.warning(f"创建爬虫失败: {str(e)},{str(self.current_platform)}") self.handle_error(f"创建爬虫失败: {str(e)}") self.overlay.hide() self.loading_indicator.stop() return try: self.collector_thread.data_collected.connect(self.update_data) self.collector_thread.status_updated.connect(self.update_status) self.collector_thread.error_occurred.connect(self.handle_error) self.collector_thread.finished.connect(self.on_collection_finished) self.collector_thread.start(keyword) self.start_btn.setEnabled(False) self.stop_btn.setEnabled(True) self.keyword_input.setEnabled(False) self.cookie_input.setEnabled(False) self.status_label.setText(f"开始采集{self.current_platform}: {keyword}") self.status_label.setStyleSheet("color: #4CAF50; font-weight: bold;") except Exception as e: logger.warning(f"启动爬虫失败: {str(e)}") self.handle_error(f"启动爬虫失败: {str(e)}") def handle_error(self, error_message): self.status_label.setText(error_message) self.status_label.setStyleSheet("color: #f44336; font-weight: bold;") #self.stop_collecting() def update_status(self, message): self.status_label.setText(message) self.status_label.setStyleSheet("color: #4CAF50; font-weight: bold;") def on_collection_finished(self): self.start_btn.setEnabled(True) self.stop_btn.setEnabled(False) self.keyword_input.setEnabled(True) self.cookie_input.setEnabled(True) self.overlay.hide() self.loading_indicator.stop() self.restore_start_button_style() def restore_start_button_style(self): self.start_btn.setStyleSheet(""" QPushButton { background-color: #4CAF50; color: white; padding: 8px 16px; border-radius: 5px; font-weight: bold; } QPushButton:hover { background-color: #388E3C; } QPushButton:pressed { background-color: #2E7D32; } QPushButton:disabled { background-color: #81C784; } """) def stop_collecting(self): if self.collector_thread and self.collector_thread.isRunning(): self.collector_thread.stop() self.collector_thread.quit() self.collector_thread.wait(1000) self.start_btn.setEnabled(True) self.stop_btn.setEnabled(False) self.keyword_input.setEnabled(True) self.cookie_input.setEnabled(True) self.overlay.hide() self.loading_indicator.stop() self.restore_start_button_style() self.status_label.setText("采集已停止") self.status_label.setStyleSheet("color: #f44336; font-weight: bold;") def remove_duplicates(self, data): seen_urls = set() unique_data = [] url_field = "视频地址" if self.current_platform == "douyin" else "笔记链接" for item in data: item_url = item.get(url_field, "") if item_url and item_url not in seen_urls: seen_urls.add(item_url) unique_data.append(item) return unique_data def update_data(self, new_data): logger.debug("new_data",new_data) if not new_data or not isinstance(new_data, list): QMessageBox.warning(self, "数据错误", "接收到无效的数据格式") return if new_data and "状态" in new_data[0] and new_data[0]["状态"] == "错误": error_msg = new_data[0].get("消息", "未知错误") QMessageBox.warning(self, "采集错误", error_msg) self.stop_collecting() return unique_data = self.remove_duplicates(new_data) # 保存到当前平台的数据 self.platform_data[self.current_platform] = unique_data # 更新表格 self.table.setSortingEnabled(False) self.table.setRowCount(len(unique_data)) flags = Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled for row, item in enumerate(unique_data): # 根据平台动态填充表格 if self.current_platform == "douyin": # 抖音数据处理 self.table.setItem(row, 0, QTableWidgetItem(str(item.get("序号", "")))) self.table.setItem(row, 1, QTableWidgetItem(item.get("作者昵称", ""))) self.table.setItem(row, 2, QTableWidgetItem(item.get("类型", ""))) video_url = item.get("视频地址", "") url_item = QTableWidgetItem(video_url) url_item.setForeground(QColor("#0000FF")) url_item.setToolTip(video_url) self.table.setItem(row, 3, url_item) self.table.setItem(row, 4, QTableWidgetItem(str(item.get("评论数量", 0)))) self.table.setItem(row, 5, QTableWidgetItem(item.get("发布时间", ""))) self.table.setItem(row, 6, QTableWidgetItem(item.get("视频ID", ""))) elif self.current_platform == "xiaohongshu": # 小红书数据处理 self.table.setItem(row, 0, QTableWidgetItem(str(item.get("序号", "")))) self.table.setItem(row, 1, QTableWidgetItem(item.get("作者昵称", ""))) self.table.setItem(row, 2, QTableWidgetItem(item.get("类型", ""))) note_url = item.get("笔记链接", "") url_item = QTableWidgetItem(note_url) url_item.setForeground(QColor("#0000FF")) url_item.setToolTip(note_url) self.table.setItem(row, 3, url_item) self.table.setItem(row, 4, QTableWidgetItem(str(item.get("点赞数", 0)))) self.table.setItem(row, 5, QTableWidgetItem(str(item.get("收藏数", 0)))) self.table.setItem(row, 6, QTableWidgetItem(item.get("发布时间", ""))) self.table.setItem(row, 7, QTableWidgetItem(item.get("笔记ID", ""))) # 添加新平台时在这里添加处理逻辑 self.table.setSortingEnabled(True) self.table.scrollToBottom() self.status_label.setText(f"已采集 {len(unique_data)} 条{self.current_platform}数据") self.status_label.setStyleSheet("color: #4CAF50; font-weight: bold;") def export_data(self): # 获取当前平台的数据 current_data = self.platform_data.get(self.current_platform, []) if not current_data: QMessageBox.warning(self, "导出失败", f"没有可导出的{self.current_platform}数据") return try: remark = self.remark_input.text().strip() platform_name = self.current_platform default_name = f"{platform_name}数据_账号{self.index + 1}_{remark}.xlsx" if remark else f"{platform_name}数据_账号{self.index + 1}.xlsx" filename, _ = QFileDialog.getSaveFileName( self, "导出数据", default_name, "Excel文件 (*.xlsx)" ) if not filename: return if not filename.endswith('.xlsx'): filename += '.xlsx' df = pd.DataFrame(current_data) df.to_excel(filename, index=False) self.export_btn.setStyleSheet(""" QPushButton { background-color: #0D47A1; color: white; padding: 8px 16px; border-radius: 5px; font-weight: bold; } """) QTimer.singleShot(500, self.restore_export_button_style) QMessageBox.information(self, "导出成功", f"数据已导出到: {filename}") except Exception as e: QMessageBox.critical(self, "导出错误", f"导出失败: {str(e)}") def restore_export_button_style(self): self.export_btn.setStyleSheet(""" QPushButton { background-color: #2196F3; color: white; padding: 8px 16px; border-radius: 5px; font-weight: bold; } QPushButton:hover { background-color: #1976D2; } QPushButton:pressed { background-color: #0D47A1; } """) def clear_table_data(self): # 获取当前平台的数据 current_data = self.platform_data.get(self.current_platform, []) if not current_data: QMessageBox.information(self, "提示", f"{self.current_platform}平台无数据") return reply = QMessageBox.question( self, "确认清空", f"确定要清空{self.current_platform}的表格数据吗? (共{len(current_data)}条)", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No ) if reply == QMessageBox.StandardButton.Yes: self.platform_data[self.current_platform] = [] self.table.setRowCount(0) self.status_label.setText(f"{self.current_platform}数据已清空") def get_comments(self): """修改:处理多个关键字""" # 获取当前平台的数据 current_data = self.platform_data.get(self.current_platform, []) if not current_data: QMessageBox.warning(self, "操作失败", f"没有可用的{self.current_platform}数据") return # 获取关键字并分割 keyword_input = self.comment_keyword_input.text().strip() if keyword_input: # 使用 | 作为分隔符,支持中文逗号、英文逗号、空格 keywords = keyword_input.replace(',', ',').replace(' ', ',') keyword_filter = '|'.join([kw.strip() for kw in keywords.split(',') if kw.strip()]) else: keyword_filter = "" # 构建有效的视频数据 video_data = [] for idx, item in enumerate(current_data): try: # 根据平台获取不同的字段 if self.current_platform == "douyin": video_id = self.extract_douyin_video_id(item.get("视频地址", "")) comment_count = int(item.get("评论数量", 0)) video_url = item.get("视频地址", "") elif self.current_platform == "xiaohongshu": video_id = self.extract_xiaohongshu_note_id(item.get("笔记链接", "")) comment_count = int(item.get("评论数量", 0)) # 假设小红书也有评论数量字段 video_url = item.get("笔记链接", "") else: continue if video_id and comment_count > 0: video_data.append({ "序号": idx + 1, "视频地址": video_url, "视频ID": video_id, "评论数量": comment_count }) except (ValueError, TypeError, KeyError): continue if not video_data: QMessageBox.warning(self, "操作失败", "没有包含评论的视频数据") return try: # 获取当前账号的cookie cookie = self.cookie_input.text().strip() # 如果评论窗口已存在,先关闭它 if hasattr(self, 'comment_window') and self.comment_window: self.comment_window.close() self.comment_window = None self.comment_window = CommentWindow( parent=self, account_index=self.index, video_data=video_data, keyword_filter=keyword_filter, # 传递处理后的关键字 cookie=cookie, # 传递cookie use_proxy=False, # 默认不开启代理 account_keywords=keyword_filter, # 传递账号关键字 platform=self.current_platform # 传递当前平台 ) # 使用独立窗口模式 self.comment_window.setWindowFlag(Qt.WindowType.Window) self.comment_window.finished.connect(self.on_comment_window_closed) self.comment_window.show() except Exception as e: QMessageBox.critical(self, "错误", f"无法打开评论窗口: {str(e)}") def extract_douyin_video_id(self, video_url): """从抖音视频地址中提取视频ID""" # 示例URL: https://www.douyin.com/video/1234567890123456789 parts = video_url.split('/') if len(parts) >= 5: return parts[-1] # 返回最后一部分作为视频ID return "" def extract_xiaohongshu_note_id(self, note_url): """从小红书笔记地址中提取笔记ID""" # 示例URL: https://www.xiaohongshu.com/explore/1234567890abcdef12345678 parts = note_url.split('/') if len(parts) >= 5: return parts[-1] # 返回最后一部分作为笔记ID return "" def extract_video_id(self, video_url): """从视频地址中提取视频ID""" # 示例URL: https://www.douyin.com/video/1234567890123456789 parts = video_url.split('/') if len(parts) >= 5: return parts[-1] # 返回最后一部分作为视频ID return "" def on_comment_window_closed(self): # 安全地关闭评论窗口 if hasattr(self, 'comment_window') and self.comment_window: # 确保停止线程 if hasattr(self.comment_window, 'comment_thread') and self.comment_window.comment_thread.isRunning(): self.comment_window.comment_thread.stop() self.comment_window.comment_thread.quit() self.comment_window.comment_thread.wait(2000) self.comment_window = None def show_context_menu(self, position): menu = QMenu(self) selected_rows = set(item.row() for item in self.table.selectedItems()) if not selected_rows: return copy_row_action = menu.addAction("复制整行") copy_row_action.triggered.connect(lambda: self.copy_selected_row(selected_rows)) copy_cell_action = menu.addAction("复制单元格") copy_cell_action.triggered.connect(self.copy_selected_cell) menu.addSeparator() # 根据平台显示不同的复制选项 if self.current_platform == "douyin": copy_url_action = menu.addAction("复制视频地址") copy_url_action.triggered.connect(lambda: self.copy_selected_column(3)) copy_video_id_action = menu.addAction("复制视频ID") copy_video_id_action.triggered.connect(lambda: self.copy_selected_column(6)) elif self.current_platform == "xiaohongshu": copy_url_action = menu.addAction("复制笔记链接") copy_url_action.triggered.connect(lambda: self.copy_selected_column(3)) copy_note_id_action = menu.addAction("复制笔记ID") copy_note_id_action.triggered.connect(lambda: self.copy_selected_column(7)) copy_author_action = menu.addAction("复制作者昵称") copy_author_action.triggered.connect(lambda: self.copy_selected_column(1)) menu.exec(self.table.viewport().mapToGlobal(position)) def copy_selected_row(self, rows): text = "" for row in sorted(rows): row_data = [] for col in range(self.table.columnCount()): item = self.table.item(row, col) if item: row_data.append(item.text()) text += "\t".join(row_data) + "\n" clipboard = QApplication.clipboard() clipboard.setText(text.strip()) def copy_selected_cell(self): selected_items = self.table.selectedItems() if not selected_items: return text = "\n".join(item.text() for item in selected_items) clipboard = QApplication.clipboard() clipboard.setText(text) def copy_selected_column(self, column): selected_items = [item for item in self.table.selectedItems() if item.column() == column] if not selected_items: return text = "\n".join(item.text() for item in selected_items) clipboard = QApplication.clipboard() clipboard.setText(text) def resizeEvent(self, event): super().resizeEvent(event) if self.table_container: self.overlay.setGeometry(0, 0, self.table_container.width(), self.table_container.height())和main_window.py的import pandas as pd from PyQt6.QtWidgets import ( QMainWindow, QWidget, QVBoxLayout, QLabel, QTabWidget, QHBoxLayout, QPushButton, QStatusBar, QStyle, QFileDialog, QMessageBox, QDialog, QFormLayout, QLineEdit, QComboBox, QCheckBox ) from PyQt6.QtGui import QFont from PyQt6.QtCore import Qt, QTimer from tanchen_v2.ui.account_widget import AccountWidget from tanchen_v2.core_v1.config_manager import ConfigManager class ProxyConfigDialog(QDialog): def __init__(self, config_manager, parent=None): super().__init__(parent) self.setWindowTitle("天启代理配置") self.setGeometry(200, 200, 500, 300) self.config_manager = config_manager layout = QVBoxLayout() layout.setContentsMargins(15, 15, 15, 15) layout.setSpacing(10) # 加载配置 self.config = self.config_manager.load_proxy_config() or {} form_layout = QFormLayout() form_layout.setSpacing(10) # 创建控件 self.secret_edit = QLineEdit() self.secret_edit.setText(self.config.get("secret", "")) self.type_combo = QComboBox() self.type_combo.addItems(["json", "txt"]) if "type" in self.config: self.type_combo.setCurrentText(self.config["type"]) else: self.type_combo.setCurrentText("json") self.time_combo = QComboBox() self.time_combo.addItems(["3", "5", "10", "15"]) if "time" in self.config: self.time_combo.setCurrentText(self.config["time"]) else: self.time_combo.setCurrentText("5") self.mr_check = QCheckBox("IP去重") self.mr_check.setChecked(self.config.get("mr", "1") == "1") self.sign_edit = QLineEdit() self.sign_edit.setText(self.config.get("sign", "")) # 添加表单行 form_layout.addRow("提取秘钥 (secret):", self.secret_edit) form_layout.addRow("返回类型 (type):", self.type_combo) form_layout.addRow("IP使用时长 (time):", self.time_combo) form_layout.addRow("", self.mr_check) # 单独一行显示复选框 form_layout.addRow("用户签名 (sign):", self.sign_edit) layout.addLayout(form_layout) # 按钮 btn_layout = QHBoxLayout() save_btn = QPushButton("保存") save_btn.setStyleSheet(""" QPushButton { background-color: #4CAF50; color: white; padding: 8px 16px; border-radius: 4px; } QPushButton:hover { background-color: #45a049; } """) save_btn.clicked.connect(self.save_config) cancel_btn = QPushButton("取消") cancel_btn.setStyleSheet(""" QPushButton { background-color: #f44336; color: white; padding: 8px 16px; border-radius: 4px; } QPushButton:hover { background-color: #d32f2f; } """) cancel_btn.clicked.connect(self.reject) btn_layout.addStretch() btn_layout.addWidget(save_btn) btn_layout.addWidget(cancel_btn) btn_layout.addStretch() layout.addLayout(btn_layout) self.setLayout(layout) def save_config(self): self.config = { "secret": self.secret_edit.text(), "type": self.type_combo.currentText(), "time": self.time_combo.currentText(), "mr": "1" if self.mr_check.isChecked() else "0", "sign": self.sign_edit.text() } self.config_manager.save_proxy_config(self.config) self.accept() class DouyinDataCollector(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("多平台数据采集工具") self.setGeometry(100, 100, 1400, 800) self.setStyleSheet(""" QMainWindow { background-color: #f5f5f5; } QTabWidget::pane { border: 1px solid #e0e0e0; background: white; } QTabBar::tab { background: #e0e0e0; border: 1px solid #e0e0e0; padding: 8px 16px; border-top-left-radius: 4px; border-top-right-radius: 4px; margin-right: 2px; } QTabBar::tab:selected { background: white; border-bottom: none; } QTabBar::tab:!selected { margin-top: 4px; } """) self.accounts = [] self.config_manager = ConfigManager() self.current_platform = "douyin" self.init_ui() def init_ui(self): main_widget = QWidget() main_layout = QVBoxLayout() main_layout.setContentsMargins(15, 15, 15, 15) main_layout.setSpacing(15) # 标题 title_label = QLabel("多平台数据采集系统") title_label.setFont(QFont("Arial", 18, QFont.Weight.Bold)) title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) title_label.setStyleSheet(""" QLabel { color: #2196F3; padding: 10px; background-color: white; border-radius: 8px; border: 1px solid #e0e0e0; } """) main_layout.addWidget(title_label) ####################### # 平台选择和代理配置按钮 top_control_layout = QHBoxLayout() # 平台选择器 platform_layout = QHBoxLayout() platform_layout.addWidget(QLabel("选择平台:")) self.platform_combo = QComboBox() self.platform_combo.addItem("抖音", "douyin") self.platform_combo.addItem("小红书", "xiaohongshu") self.platform_combo.currentIndexChanged.connect(self.switch_platform) platform_layout.addWidget(self.platform_combo) top_control_layout.addLayout(platform_layout) # 代理配置按钮 top_control_layout.addStretch() self.proxy_config_btn = self.create_button( "天启代理配置", "#FF9800", "#F57C00", "#E65100", QStyle.StandardPixmap.SP_ComputerIcon) self.proxy_config_btn.clicked.connect(self.show_proxy_config) top_control_layout.addWidget(self.proxy_config_btn) main_layout.addLayout(top_control_layout) # 账号标签页 self.tabs = QTabWidget() self.tabs.setTabPosition(QTabWidget.TabPosition.North) self.tabs.setDocumentMode(True) for i in range(6): account_widget = AccountWidget(i, self.config_manager, self.current_platform) self.accounts.append(account_widget) tab_name = self.get_tab_name(i) # 添加平台参数 self.tabs.addTab(account_widget, tab_name) self.tabs.currentChanged.connect(self.update_tab_names) main_layout.addWidget(self.tabs, 1) # 状态栏 self.status_bar = self.statusBar() self.status_bar.setStyleSheet("background-color: #e0e0e0; color: #666; padding: 5px;") self.status_bar.showMessage("就绪 | 数据采集工具 v1.1 | 配置已自动保存") main_widget.setLayout(main_layout) self.setCentralWidget(main_widget) def switch_platform(self): """切换平台时的处理""" new_platform = self.platform_combo.currentData() self.current_platform = new_platform # 更新所有账号的平台 for account in self.accounts: account.switch_platform(new_platform) self.status_bar.showMessage(f"已切换到{self.platform_combo.currentText()}平台") def create_button(self, text, bg_color, hover_color, pressed_color, icon): button = QPushButton(text) button.setIcon(self.style().standardIcon(icon)) button.setStyleSheet(f""" QPushButton {{ background-color: {bg_color}; color: white; padding: 10px 20px; border-radius: 6px; font-weight: bold; font-size: 14px; }} QPushButton:hover {{ background-color: {hover_color}; }} QPushButton:pressed {{ background-color: {pressed_color}; }} """) # 存储基础颜色用于动画 button.setProperty("base_color", bg_color) return button def get_tab_name(self, index): config = self.config_manager.load_account_config(index,self.current_platform) tab_name = f"账号 {index + 1}" if config and config.get("remark"): tab_name = f"{tab_name}: {config['remark']}" return tab_name def update_tab_names(self): for i in range(self.tabs.count()): self.tabs.setTabText(i, self.get_tab_name(i)) def show_proxy_config(self): try: dialog = ProxyConfigDialog(self.config_manager, self) dialog.setWindowModality(Qt.WindowModality.ApplicationModal) if dialog.exec() == QDialog.DialogCode.Accepted: self.status_bar.showMessage("代理配置已保存") self.animate_button(self.proxy_config_btn, "#E65100") except Exception as e: QMessageBox.critical(self, "错误", f"打开代理配置时出错: {str(e)}") def animate_button(self, button, color): original_style = button.styleSheet() button.setStyleSheet(original_style.replace( f"background-color: {button.property('base_color')}", f"background-color: {color}") ) QTimer.singleShot(500, lambda: button.setStyleSheet(original_style))
最新发布
07-23
import sys import json import sqlite3 import requests from PyQt5.QtWidgets import (QApplication, QMainWindow, QSplitter, QListWidget, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit, QLabel, QMessageBox, QStatusBar) from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile from PyQt5.QtCore import QUrl, Qt, QTimer from PyQt5.QtNetwork import QNetworkCookie class AccountManager: """账号管理类,处理账号的存储和加载""" def __init__(self, db_name='bilibili_accounts.db'): self.conn = sqlite3.connect(db_name) self.cursor = self.conn.cursor() self._create_table() def _create_table(self): self.cursor.execute(''' CREATE TABLE IF NOT EXISTS accounts ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, cookies TEXT NOT NULL ) ''') self.conn.commit() def add_account(self, name, cookies): """添加新账号""" try: self.cursor.execute( "INSERT INTO accounts (name, cookies) VALUES (?, ?)", (name, json.dumps(cookies)) ) self.conn.commit() return True except sqlite3.IntegrityError: return False def get_accounts(self): """获取所有账号""" self.cursor.execute("SELECT id, name, cookies FROM accounts") return [ {'id': row[0], 'name': row[1], 'cookies': json.loads(row[2])} for row in self.cursor.fetchall() ] def delete_account(self, account_id): """删除账号""" self.cursor.execute("DELETE FROM accounts WHERE id=?", (account_id,)) self.conn.commit() def __del__(self): self.conn.close() class BilibiliBrowser(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("B站多账号浏览器") self.resize(1200, 800) # 初始化账号管理器 self.account_manager = AccountManager() # 创建UI self.init_ui() # 加载账号列表 self.load_accounts() # 设置状态栏 self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self.status_bar.showMessage("就绪", 3000) def init_ui(self): """初始化用户界面""" # 主分割布局 main_splitter = QSplitter(Qt.Horizontal) # 左侧账号管理区域 account_widget = QWidget() account_layout = QVBoxLayout() # 账号列表 account_layout.addWidget(QLabel("账号列表")) self.account_list = QListWidget() self.account_list.itemClicked.connect(self.switch_account) account_layout.addWidget(self.account_list) # 添加账号区域 add_account_layout = QHBoxLayout() self.account_name_input = QLineEdit() self.account_name_input.setPlaceholderText("输入账号名称") add_account_layout.addWidget(self.account_name_input) add_btn = QPushButton("添加账号") add_btn.clicked.connect(self.add_account) add_account_layout.addWidget(add_btn) account_layout.addLayout(add_account_layout) # 删除账号按钮 del_btn = QPushButton("删除选中账号") del_btn.clicked.connect(self.delete_account) account_layout.addWidget(del_btn) # 检测账号状态按钮 check_btn = QPushButton("检测账号状态") check_btn.clicked.connect(self.check_account_status) account_layout.addWidget(check_btn) account_widget.setLayout(account_layout) # 右侧浏览器区域 browser_widget = QWidget() browser_layout = QVBoxLayout() # 导航栏 nav_layout = QHBoxLayout() self.url_bar = QLineEdit() self.url_bar.setPlaceholderText("输入网址或搜索内容") self.url_bar.returnPressed.connect(self.navigate_to_url) nav_layout.addWidget(self.url_bar) home_btn = QPushButton("首页") home_btn.clicked.connect(self.go_home) nav_layout.addWidget(home_btn) refresh_btn = QPushButton("刷新") refresh_btn.clicked.connect(self.refresh_page) nav_layout.addWidget(refresh_btn) browser_layout.addLayout(nav_layout) # 浏览器视图 self.browser = QWebEngineView() self.browser.load(QUrl("https://www.bilibili.com")) self.browser.urlChanged.connect(self.update_url_bar) browser_layout.addWidget(self.browser) browser_widget.setLayout(browser_layout) # 添加组件到主分割器 main_splitter.addWidget(account_widget) main_splitter.addWidget(browser_widget) main_splitter.setSizes([300, 900]) self.setCentralWidget(main_splitter) def load_accounts(self): """加载所有账号到列表""" self.account_list.clear() accounts = self.account_manager.get_accounts() for account in accounts: self.account_list.addItem(f"{account['name']} (ID:{account['id']})") def add_account(self): """添加新账号""" account_name = self.account_name_input.text().strip() if not account_name: QMessageBox.warning(self, "输入错误", "请输入账号名称") return # 获取当前浏览器的Cookie cookie_store = self.browser.page().profile().cookieStore() cookies = [] # 使用闭包捕获cookies列表 def capture_cookies(cookie): print(cookie) cookies.append({ 'name': cookie.name().data().decode(), 'value': cookie.value().data().decode(), 'domain': cookie.domain(), 'path': cookie.path(), 'expirationDate': cookie.expirationDate().toSecsSinceEpoch() if cookie.expirationDate().isValid() else None }) # 连接信号捕获Cookie cookie_store.cookieAdded.connect(capture_cookies) # 设置一个定时器来等待Cookie收集完成 QTimer.singleShot(1000, lambda: self.finalize_account_creation(account_name, cookies, cookie_store)) # 提示用户登录 self.browser.load(QUrl("https://passport.bilibili.com/login")) self.status_bar.showMessage("请在浏览器中登录B站账号...", 5000) def finalize_account_creation(self, account_name, cookies, cookie_store): """完成账号创建过程""" # 断开信号连接 cookie_store.cookieAdded.disconnect() print(cookies) # 过滤B站相关Cookie bili_cookies = [c for c in cookies if 'bilibili' in c['domain']] print(bili_cookies) if not bili_cookies: QMessageBox.warning(self, "添加失败", "未检测到有效的B站Cookie") return # 保存账号 if self.account_manager.add_account(account_name, bili_cookies): self.load_accounts() self.account_name_input.clear() self.status_bar.showMessage(f"账号 '{account_name}' 添加成功", 3000) else: QMessageBox.warning(self, "添加失败", "账号名称已存在") def delete_account(self): """删除选中账号""" current_item = self.account_list.currentItem() if not current_item: return account_id = int(current_item.text().split("ID:")[1].strip(")")) self.account_manager.delete_account(account_id) self.load_accounts() self.status_bar.showMessage("账号已删除", 3000) def switch_account(self, item): """切换到选中的账号""" account_id = int(item.text().split("ID:")[1].strip(")")) accounts = self.account_manager.get_accounts() account = next((a for a in accounts if a['id'] == account_id), None) if account: self.apply_cookies(account['cookies']) self.browser.load(QUrl("https://www.bilibili.com")) self.status_bar.showMessage(f"已切换到账号: {account['name']}", 3000) def apply_cookies(self, cookies): """应用Cookie到浏览器""" profile = QWebEngineProfile.defaultProfile() cookie_store = profile.cookieStore() cookie_store.deleteAllCookies() # 清除现有Cookie for cookie_data in cookies: cookie = QNetworkCookie() cookie.setName(cookie_data['name'].encode()) cookie.setValue(cookie_data['value'].encode()) cookie.setDomain(cookie_data['domain']) cookie.setPath(cookie_data['path']) if cookie_data['expirationDate']: cookie.setExpirationDate(cookie_data['expirationDate']) cookie_store.setCookie(cookie) def check_account_status(self): """检查选中账号的状态""" current_item = self.account_list.currentItem() if not current_item: return account_id = int(current_item.text().split("ID:")[1].strip(")")) accounts = self.account_manager.get_accounts() account = next((a for a in accounts if a['id'] == account_id), None) if account: # 使用requests验证Cookie有效性 session = requests.Session() for cookie in account['cookies']: session.cookies.set( cookie['name'], cookie['value'], domain=cookie['domain'], path=cookie['path'] ) try: response = session.get("https://api.bilibili.com/x/web-interface/nav") data = response.json() if data['code'] == 0: uname = data['data']['uname'] self.status_bar.showMessage(f"账号有效: {uname}", 5000) else: self.status_bar.showMessage("账号已失效,请重新登录", 5000) except Exception as e: self.status_bar.showMessage(f"检测失败: {str(e)}", 5000) def navigate_to_url(self): """导航到输入的URL""" url = self.url_bar.text().strip() if not url.startswith(('http://', 'https://')): url = 'https://' + url self.browser.load(QUrl(url)) def update_url_bar(self, q): """更新URL地址栏""" self.url_bar.setText(q.toString()) def go_home(self): """返回B站首页""" self.browser.load(QUrl("https://www.bilibili.com")) def refresh_page(self): """刷新当前页面""" self.browser.reload() if __name__ == "__main__": app = QApplication(sys.argv) browser = BilibiliBrowser() browser.show() sys.exit(app.exec_()) 获取不到cookie请帮我检查修改并且完整返回
07-09
# -*- coding: utf-8 -*- import sys import os import cv2 import numpy as np import time import threading from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QMessageBox, QLabel, QFileDialog, QToolBar, QComboBox, QStatusBar, QGroupBox, QSlider, QDockWidget, QProgressDialog, QCheckBox, QSpinBox, QImage, QPixmap) from PyQt5.QtCore import QRect, Qt, QSettings, QThread, pyqtSignal, QTimer from CamOperation_class import CameraOperation sys.path.append("D:\\海康\\MVS\\Development\\Samples\\Python\\BasicDemo") from MvCameraControl_class import * from MvErrorDefine_const import * from CameraParams_header import * from PyUICBasicDemo import Ui_MainWindow import ctypes from ctypes import cast, POINTER from datetime import datetime import logging import platform import queue def check_network_configuration(): """ 检查网络配置并枚举相机设备 """ # 初始化设备列表 device_list = MV_CC_DEVICE_INFO_LIST() # 枚举所有GigE设备 ret = MvCamera.MV_CC_EnumDevices(MV_GIGE_DEVICE, device_list) # 修复:定义变量并初始化 discovered_cameras = 0 if ret == MV_OK: # 获取发现的相机数量 discovered_cameras = device_list.nDeviceNum else: print(f"枚举设备失败! 错误码: {hex(ret)}") print(f"发现 {discovered_cameras} 台相机设备") # 检查是否发现相机 if discovered_cameras == 0: print("未发现任何相机设备,请检查网络连接和相机电源") return False # 打印每个相机的信息 for i in range(discovered_cameras): device_info = cast(device_list.pDeviceInfo[i], POINTER(MV_CC_DEVICE_INFO)).contents if device_info.nTLayerType == MV_GIGE_DEVICE: ip_addr = device_info.SpecialInfo.stGigEInfo.nCurrentIp ip = f"{(ip_addr >> 24) & 0xFF}.{(ip_addr >> 16) & 0xFF}.{(ip_addr >> 8) & 0xFF}.{ip_addr & 0xFF}" print(f"相机 {i+1}: IP地址={ip}") return True # 初始化SDK ret = MvCamera.MV_CC_Initialize() if ret != MV_OK: print(f"SDK初始化失败! 错误码: {hex(ret)}") exit(1) # 检查网络配置 if not check_network_configuration(): print("网络配置检查失败,请解决上述问题后重试") # 反初始化SDK MvCamera.MV_CC_Finalize() exit(1) # 配置日志系统 logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("cloth_inspection_debug.log"), logging.StreamHandler() ] ) logging.info("布料印花检测系统启动 - 优化版") # ====================== 新增检测算法类 ====================== class PrintQualityDetector: def __init__(self): self.preprocessing_params = { 'contrast': 1.2, 'brightness': 10, 'gaussian_blur': (5, 5), 'threshold': 30, 'morph_kernel': np.ones((3, 3), np.uint8) } def preprocess_image(self, image): """图像预处理:增强对比度、去噪、二值化""" # 转换为灰度图 if len(image.shape) == 3: gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) else: gray = image.copy() # 增强对比度和亮度 enhanced = cv2.convertScaleAbs(gray, alpha=self.preprocessing_params['contrast'], beta=self.preprocessing_params['brightness']) # 高斯模糊去噪 blurred = cv2.GaussianBlur(enhanced, self.preprocessing_params['gaussian_blur'], 0) return blurred def detect_defects(self, sample_image, test_image, threshold=0.05): """ 使用模板匹配和形态学操作检测缺陷 :param sample_image: 标准样本图像 (灰度图) :param test_image: 测试图像 (灰度图) :param threshold: 缺陷面积阈值 :return: (是否合格, 缺陷面积占比, 标记图像) """ # 确保图像大小一致 if sample_image.shape != test_image.shape: test_image = cv2.resize(test_image, (sample_image.shape[1], sample_image.shape[0])) # 计算绝对差异 diff = cv2.absdiff(sample_image, test_image) # 二值化差异图 _, thresh = cv2.threshold(diff, self.preprocessing_params['threshold'], 255, cv2.THRESH_BINARY) # 形态学操作去除小噪点 cleaned = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, self.preprocessing_params['morph_kernel']) # 查找缺陷轮廓 contours, _ = cv2.findContours(cleaned, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 计算缺陷总面积 defect_area = 0 marked_image = cv2.cvtColor(test_image, cv2.COLOR_GRAY2BGR) for contour in contours: area = cv2.contourArea(contour) defect_area += area # 绘制缺陷轮廓 cv2.drawContours(marked_image, [contour], -1, (0, 0, 255), 2) # 计算缺陷面积占比 total_pixels = sample_image.size defect_ratio = defect_area / total_pixels # 判断是否合格 is_qualified = defect_ratio <= threshold return is_qualified, defect_ratio, marked_image # ====================== 新增帧缓冲区类 ====================== class FrameBuffer: def __init__(self, max_size=10): self.buffer = queue.Queue(maxsize=max_size) self.lock = threading.Lock() def put_frame(self, frame): """添加新帧到缓冲区""" with self.lock: if self.buffer.full(): self.buffer.get() # 移除最旧的帧 self.buffer.put(frame.copy()) def get_latest_frame(self): """获取最新的帧""" with self.lock: if not self.buffer.empty(): return self.buffer.queue[-1].copy() return None def get_frame_at_time(self, timestamp, tolerance=0.01): """ 获取最接近指定时间戳的帧 :param timestamp: 目标时间戳 :param tolerance: 时间容差(秒) :return: (帧, 时间差) """ best_frame = None min_diff = float('inf') with self.lock: for frame_data in list(self.buffer.queue): frame, frame_time = frame_data time_diff = abs(frame_time - timestamp) if time_diff < min_diff: min_diff = time_diff best_frame = frame if min_diff <= tolerance: return best_frame.copy(), min_diff return None, min_diff # ====================== 全局变量 ====================== current_sample_path = "" # 当前使用的样本路径 detection_history = [] # 检测历史记录 frame_buffer = FrameBuffer(max_size=30) # 帧缓冲区 detector = PrintQualityDetector() # 检测器实例 sensor_trigger_time = None # 传感器触发时间 sensor_trigger_enabled = False # 是否启用传感器触发 isGrabbing = False # 相机取流状态 obj_cam_operation = None # 相机操作对象 # ====================== 新增线程类 ====================== class SensorTriggerThread(QThread): trigger_detected = pyqtSignal() def __init__(self): super().__init__() self.running = True def run(self): """模拟传感器触发线程""" while self.running: # 在实际应用中,这里会读取真实的传感器输入 # 此处模拟每5秒触发一次 time.sleep(5) if sensor_trigger_enabled: self.trigger_detected.emit() def stop(self): self.running = False class FrameMonitorThread(QThread): frame_status = pyqtSignal(str) new_frame = pyqtSignal(np.ndarray, float) # 帧数据和时间戳 def __init__(self, cam_operation): super().__init__() self.cam_operation = cam_operation self.running = True def run(self): while self.running: if self.cam_operation: status = self.cam_operation.get_frame_status() frame_text = "有帧" if status.get('current_frame', False) else "无帧" self.frame_status.emit(f"帧状态: {frame_text}") # 获取新帧并添加到缓冲区 frame = self.cam_operation.get_current_frame() if frame is not None: timestamp = time.time() frame_buffer.put_frame((frame, timestamp)) self.new_frame.emit(frame, timestamp) QThread.msleep(50) # 更频繁地检查帧状态 def stop(self): self.running = False # ====================== 优化后的检测函数 ====================== def check_print_quality(sample_image_path, test_image, threshold=0.05): """ 优化版布料印花质量检测 :param sample_image_path: 合格样本图像路径 :param test_image: 内存中的测试图像 (numpy数组) :param threshold: 差异阈值 :return: 是否合格,差异值,标记图像 """ # 读取样本图像 try: sample_img_data = np.fromfile(sample_image_path, dtype=np.uint8) sample_image = cv2.imdecode(sample_img_data, cv2.IMREAD_GRAYSCALE) if sample_image is None: logging.error(f"无法解码样本图像: {sample_image_path}") return None, None, None except Exception as e: logging.exception(f"样本图像读取异常: {str(e)}") return None, None, None # 预处理测试图像 processed_test = detector.preprocess_image(test_image) # 确保测试图像是灰度图 if len(processed_test.shape) == 3: # 如果是彩色图像 processed_test = cv2.c极Color(processed_test, cv2.COLOR_BGR2GRAY) # 使用优化算法检测缺陷 is_qualified, defect_ratio, marked_image = detector.detect_defects( sample_image, processed_test, threshold=threshold ) return is_qualified, defect_ratio, marked_image # ====================== 传感器触发处理 ====================== def handle_sensor_trigger(): """处理传感器触发信号""" global sensor_trigger_time logging.info("传感器触发信号接收") # 记录触发时间 sensor_trigger_time = time.time() # 更新UI状态 ui.lblSensorStatus.setText("传感器: 已触发 (等待拍摄...)") ui.lblSensorStatus.setStyleSheet("color: orange; font-weight: bold;") # 设置定时器在0.05秒后执行拍摄和检测 QTimer.singleShot(50, capture_after_sensor_trigger) def capture_after_sensor_trigger(): """传感器触发后0.05秒执行拍摄和检测""" global sensor_trigger_time if sensor_trigger_time is None: logging.warning("传感器触发时间未记录") return # 计算目标时间戳 (触发时间 + 0.05秒) target_time = sensor_trigger_time + 0.05 # 从缓冲区获取最接近目标时间的帧 frame, time_diff = frame_buffer.get_frame_at_time(target_time, tolerance=0.01) if frame is None: logging.warning(f"未找到接近目标时间({target_time:.3f})的帧,最小时间差: {time_diff:.3f}秒") ui.lblSensorStatus.setText("传感器: 触发失败 (无匹配帧)") ui.lblSensorStatus.setStyleSheet("color: red;") return logging.info(f"成功捕获传感器触发后帧,时间差: {time_diff:.3f}秒") # 更新UI状态 ui.lblSensorStatus.setText("传感器: 检测中...") ui.lblSensorStatus.setStyleSheet("color: blue; font-weight: bold;") # 执行检测 perform_detection(frame) # ====================== 优化后的检测流程 ====================== def perform_detection(test_image): """执行检测逻辑""" global current_sample_path, detection_history # 检查样本路径 if not current_sample_path or not os.path.exists(current_sample_path): logging.warning(f"无效样本路径: {current_sample_path}") QMessageBox.warning(mainWindow, "错误", "请先设置有效的标准样本图像!", QMessageBox.Ok) ui.lblSensorStatus.setText("传感器: 就绪") ui.lblSensorStatus.setStyleSheet("color: green;") return # 使用进度对话框防止UI阻塞 progress = QProgressDialog("正在检测...", "取消", 0, 100, mainWindow) progress.setWindowModality(Qt.WindowModal) progress.setValue(10) try: # 获取差异度阈值 diff_threshold = ui.sliderDiffThreshold.value() / 100.0 logging.info(f"使用差异度阈值: {diff_threshold}") progress.setValue(30) # 执行检测 is_qualified, diff_ratio, marked_image = check_print_quality( current_sample_path, test_image, threshold=diff_threshold ) progress.setValue(70) # 检查返回结果是否有效 if is_qualified is None: logging.error("检测函数返回无效结果") QMessageBox.critical(mainWindow, "检测错误", "检测失败,请检查日志", QMessageBox.Ok) ui.lblSensorStatus.setText("传感器: 检测失败") ui.lblSensorStatus.setStyleSheet("color: red;") return logging.info(f"检测结果: 合格={is_qualified}, 差异={diff_ratio}") progress.setValue(90) # 更新UI update_diff_display(diff_ratio, is_qualified) result_text = f"印花是否合格: {'合格' if is_qualified else '不合格'}\n缺陷占比: {diff_ratio*100:.2f}%\n阈值: {diff_threshold*100:.2f}%" QMessageBox.information(mainWindow, "检测结果", result_text, QMessageBox.Ok) if marked_image is not None: cv2.imshow("缺陷标记结果", marked_image) cv2.waitKey(0) cv2.destroyAllWindows() else: logging.warning("标记图像为空") # 记录检测结果 detection_result = { 'timestamp': datetime.now(), 'qualified': is_qualified, 'diff_ratio': diff_ratio, 'threshold': diff_threshold, 'trigger_type': '传感器' if sensor_trigger_enabled else '手动' } detection_history.append(detection_result) update_history_display() progress.setValue(100) # 更新传感器状态 ui.lblSensorStatus.setText("传感器: 就绪") ui.lblSensorStatus.setStyleSheet("color: green;") except Exception as e: logging.exception("印花检测失败") QMessageBox.critical(mainWindow, "检测错误", f"检测过程中发生错误: {str(e)}", QMessageBox.Ok) ui.lblSensorStatus.setText("传感器: 检测错误") ui.lblSensorStatus.setStyleSheet("color: red;") finally: progress.close() def check_print(): """手动触发检测""" global obj_cam_operation, isGrabbing # 声明全局变量 logging.info("手动检测印花质量") # 检查相机状态 if not isGrabbing: logging.warning("相机未取流") QMessageBox.warning(mainWindow, "错误", "请先开始取流并捕获图像!", QMessageBox.Ok) return # 获取当前帧 frame = obj_cam_operation.get_current_frame() if frame is None: logging.warning("获取当前帧失败") QMessageBox.warning(mainWindow, "错误", "无法获取当前帧图像!", QMessageBox.Ok) return # 执行检测 perform_detection(frame) # ====================== UI更新函数 ====================== def update_diff_display(diff_ratio, is_qualified): """更新差异度显示控件""" # 更新当前差异度显示 ui.lblCurrentDiff.setText(f"当前差异度: {diff_ratio*100:.2f}%") # 根据合格状态设置颜色 if is_qualified: ui.lblDiffStatus.setText("状态: 合格") ui.lblDiffStatus.setStyleSheet("color: green; font-size: 12px;") else: ui.lblDiffStatus.setText("状态: 不合格") ui.lblDiffStatus.setStyleSheet("color: red; font-size: 12px;") def update_diff_threshold(value): """当滑块值改变时更新阈值显示""" ui.lblDiffValue.setText(f"{value}%") def update_sample_display(): """更新样本路径显示""" global current_sample_path if current_sample_path: ui.lblSamplePath.setText(f"当前样本: {os.path.basename(current_sample_path)}") ui.lblSamplePath.setToolTip(current_sample_path) ui.bnPreviewSample.setEnabled(True) else: ui.lblSamplePath.setText("当前样本: 未设置样本") ui.bnPreviewSample.setEnabled(False) def update_history_display(): """更新历史记录显示""" global detection_history ui.cbHistory.clear() for i, result in enumerate(detection_history[-10:]): # 显示最近10条记录 timestamp = result['timestamp'].strftime("%H:%M:%S") status = "合格" if result['qualified'] else "不合格" ratio = f"{result['diff_ratio']*100:.2f}%" trigger = result['trigger_type'] ui.cbHistory.addItem(f"[{timestamp}] {trigger} - {status} - 差异: {ratio}") # ====================== 样本管理函数 ====================== def save_sample_image(): """保存当前帧为标准样本图像""" global current_sample_path, obj_cam_operation, isGrabbing # 声明全局变量 logging.info("保存标准样本图像") # 检查相机状态 if not isGrabbing: logging.warning("相机未取流") QMessageBox.warning(mainWindow, "错误", "请极开始取流并捕获图像!", QMessageBox.Ok) return # 获取当前帧 frame = obj_cam_operation.get_current_frame() if frame is None: logging.warning("获取当前帧失败") QMessageBox.warning(mainWindow, "错误", "无法获取当前帧图像!", QMessageBox.Ok) return # 弹出文件保存对话框 file_path, _ = QFileDialog.getSaveFileName( mainWindow, "保存标准样本图像", "", "图像文件 (*.png *.jpg *.bmp);;所有文件 (*)" ) if not file_path: logging.info("用户取消保存操作") return try: # 保存图像 cv2.imwrite(file_path, frame) current_sample_path = file_path logging.info(f"标准样本已保存至: {file_path}") # 更新UI显示 update_sample_display() QMessageBox.information(mainWindow, "保存成功", "标准样本图像已保存!", QMessageBox.Ok) except Exception as e: logging.error(f"保存样本图像失败: {str(e)}") QMessageBox.critical(mainWindow, "保存失败", f"保存图像时发生错误: {str(e)}", QMessageBox.Ok) def preview_sample(): """预览当前标准样本图像""" global current_sample_path logging.info("预览标准样本图像") if not current_sample_path or not os.path.exists(current_sample_path): logging.warning(f"无效样本路径: {current_sample_path}") QMessageBox.warning(mainWindow, "错误", "请先设置有效的标准样本图像!", QMessageBox.Ok) return try: # 读取样本图像 sample_img = cv2.imread(current_sample_path) if sample_img is None: logging.error(f"无法读取样本图像: {current_sample_path}") QMessageBox.critical(mainWindow, "预览失败", "无法读取样本图像文件!", QMessageBox.Ok) return # 创建预览窗口 preview_window = QMainWindow() preview_window.setWindowTitle(f"标准样本预览 - {os.path.basename(current_sample_path)}") preview_window.resize(800, 600) # 创建显示标签 image_label = QLabel() image_label.setAlignment(Qt.AlignCenter) # 转换为QPixmap并显示 height, width, channel = sample_img.shape bytes_per_line = 3 * width q_img = QImage(sample_img.data, width, height, bytes_per_line, QImage.Format_RGB888).rgbSwapped() pixmap = QPixmap.fromImage(q_img) # 缩放以适应窗口 scaled_pixmap = pixmap.scaled( preview_window.width() - 50, preview_window.height() - 50, Qt.KeepAspectRatio, Qt.SmoothTransformation ) image_label.setPixmap(scaled_pixmap) # 设置布局 central_widget = QWidget() layout = QVBoxLayout(central_widget) layout.addWidget(image_label) preview_window.setCentralWidget(central_widget) # 显示窗口 preview_window.show() except Exception as e: logging.error(f"预览样本图像失败: {str(e)}") QMessageBox.critical(mainWindow, "预览失败", f"预览图像时发生错误: {str(e)}", QMessageBox.Ok) # ====================== 传感器控制函数 ====================== def toggle_sensor_trigger(state): """切换传感器触发状态""" global sensor_trigger_enabled sensor_trigger_enabled = state if sensor_trigger_enabled: ui.lblSensorStatus.setText("传感器: 就绪") ui.lblSensorStatus.setStyleSheet("color: green;") logging.info("传感器触发已启用") else: ui.lblSensorStatus.setText("传感器: 禁用") ui.lblSensorStatus.setStyleSheet("color: gray;") logging.info("传感器触发已禁用") # ... (其余相机操作函数保持不变) ... if __name__ == "__main__": # 初始化UI app = QApplication(sys.argv) mainWindow = QMainWindow() ui = Ui_MainWindow() ui.setupUi(mainWindow) # 扩大主窗口尺寸 mainWindow.resize(1200, 800) # ====================== 新增传感器控制UI ====================== # 创建工具栏 toolbar = mainWindow.addToolBar("检测工具") # 添加检测按钮 ui.bnCheckPrint = QPushButton("手动检测") toolbar.addWidget(ui.bnCheckPrint) # 添加保存样本按钮 ui.bnSaveSample = QPushButton("保存标准样本") toolbar.addWidget(ui.bnSaveSample) # 添加预览样本按钮 ui.bnPreviewSample = QPushButton("预览样本") toolbar.addWidget(ui.bnPreviewSample) # 添加传感器控制 sensor_group = QWidget() sensor_layout = QHBoxLayout(sensor_group) sensor_layout.setContentsMargins(0, 0, 0, 0) ui.chkSensorEnable = QCheckBox("启用传感器触发") ui.chkSensorEnable.setChecked(False) ui.chkSensorEnable.stateChanged.connect(toggle_sensor_trigger) ui.lblSensorStatus = QLabel("传感器: 禁用") ui.lblSensorStatus.setStyleSheet("color: gray;") ui.spinDelay = QSpinBox() ui.spinDelay.setRange(1, 100) # 1-100毫秒 ui.spinDelay.setValue(50) # 默认50毫秒 ui.spinDelay.setSuffix(" ms") sensor_layout.addWidget(ui.chkSensorEnable) sensor_layout.addWidget(ui.lblSensorStatus) sensor_layout.addWidget(QLabel("延迟:")) sensor_layout.addWidget(ui.spinDelay) toolbar.addWidget(sensor_group) # 添加历史记录下拉框 ui.cbHistory = QComboBox() ui.cbHistory.setMinimumWidth(300) toolbar.addWidget(QLabel("历史记录:")) toolbar.addWidget(ui.cbHistory) # 添加当前样本显示标签 ui.lblSamplePath = QLabel("当前样本: 未设置样本") status_bar = mainWindow.statusBar() status_bar.addPermanentWidget(ui.lblSamplePath) # ====================== 优化后的差异度调整UI ====================== # 创建右侧面板 right_panel = QWidget() right_layout = QVBoxLayout(right_panel) right_layout.setContentsMargins(10, 10, 10, 10) # 差异度调整组 diff_group = QGroupBox("检测参数设置") diff_layout = QVBoxLayout(diff_group) ui.lblDiffThreshold = QLabel("缺陷阈值 (0-100%):") ui.sliderDiffThreshold = QSlider(Qt.Horizontal) ui.sliderDiffThreshold.setRange(0, 100) ui.sliderDiffThreshold.setValue(5) ui.lblDiffValue = QLabel("5%") # 当前检测结果显示 ui.lblCurrentDiff = QLabel("当前差异度: -") ui.lblCurrentDiff.setStyleSheet("font-size: 14px; font-weight: bold;") ui.lblDiffStatus = QLabel("状态: 未检测") ui.lblDiffStatus.setStyleSheet("font-size: 12px;") # 算法参数调整 param_group = QGroupBox("算法参数") param_layout = QVBoxLayout(param_group) ui.lblContrast = QLabel("对比度增强:") ui.sliderContrast = QSlider(Qt.Horizontal) ui.sliderContrast.setRange(5, 30) # 0.5-3.0 ui.sliderContrast.setValue(12) # 默认1.2 ui.lblContrastValue = QLabel("1.2") ui.lblThreshold = QLabel("二值化阈值:") ui.sliderThreshold = QSlider(Qt.Horizontal) ui.sliderThreshold.setRange(10, 100) ui.sliderThreshold.setValue(30) ui.lblThresholdValue = QLabel("30") param_layout.addWidget(ui.lblContrast) param_layout.addWidget(ui.sliderContrast) param_layout.addWidget(ui.lblContrastValue) param_layout.addWidget(ui.lblThreshold) param_layout.addWidget(ui.sliderThreshold) param_layout.addWidget(ui.lblThresholdValue) # 布局控件 diff_layout.addWidget(ui.lblDiffThreshold) diff_layout.addWidget(ui.sliderDiffThreshold) diff_layout.addWidget(ui.lblDiffValue) diff_layout.addWidget(ui.lblCurrentDiff) diff_layout.addWidget(ui.lblDiffStatus) diff_layout.addWidget(param_group) right_layout.addWidget(diff_group) right_layout.addStretch(1) # 创建停靠窗口 dock = QDockWidget("检测控制面板", mainWindow) dock.setWidget(right_panel) dock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) mainWindow.addDockWidget(Qt.RightDockWidgetArea, dock) # ====================== 信号连接 ====================== # 连接滑块信号 ui.sliderDiffThreshold.valueChanged.connect(update_diff_threshold) ui.sliderContrast.valueChanged.connect(lambda v: ui.lblContrastValue.setText(f"{v/10:.1f}")) ui.sliderThreshold.valueChanged.connect(lambda v: ui.lblThresholdValue.setText(str(v))) # 更新算法参数 def update_detector_params(): detector.preprocessing_params['contrast'] = ui.sliderContrast.value() / 10.0 detector.preprocessing_params['threshold'] = ui.sliderThreshold.value() logging.info(f"更新检测参数: 对比度={detector.preprocessing_params['contrast']}, 阈值={detector.preprocessing_params['threshold']}") ui.sliderContrast.valueChanged.connect(update_detector_params) ui.sliderThreshold.valueChanged.connect(update_detector_params) # 绑定按钮事件 ui.bnCheckPrint.clicked.connect(check_print) ui.bnSaveSample.clicked.connect(save_sample_image) ui.bnPreviewSample.clicked.connect(preview_sample) # ... (其余相机操作信号连接保持不变) ... # ====================== 启动传感器线程 ====================== sensor_thread = SensorTriggerThread() sensor_thread.trigger_detected.connect(handle_sensor_trigger) sensor_thread.start() # 显示主窗口 mainWindow.show() # 执行应用 app.exec_() # 关闭线程和设备 sensor_thread.stop() sensor_thread.wait() close_device() # 反初始化SDK MvCamera.MV_CC_Finalize() sys.exit() 这个程序还是Traceback (most recent call last): File "d:\海康\MVS\Development\Samples\Python\MvImport\1.py", line 22, in <module> check_network_configuration() File "d:\海康\MVS\Development\Samples\Python\MvImport\1.py", line 12, in check_network_configuration if discovered_cameras: NameError: name 'discovered_cameras' is not defined出现这样的问题
07-09
在使用PyQt6时,有时可能会遇到应用冻结或性能下降的问题,这可能是由于内存泄漏、过度绘制、长时间运行的任务或其他耗时操作引起的。以下是几个防止PyQt6卡死的常见策略: 1. **垃圾回收管理**: - 使用`del`语句手动删除不再使用的对象,或者让它们自动回收。避免循环引用,特别是在处理复杂图形用户界面时。 2. **避免长时间阻塞线程**: - 将长时间运行的操作放到单独的线程或子进程中执行,以防主线程被阻塞,影响UI响应。 3. **使用QThread和槽函数**: - 使用`QThread`来分离计算密集型任务,通过槽函数(slot functions)接收结果并更新UI。 4. **控制刷新频率**: - 避免在循环中频繁地更新GUI,可以设置定时器并在适当的时间更新视图。 5. **内存限制和优化**: - 使用`PyQt6.QtCore.QTimer.singleShot`代替`sleep()`,因为后者会暂停整个应用程序。 - 调整内存缓存大小和布局策略,比如使用`QCachePolicy`。 6. **检查异常和日志**: - 优雅地处理异常,并使用`logging`记录程序运行过程,以便查找潜在问题。 7. **适时关闭资源**: - 关闭无用的窗口、文件和数据库连接,及时释放资源。 8. **使用可视化的性能分析工具**: - PyQt6提供了一些工具,如`QTest`和`PyQt6.QtWidgets.QApplication.testMode`,用于诊断性能瓶颈。 9. **代码审查和重构**: - 定期审查代码,看是否有不必要的复杂度和冗余操作。 记得每个项目都有其特性和需求,以上只是一般性的建议。根据具体情况调整策略会有更好的效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值