The artist's coding log

本文详细介绍了C语言中的指针与函数概念,包括如何使用指针创建新节点、理解不同类型的指针函数和函数指针,以及通过具体实例展示它们的应用。
// Node 结构体
typedef struct node{
  struct node *next; // 结构体 node 指针
  Buch i; // Buch 类型
}Node;
// 新建一个结点并返回
Node * New_Node(void)
{
  Node * newNode; // 声明一个 Node 类型的指针
  newNode = (Node*)malloc(sizeof(Node)); // 分配内存
  if( newNode != NULL){ // 内存分配成功
    newNode->next = NULL; // 设置下一个结点为空,不存在
    memset(&newNode->i,0,sizeof(Buch)); // 初始化内存为 0
  }else
    printf("Kein Speicherplatz mehr\n"); // 内存分配失败
  
  return newNode; // 返回新建的结点
}

1.int * fun() :fun是一个函数,返回值是int *类型
2. (int *)fun():一般来说是将fun的返回值强制转换为int *
3.int (* fun)():fun是一个函数指针,它指向一个没有形式参数的函数,这函数返回一个int值.

函数指针:int (*f)(intint);

  1. /* 
  2.  * 求最大值 
  3.  * 返回值是int类型,返回两个整数中较大的一个 
  4.  */  
  5. int max(int a, int b) {  
  6.     return a > b ? a : b;  
  7. }  
  8.   
  9. /* 
  10.  * 求最小值 
  11.  * 返回值是int类型,返回两个整数中较小的一个 
  12.  */  
  13. int min(int a, int b) {  
  14.     return a < b ? a : b;  
  15. }  
  16.   
  17. int (*f)(intint); // 声明函数指针,指向返回值类型为int,有两个参数类型都是int的函数  
  18.   
  19. int _tmain(int argc, _TCHAR* argv[])  
  20. {  
  21.     printf("------------------------------ Start\n");  
  22.   
  23.     f = max; // 函数指针f指向求最大值的函数max  
  24.     int c = (*f)(1, 2);  
  25.   
  26.     printf("The max value is %d \n", c);  
  27.   
  28.     f = min; // 函数指针f指向求最小值的函数min  
  29.     c = (*f)(1, 2);  
  30.   
  31.     printf("The min value is %d \n", c);  
  32.   
  33.     printf("------------------------------ End\n");  
  34.     getchar();  
  35.     return 0;  
  36. }  


指针函数:int *f(int a, int b);  

  1. int *f(int a, int b); // 声明指针函数  
  2.   
  3. int _tmain(int argc, _TCHAR* argv[])  
  4. {  
  5.     printf("------------------------------ Start\n");  
  6.   
  7.     int *p1 = NULL;  
  8.     printf("The memeory address of p1 = 0x%x \n", p1);  
  9.   
  10.     p1 = f(1, 2);  
  11.   
  12.     printf("The memeory address of p1 = 0x%x \n", p1);  
  13.     printf("*p1 = %d \n", *p1);  
  14.   
  15.     printf("------------------------------ End\n");  
  16.     getchar();  
  17.     return 0;  
  18. }  
  19.   
  20. /* 
  21.  * 指针函数的定义 
  22.  * 返回值是指针类型int * 
  23.  */  
  24. int *f(int a, int b) {  
  25.     int *p = (int *)malloc(sizeof(int));  
  26.     printf("The memeory address of p = 0x%x \n", p);  
  27.     memset(p, 0, sizeof(int));  
  28.     *p = a + b;  
  29.     printf("*p = %d \n", *p);  
  30.   
  31.     return p;  
  32. }  


# -- coding: utf-8 -- import sys import random import time import re import requests from bs4 import BeautifulSoup from openpyxl import Workbook from PyQt6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QProgressBar, QTableWidget, QTableWidgetItem, QTextEdit, QFileDialog, QHeaderView, QTabWidget, QMessageBox, QComboBox, QScrollArea ) from PyQt6.QtCore import Qt, QThread, pyqtSignal from PyQt6.QtGui import QFont, QColor import matplotlib matplotlib.use('Agg') # 非GUI后端 import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS'] plt.rcParams['axes.unicode_minus'] = False import numpy as np from wordcloud import WordCloud import jieba jieba.initialize() from collections import Counter import os import platform # ==================== 字体路径检测 ==================== def get_font_path(): system = platform.system() if system == "Windows": return r"C:\Windows\Fonts\simhei.ttf" elif system == "Darwin": # macOS return "/System/Library/Fonts/PingFang.ttc" else: return "/usr/share/fonts/truetype/wqy/wenquanyi-microhei.ttc" FONT_PATH = get_font_path() if not os.path.exists(FONT_PATH): print(f"⚠️ 字体未找到: {FONT_PATH}") else: print(f"✅ 使用字体: {FONT_PATH}") # ==================== 配色 ==================== C = { "bg": QColor(255, 255, 255), "primary": QColor(255, 179, 186), "secondary": QColor(186, 220, 255), "text": QColor(51, 51, 51), } def rgb(c): return f"#{c.red():02x}{c.green():02x}{c.blue():02x}" # ==================== 按钮样式 ==================== def create_button(text): btn = QPushButton(text) btn.setStyleSheet(f""" QPushButton {{ background: qlineargradient(x1:0,y1:0,x2:1,y2:0, stop:0 {rgb(C['primary'])}, stop:1 {rgb(C['secondary'])}); color: {rgb(C['text'])}; font-size: 14px; font-weight: bold; border: none; border-radius: 8px; padding: 8px 16px; }} QPushButton:hover {{ background: {rgb(C['secondary'])}; }} QPushButton:disabled {{ background: #eeeeee; color: #999999; }} """) return btn # ==================== 爬虫线程 ==================== class CrawlThread(QThread): progress_signal = pyqtSignal(int) log_signal = pyqtSignal(str) data_signal = pyqtSignal(list) finish_signal = pyqtSignal() def __init__(self): super().__init__() self.is_running = True def run(self): movies = [] headers_list = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0)" ] for page in range(10): if not self.is_running: break try: time.sleep(random.uniform(0.5, 1.0)) url = "https://movie.douban.com/top250" params = {"start": page * 25} headers = {"User-Agent": random.choice(headers_list)} resp = requests.get(url, headers=headers, params=params, timeout=10) resp.encoding = 'utf-8' if resp.status_code != 200: self.log_signal.emit(f"第{page+1}页失败: {resp.status_code}") continue soup = BeautifulSoup(resp.text, 'html.parser') items = soup.find_all('div', class_='item') for item in items: try: title_tag = item.find('span', class_='title') rating_tag = item.find('span', class_='rating_num') info_p = item.find('div', class_='bd').find('p').get_text('\n', strip=True) quote_tag = item.find('span', class_='inq') name = title_tag.text.strip() if title_tag else "未知电影" rating = float(rating_tag.text.strip()) if rating_tag else 0.0 year_match = re.search(r'(19\d{2}|20[012]\d)', info_p) lines = [line.strip() for line in info_p.split('\n') if line.strip()] country_line = "" for line in reversed(lines): if '/' in line and not re.search(r'\d{4}\s*$', line): country_line = line break parts = [p.strip() for p in country_line.split('/')] country = "未知" for p in parts: p = re.sub(r'^\d{4}', '', p).strip() if len(p) > 1 and not p.isdigit() and re.match(r'^[a-zA-Z\u4e00-\u9fa5]', p): country = p break director_actor = info_p.split('\n')[0].strip() summary = quote_tag.text.strip() if quote_tag else "" movie = { "name": name, "rating": rating, "year": int(year_match.group(1)) if year_match else 0, "country": country, "director_actor": director_actor, "summary": summary, "full_text": f"{name} {summary}" } movies.append(movie) except Exception as e: continue current_count = len(movies) self.progress_signal.emit(min(int(current_count / 250 * 100), 100)) self.log_signal.emit(f"第{page+1}页完成 → 抓取 {current_count}/250") except Exception as e: self.log_signal.emit(f"网络错误: {str(e)[:30]}") # 补全数据至250条 while len(movies) < 250: movies.append({ "name": "数据缺失", "rating": 0.0, "year": 0, "country": "未知", "director_actor": "", "summary": "", "full_text": "" }) # ✅ 关键修复:按评分降序 + 年份升序 排列(确保高分靠前,同分按年份) movies.sort(key=lambda x: (-x["rating"], x["year"] if x["year"] > 0 else 9999)) self.data_signal.emit(movies) self.finish_signal.emit() # ==================== 可视化图表生成器(增强版,多图+防重叠)==================== from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas def generate_rating_line_chart(movies): """折线图:8.0~10.0 每0.1一格,趋势清晰""" ratings = [m["rating"] for m in movies if 8.0 <= m["rating"] <= 10.0] bins = np.arange(8.0, 10.1, 0.1) hist, _ = np.histogram(ratings, bins=bins) fig, ax = plt.subplots(figsize=(10, 4), dpi=100) ax.plot(bins[:-1], hist, marker='o', linestyle='-', color=C["primary"].name(), linewidth=2, markersize=4) ax.set_title("📈 评分分布趋势图(8.0~10.0,步长0.1)", fontsize=14, fontweight='bold', pad=10) ax.set_xlabel("评分", fontsize=12) ax.set_ylabel("数量", fontsize=12) ax.grid(True, alpha=0.3) ax.set_xticks(bins[::2]) plt.setp(ax.get_xticklabels(), rotation=0) plt.tight_layout(pad=1.0) return FigureCanvas(fig) def generate_wordcloud(movies): """词云图:关键词提取""" texts = [m["full_text"] for m in movies if m["full_text"].strip()] text = " ".join(texts) words = jieba.lcut(text) stopwords = {"电影", "影片", "故事", "这部", "一个", "没有", "我们", "自己", "导演", "主演", "一部"} words = [w for w in words if len(w) > 1 and w not in stopwords and re.match(r'^[\u4e00-\u9fa5a-zA-Z]+$', w)] freq = Counter(words).most_common(100) if not freq: freq = [("经典", 10)] wc = WordCloud( font_path=FONT_PATH, width=800, height=400, background_color="white", max_words=100 ).generate_from_frequencies(dict(freq)) fig, ax = plt.subplots(figsize=(10, 4), dpi=100) ax.imshow(wc, interpolation='bilinear') ax.axis("off") ax.set_title("☁️ 高频词汇词云", fontsize=14, pad=10) plt.tight_layout(pad=1.0) return FigureCanvas(fig) def generate_year_histogram(movies): """新增柱状图:年份分布""" years = [m["year"] for m in movies if m["year"] > 0] fig, ax = plt.subplots(figsize=(10, 4), dpi=100) ax.hist(years, bins=range(1950, 2030, 5), color=C["secondary"].name(), edgecolor='black', alpha=0.7) ax.set_title("📅 电影年份分布直方图(每5年一档)", fontsize=14, fontweight='bold', pad=10) ax.set_xlabel("年份", fontsize=12) ax.set_ylabel("数量", fontsize=12) ax.grid(True, axis='y', alpha=0.3) plt.tight_layout(pad=1.0) return FigureCanvas(fig) def generate_country_pie_chart(movies): """新增饼图:国家/地区占比""" countries = [m["country"] for m in movies if m["country"] != "未知"] counter = Counter(countries).most_common(8) others = sum(count for _, count in Counter(countries).items()) - sum(count for _, count in counter) labels = [c[0] for c in counter] + (["其他"] if others > 0 else []) sizes = [c[1] for c in counter] + ([others] if others > 0 else []) fig, ax = plt.subplots(figsize=(10, 4), dpi=100) ax.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90, colors=plt.cm.Pastel1.colors) ax.set_title("🌍 主要出品国家/地区占比", fontsize=14, pad=10) plt.tight_layout(pad=1.0) return FigureCanvas(fig) def generate_rating_boxplot(movies): """新增箱型图:评分分布统计""" ratings = [m["rating"] for m in movies if m["rating"] > 0] fig, ax = plt.subplots(figsize=(10, 4), dpi=100) ax.boxplot(ratings, vert=False, patch_artist=True, boxprops=dict(facecolor=C["primary"].name()), medianprops=dict(color="red")) ax.set_title("📦 评分箱型图(中位数、异常值等)", fontsize=14, fontweight='bold', pad=10) ax.set_xlabel("评分") plt.tight_layout(pad=1.0) return FigureCanvas(fig) # ==================== 主窗口 ==================== class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("豆瓣Top250 - 评分趋势分析") self.resize(1000, 700) self.movies_data = [] self.filtered_data = [] self.crawl_thread = None self.init_ui() def init_ui(self): container = QWidget() self.setCentralWidget(container) layout = QVBoxLayout(container) title = QLabel("📊 豆瓣Top250 评分趋势分析") title.setAlignment(Qt.AlignmentFlag.AlignCenter) title.setStyleSheet(f"color: {rgb(C['primary'])}; font-size: 24px; font-weight: bold;") layout.addWidget(title) # 控制按钮 ctrl_layout = QHBoxLayout() self.start_btn = create_button("开始爬取") self.stop_btn = create_button("停止") self.export_btn = create_button("导出Excel") self.filter_btn = create_button("应用筛选") self.reset_btn = create_button("重置") self.stop_btn.setEnabled(False) self.export_btn.setEnabled(False) self.filter_btn.setEnabled(False) self.reset_btn.setEnabled(False) ctrl_layout.addWidget(self.start_btn) ctrl_layout.addWidget(self.stop_btn) ctrl_layout.addWidget(self.export_btn) ctrl_layout.addWidget(self.filter_btn) ctrl_layout.addWidget(self.reset_btn) layout.addLayout(ctrl_layout) self.progress_bar = QProgressBar() layout.addWidget(self.progress_bar) self.log_text = QTextEdit() self.log_text.setMaximumHeight(80) self.log_text.setReadOnly(True) layout.addWidget(self.log_text) # 筛选条件 filter_layout = QHBoxLayout() filter_layout.addWidget(QLabel("最低评分:")) self.min_rate = QComboBox() self.min_rate.addItems([f"{x:.1f}" for x in np.arange(8.0, 10.0, 0.1)]) self.min_rate.setCurrentText("8.0") filter_layout.addWidget(self.min_rate) filter_layout.addWidget(QLabel("最高评分:")) self.max_rate = QComboBox() self.max_rate.addItems([f"{x:.1f}" for x in np.arange(8.1, 10.1, 0.1)]) self.max_rate.setCurrentText("10.0") filter_layout.addWidget(self.max_rate) filter_layout.addWidget(QLabel("起始年份:")) self.min_year = QComboBox() self.min_year.addItems([str(y) for y in range(1900, 2025)]) self.min_year.setCurrentText("1900") filter_layout.addWidget(self.min_year) filter_layout.addWidget(QLabel("结束年份:")) self.max_year = QComboBox() self.max_year.addItems([str(y) for y in range(1901, 2031)]) self.max_year.setCurrentText("2030") filter_layout.addWidget(self.max_year) layout.addLayout(filter_layout) # 表格 self.table = QTableWidget() self.table.setColumnCount(7) self.table.setHorizontalHeaderLabels(["排名", "名称", "评分", "年份", "地区", "主创", "简介"]) header = self.table.horizontalHeader() header.setSectionResizeMode(QHeaderView.ResizeMode.Stretch) layout.addWidget(self.table) # 多标签页 self.tabs = QTabWidget() self.tabs.addTab(self.table, "数据") log_tab = QWidget() log_tab.setLayout(QVBoxLayout()) log_tab.layout().addWidget(self.log_text) self.tabs.addTab(log_tab, "日志") chart_tab = QWidget() scroll = QScrollArea() scroll.setWidgetResizable(True) content = QWidget() self.chart_layout = QVBoxLayout(content) scroll.setWidget(content) self.tabs.addTab(scroll, "📊 可视化") layout.addWidget(self.tabs) # 信号连接 self.start_btn.clicked.connect(self.start_crawl) self.stop_btn.clicked.connect(self.stop_crawl) self.export_btn.clicked.connect(self.export_excel) self.filter_btn.clicked.connect(self.apply_filter) self.reset_btn.clicked.connect(self.reset_filter) def start_crawl(self): if self.crawl_thread and self.crawl_thread.isRunning(): return self.crawl_thread = CrawlThread() self.crawl_thread.progress_signal.connect(self.progress_bar.setValue) self.crawl_thread.log_signal.connect(self.log_text.append) self.crawl_thread.data_signal.connect(self.on_data_received) self.crawl_thread.finish_signal.connect(self.on_crawl_finished) self.crawl_thread.start() self.start_btn.setEnabled(False) self.stop_btn.setEnabled(True) def on_data_received(self, movies): self.movies_data = movies self.filtered_data = movies.copy() self.update_table() def update_table(self): self.table.setRowCount(0) for i, m in enumerate(self.filtered_data): self.table.insertRow(i) self.table.setItem(i, 0, QTableWidgetItem(str(i + 1))) # 正确排名 self.table.setItem(i, 1, QTableWidgetItem(m["name"])) self.table.setItem(i, 2, QTableWidgetItem(f"{m['rating']:.1f}")) self.table.setItem(i, 3, QTableWidgetItem(str(m["year"]) if m["year"] > 0 else "未知")) self.table.setItem(i, 4, QTableWidgetItem(m["country"])) self.table.setItem(i, 5, QTableWidgetItem(m["director_actor"][:20])) self.table.setItem(i, 6, QTableWidgetItem(m["summary"][:30])) def on_crawl_finished(self): self.start_btn.setEnabled(True) self.stop_btn.setEnabled(False) self.export_btn.setEnabled(True) self.filter_btn.setEnabled(True) self.reset_btn.setEnabled(True) self.log_text.append("🎉 爬取完成!共获取 250 部电影") self.apply_filter() # 自动刷新筛选和图表 def stop_crawl(self): if self.crawl_thread and self.crawl_thread.isRunning(): self.crawl_thread.is_running = False self.log_text.append("🛑 正在停止...") def apply_filter(self): try: min_r = float(self.min_rate.currentText()) max_r = float(self.max_rate.currentText()) min_y = int(self.min_year.currentText()) max_y = int(self.max_year.currentText()) self.filtered_data = [ m for m in self.movies_data if min_r <= m["rating"] <= max_r and (m["year"] == 0 or min_y <= m["year"] <= max_y) ] # ✅ 再次排序:确保筛选后仍有序 self.filtered_data.sort(key=lambda x: (-x["rating"], x["year"] if x["year"] > 0 else 9999)) self.update_table() self.show_visualization() self.log_text.append(f"🔍 筛选完成:{len(self.filtered_data)} 部符合条件") except Exception as e: QMessageBox.warning(self, "筛选错误", str(e)) def reset_filter(self): self.filtered_data = self.movies_data.copy() self.update_table() self.show_visualization() self.log_text.append("🔄 已重置筛选") def show_visualization(self): # 清除旧图表 while self.chart_layout.count(): child = self.chart_layout.takeAt(0) if child.widget(): child.widget().deleteLater() if not self.filtered_data: return # 添加多个图表,并用空白间隔开(避免紧贴) self.chart_layout.addWidget(generate_rating_line_chart(self.filtered_data)) self.chart_layout.addSpacing(20) self.chart_layout.addWidget(generate_year_histogram(self.filtered_data)) self.chart_layout.addSpacing(20) self.chart_layout.addWidget(generate_country_pie_chart(self.filtered_data)) self.chart_layout.addSpacing(20) self.chart_layout.addWidget(generate_rating_boxplot(self.filtered_data)) self.chart_layout.addSpacing(20) self.chart_layout.addWidget(generate_wordcloud(self.filtered_data)) self.chart_layout.addStretch() def export_excel(self): if not self.filtered_data: QMessageBox.warning(self, "警告", "暂无数据可导出") return path, _ = QFileDialog.getSaveFileName(self, "导出Excel", "豆瓣Top250.xlsx", "Excel文件 (*.xlsx)") if not path: return try: wb = Workbook() ws = wb.active ws.title = "豆瓣Top250" ws.append(["排名", "名称", "评分", "年份", "地区", "主创", "简介"]) for i, m in enumerate(self.filtered_data, 1): ws.append([ i, m["name"], m["rating"], m["year"] if m["year"] > 0 else "未知", m["country"], m["director_actor"], m["summary"] ]) for col in ws.columns: col_letter = col[0].column_letter ws.column_dimensions[col_letter].width = 15 wb.save(path) QMessageBox.information(self, "成功", f"✅ 已导出至:\n{path}") except PermissionError: QMessageBox.critical(self, "权限错误", "请关闭同名文件再试") except Exception as e: QMessageBox.critical(self, "导出失败", str(e)) # ==================== 启动 ==================== if __name__ == "__main__": app = QApplication(sys.argv) app.setStyle("Fusion") app.setFont(QFont("微软雅黑", 9)) win = MainWindow() win.show() sys.exit(app.exec())可视化图可以分成几个正方形方框,这样不挤一起,导致看不完,给我完整代码
12-24
给我完整代码 -- coding: utf-8 -- import sys import random import time import re import requests import os from bs4 import BeautifulSoup from openpyxl import Workbook PyQt6 from PyQt6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QProgressBar, QTableWidget, QTableWidgetItem, QTextEdit, QFileDialog, QHeaderView, QTabWidget, QMessageBox, QComboBox, QScrollArea ) from PyQt6.QtCore import Qt, QThread, pyqtSignal from PyQt6.QtGui import QFont, QColor Matplotlib import matplotlib matplotlib.use(‘Agg’) # 非GUI后端 import matplotlib.pyplot as plt plt.rcParams[‘font.sans-serif’] = [‘SimHei’, ‘Arial Unicode MS’] plt.rcParams[‘axes.unicode_minus’] = False import numpy as np from wordcloud import WordCloud import jieba jieba.initialize() from collections import Counter ==================== 字体路径检测 ==================== def get_font_path(): import platform system = platform.system() if system == “Windows”: return r"C:\Windows\Fonts\simhei.ttf" elif system == “Darwin”: return “/System/Library/Fonts/PingFang.ttc” else: return “/usr/share/fonts/truetype/wqy/wenquanyi-microhei.ttc” FONT_PATH = get_font_path() if not os.path.exists(FONT_PATH): print(f"⚠️ 字体未找到: {FONT_PATH}“) else: print(f”✅ 使用字体: {FONT_PATH}") ==================== 配色 ==================== C = { “bg”: QColor(255, 255, 255), “primary”: QColor(255, 179, 186), “secondary”: QColor(186, 220, 255), “text”: QColor(51, 51, 51), } def rgb©: return f"#{c.red():02x}{c.green():02x}{c.blue():02x}" ==================== 按钮样式 ==================== def create_button(text): btn = QPushButton(text) btn.setStyleSheet(f"“” QPushButton {{ background: qlineargradient(x1:0,y1:0,x2:1,y2:0,stop:0 {rgb(C[‘primary’])},stop:1 {rgb(C[‘secondary’])}); color: {rgb(C[‘text’])}; font-size: 14px; font-weight: bold; border: none; border-radius: 8px; padding: 8px 16px; }} QPushButton:hover {{ background: {rgb(C[‘secondary’])}; }} QPushButton:disabled {{ background: #eeeeee; color: #999999; }} “”") return btn ==================== 爬虫线程 ==================== class CrawlThread(QThread): progress_signal = pyqtSignal(int) log_signal = pyqtSignal(str) data_signal = pyqtSignal(list) finish_signal = pyqtSignal() def __init__(self): super().__init__() self.is_running = True def run(self): movies = [] headers_list = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0)" ] for page in range(10): if not self.is_running: break try: time.sleep(random.uniform(0.5, 1.0)) url = "https://movie.douban.com/top250" params = {"start": page * 25} headers = {"User-Agent": random.choice(headers_list)} resp = requests.get(url, headers=headers, params=params, timeout=10) resp.encoding = 'utf-8' if resp.status_code != 200: self.log_signal.emit(f"第{page+1}页失败: {resp.status_code}") continue soup = BeautifulSoup(resp.text, 'html.parser') items = soup.find_all('div', class_='item') for item in items: try: title_tag = item.find('span', class_='title') rating_tag = item.find('span', class_='rating_num') info_p = item.find('div', class_='bd').find('p').get_text('\n', strip=True) quote_tag = item.find('span', class_='inq') name = title_tag.text.strip() if title_tag else "未知电影" rating = float(rating_tag.text.strip()) if rating_tag else 0.0 year_match = re.search(r'(19\d{2}|20[012]\d)', info_p) lines = [line.strip() for line in info_p.split('\n') if line.strip()] country_line = "" for line in reversed(lines): if '/' in line and not re.search(r'\d{4}\s*$', line): country_line = line break parts = [p.strip() for p in country_line.split('/')] country = "未知" for p in parts: p = re.sub(r'^\d{4}', '', p).strip() if len(p) > 1 and not p.isdigit() and re.match(r'^[a-zA-Z\u4e00-\u9fa5]', p): country = p break director_actor = info_p.split('\n')[0].strip() summary = quote_tag.text.strip() if quote_tag else "" movie = { "name": name, "rating": rating, "year": int(year_match.group(1)) if year_match else 0, "country": country, "director_actor": director_actor, "summary": summary, "full_text": f"{name} {summary}" } movies.append(movie) except: continue current_count = len(movies) self.progress_signal.emit(min(int(current_count / 250 * 100), 100)) self.log_signal.emit(f"第{page+1}页完成 → 抓取") except Exception as e: self.log_signal.emit(f"网络错误: {str(e)[:30]}") while len(movies) < 250: movies.append({ "name": "数据缺失", "rating": 0.0, "year": 0, "country": "未知", "director_actor": "", "summary": "", "full_text": "" }) self.data_signal.emit(movies) self.finish_signal.emit() ==================== 可视化图表生成器(重点修复尺寸)==================== from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas def generate_rating_line_chart(movies): “”“生成 8.0~10.0 每0.1一格的折线图,并确保文字不重叠”“” ratings = [m[“rating”] for m in movies if 8.0 <= m[“rating”] <= 10.0] bins = np.arange(8.0, 10.1, 0.1) # 正好21档 hist, _ = np.histogram(ratings, bins=bins) # 关键:减小图像尺寸以适应界面 fig, ax = plt.subplots(figsize=(10, 4), dpi=100) # 控制宽高比和分辨率 ax.plot(bins[:-1], hist, marker='o', linestyle='-', color=C["primary"].name(), linewidth=2, markersize=4) ax.set_title("📈 评分分布趋势图(8.0~10.0,步长0.1)", fontsize=14, fontweight='bold', pad=10) ax.set_xlabel("评分", fontsize=12) ax.set_ylabel("数量", fontsize=12) ax.grid(True, alpha=0.3) ax.tick_params(axis='both', which='major', labelsize=10) # 设置X轴只显示部分标签防止拥挤 ax.set_xticks(bins[::2]) # 每隔一个显示一次(即每0.2显示一次) plt.setp(ax.get_xticklabels(), rotation=0) # 不旋转 plt.tight_layout(pad=1.0) # 关键:留出边距避免裁剪 return FigureCanvas(fig) def generate_wordcloud(movies): texts = [m[“full_text”] for m in movies if m[“full_text”].strip()] text = " ".join(texts) words = jieba.lcut(text) stopwords = {“电影”, “影片”, “故事”, “这部”, “一个”, “没有”, “我们”, “自己”, “导演”, “主演”} words = [w for w in words if len(w) > 1 and w not in stopwords and re.match(r’+$', w)] freq = Counter(words).most_common(100) if not freq: freq = [(“经典”, 10)] wc = WordCloud( font_path=FONT_PATH, width=800, height=400, background_color="white", max_words=100 ).generate_from_frequencies(dict(freq)) fig, ax = plt.subplots(figsize=(10, 4), dpi=100) ax.imshow(wc, interpolation='bilinear') ax.axis("off") ax.set_title("☁️ 高频词汇词云", fontsize=14, pad=10) plt.tight_layout(pad=1.0) return FigureCanvas(fig) ==================== 主窗口 ==================== class MainWindow(QMainWindow): def init(self): super().init() self.setWindowTitle(“豆瓣Top250 - 评分趋势分析”) self.resize(1000, 700) self.movies_data = [] self.filtered_data = [] self.crawl_thread = None self.init_ui() def init_ui(self): container = QWidget() self.setCentralWidget(container) layout = QVBoxLayout(container) title = QLabel("📊 豆瓣Top250 评分趋势分析") title.setAlignment(Qt.AlignmentFlag.AlignCenter) title.setStyleSheet(f"color: {rgb(C['primary'])}; font-size: 24px; font-weight: bold;") layout.addWidget(title) # 控制按钮 ctrl_layout = QHBoxLayout() self.start_btn = create_button("开始爬取") self.stop_btn = create_button("停止") self.export_btn = create_button("导出Excel") self.filter_btn = create_button("应用筛选") self.reset_btn = create_button("重置") self.stop_btn.setEnabled(False) self.export_btn.setEnabled(False) self.filter_btn.setEnabled(False) self.reset_btn.setEnabled(False) ctrl_layout.addWidget(self.start_btn) ctrl_layout.addWidget(self.stop_btn) ctrl_layout.addWidget(self.export_btn) ctrl_layout.addWidget(self.filter_btn) ctrl_layout.addWidget(self.reset_btn) layout.addLayout(ctrl_layout) self.progress_bar = QProgressBar() layout.addWidget(self.progress_bar) self.log_text = QTextEdit() self.log_text.setMaximumHeight(80) self.log_text.setReadOnly(True) layout.addWidget(self.log_text) # 筛选条件(含年份) filter_layout = QHBoxLayout() filter_layout.addWidget(QLabel("最低评分:")) self.min_rate = QComboBox() self.min_rate.addItems([f"{x:.1f}" for x in np.arange(8.0, 10.0, 0.1)]) self.min_rate.setCurrentText("8.0") filter_layout.addWidget(self.min_rate) filter_layout.addWidget(QLabel("最高评分:")) self.max_rate = QComboBox() self.max_rate.addItems([f"{x:.1f}" for x in np.arange(8.1, 10.1, 0.1)]) self.max_rate.setCurrentText("10.0") filter_layout.addWidget(self.max_rate) filter_layout.addWidget(QLabel("起始年份:")) self.min_year = QComboBox() self.min_year.addItems([str(y) for y in range(1900, 2025)]) self.min_year.setCurrentText("1900") filter_layout.addWidget(self.min_year) filter_layout.addWidget(QLabel("结束年份:")) self.max_year = QComboBox() self.max_year.addItems([str(y) for y in range(1901, 2031)]) self.max_year.setCurrentText("2030") filter_layout.addWidget(self.max_year) layout.addLayout(filter_layout) # 表格 self.table = QTableWidget() self.table.setColumnCount(7) self.table.setHorizontalHeaderLabels(["排名", "名称", "评分", "年份", "地区", "主创", "简介"]) header = self.table.horizontalHeader() header.setSectionResizeMode(QHeaderView.ResizeMode.Stretch) layout.addWidget(self.table) # 多标签页 self.tabs = QTabWidget() self.tabs.addTab(self.table, "数据") log_tab = QWidget() log_tab.setLayout(QVBoxLayout()) log_tab.layout().addWidget(self.log_text) self.tabs.addTab(log_tab, "日志") chart_tab = QWidget() scroll = QScrollArea() scroll.setWidgetResizable(True) # 允许自适应大小 content = QWidget() self.chart_layout = QVBoxLayout(content) scroll.setWidget(content) self.tabs.addTab(scroll, "📊 可视化") layout.addWidget(self.tabs) # 信号连接 self.start_btn.clicked.connect(self.start_crawl) self.stop_btn.clicked.connect(self.stop_crawl) self.export_btn.clicked.connect(self.export_excel) self.filter_btn.clicked.connect(self.apply_filter) self.reset_btn.clicked.connect(self.reset_filter) def start_crawl(self): if self.crawl_thread and self.crawl_thread.isRunning(): return self.crawl_thread = CrawlThread() self.crawl_thread.progress_signal.connect(self.progress_bar.setValue) self.crawl_thread.log_signal.connect(self.log_text.append) self.crawl_thread.data_signal.connect(self.on_data_received) self.crawl_thread.finish_signal.connect(self.on_crawl_finished) self.crawl_thread.start() self.start_btn.setEnabled(False) self.stop_btn.setEnabled(True) def on_data_received(self, movies): self.movies_data = movies self.filtered_data = movies.copy() self.update_table() def update_table(self): self.table.setRowCount(0) for i, m in enumerate(self.filtered_data): self.table.insertRow(i) self.table.setItem(i, 0, QTableWidgetItem(str(i + 1))) self.table.setItem(i, 1, QTableWidgetItem(m["name"])) self.table.setItem(i, 2, QTableWidgetItem(f"{m['rating']:.1f}")) self.table.setItem(i, 3, QTableWidgetItem(str(m["year"]) if m["year"] > 0 else "未知")) self.table.setItem(i, 4, QTableWidgetItem(m["country"])) self.table.setItem(i, 5, QTableWidgetItem(m["director_actor"][:20])) self.table.setItem(i, 6, QTableWidgetItem(m["summary"][:30])) def on_crawl_finished(self): self.start_btn.setEnabled(True) self.stop_btn.setEnabled(False) self.export_btn.setEnabled(True) self.filter_btn.setEnabled(True) self.reset_btn.setEnabled(True) self.log_text.append("🎉 爬取完成!共获取 250 部电影") self.apply_filter() def stop_crawl(self): if self.crawl_thread and self.crawl_thread.isRunning(): self.crawl_thread.is_running = False self.log_text.append("🛑 正在停止...") def apply_filter(self): try: min_r = float(self.min_rate.currentText()) max_r = float(self.max_rate.currentText()) min_y = int(self.min_year.currentText()) max_y = int(self.max_year.currentText()) self.filtered_data = [ m for m in self.movies_data if min_r <= m["rating"] <= max_r and (m["year"] == 0 or min_y <= m["year"] <= max_y) ] self.update_table() self.show_visualization() self.log_text.append(f"🔍 筛选完成:{len(self.filtered_data)} 部符合条件") except Exception as e: QMessageBox.warning(self, "筛选错误", str(e)) def reset_filter(self): self.filtered_data = self.movies_data.copy() self.update_table() self.show_visualization() self.log_text.append("🔄 已重置筛选") def show_visualization(self): while self.chart_layout.count(): child = self.chart_layout.takeAt(0) if child.widget(): child.widget().deleteLater() if not self.filtered_data: return self.chart_layout.addWidget(generate_rating_line_chart(self.filtered_data)) self.chart_layout.addWidget(generate_wordcloud(self.filtered_data)) self.chart_layout.addStretch() # 底部留空便于滚动 def export_excel(self): if not self.filtered_data: QMessageBox.warning(self, "警告", "暂无数据可导出") return path, _ = QFileDialog.getSaveFileName(self, "导出Excel", "豆瓣Top250.xlsx", "Excel文件 (*.xlsx)") if not path: return try: wb = Workbook() ws = wb.active ws.title = "豆瓣Top250" ws.append(["排名", "名称", "评分", "年份", "地区", "主创", "简介"]) for i, m in enumerate(self.filtered_data, 1): ws.append([i, m["name"], m["rating"], m["year"], m["country"], m["director_actor"], m["summary"]]) for col in ws.columns: col_letter = col[0].column_letter ws.column_dimensions[col_letter].width = 15 wb.save(path) QMessageBox.information(self, "成功", f"✅ 已导出至:\n{path}") except PermissionError: QMessageBox.critical(self, "权限错误", "请关闭同名文件再试") except Exception as e: QMessageBox.critical(self, "导出失败", str(e)) ==================== 启动 ==================== if name == “main”: app = QApplication(sys.argv) app.setStyle(“Fusion”) app.setFont(QFont(“微软雅黑”, 9)) win = MainWindow() win.show() sys.exit(app.exec())排名有问题,9.4在9.5前,部分没有按顺序,且给我可视化多些图不要挨一起去,在我源代码上
12-24
可视化图可以分成几个正方形,这样不挤一起,导致看不完,给我完整代码# -- coding: utf-8 -- import sys import random import time import re import requests from bs4 import BeautifulSoup from openpyxl import Workbook from PyQt6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QProgressBar, QTableWidget, QTableWidgetItem, QTextEdit, QFileDialog, QHeaderView, QTabWidget, QMessageBox, QComboBox, QScrollArea ) from PyQt6.QtCore import Qt, QThread, pyqtSignal from PyQt6.QtGui import QFont, QColor import matplotlib matplotlib.use('Agg') # 非GUI后端 import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS'] plt.rcParams['axes.unicode_minus'] = False import numpy as np from wordcloud import WordCloud import jieba jieba.initialize() from collections import Counter import os import platform # ==================== 字体路径检测 ==================== def get_font_path(): system = platform.system() if system == "Windows": return r"C:\Windows\Fonts\simhei.ttf" elif system == "Darwin": # macOS return "/System/Library/Fonts/PingFang.ttc" else: return "/usr/share/fonts/truetype/wqy/wenquanyi-microhei.ttc" FONT_PATH = get_font_path() if not os.path.exists(FONT_PATH): print(f"⚠️ 字体未找到: {FONT_PATH}") else: print(f"✅ 使用字体: {FONT_PATH}") # ==================== 配色 ==================== C = { "bg": QColor(255, 255, 255), "primary": QColor(255, 179, 186), "secondary": QColor(186, 220, 255), "text": QColor(51, 51, 51), } def rgb(c): return f"#{c.red():02x}{c.green():02x}{c.blue():02x}" # ==================== 按钮样式 ==================== def create_button(text): btn = QPushButton(text) btn.setStyleSheet(f""" QPushButton {{ background: qlineargradient(x1:0,y1:0,x2:1,y2:0, stop:0 {rgb(C['primary'])}, stop:1 {rgb(C['secondary'])}); color: {rgb(C['text'])}; font-size: 14px; font-weight: bold; border: none; border-radius: 8px; padding: 8px 16px; }} QPushButton:hover {{ background: {rgb(C['secondary'])}; }} QPushButton:disabled {{ background: #eeeeee; color: #999999; }} """) return btn # ==================== 爬虫线程 ==================== class CrawlThread(QThread): progress_signal = pyqtSignal(int) log_signal = pyqtSignal(str) data_signal = pyqtSignal(list) finish_signal = pyqtSignal() def __init__(self): super().__init__() self.is_running = True def run(self): movies = [] headers_list = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0)" ] for page in range(10): if not self.is_running: break try: time.sleep(random.uniform(0.5, 1.0)) url = "https://movie.douban.com/top250" params = {"start": page * 25} headers = {"User-Agent": random.choice(headers_list)} resp = requests.get(url, headers=headers, params=params, timeout=10) resp.encoding = 'utf-8' if resp.status_code != 200: self.log_signal.emit(f"第{page+1}页失败: {resp.status_code}") continue soup = BeautifulSoup(resp.text, 'html.parser') items = soup.find_all('div', class_='item') for item in items: try: title_tag = item.find('span', class_='title') rating_tag = item.find('span', class_='rating_num') info_p = item.find('div', class_='bd').find('p').get_text('\n', strip=True) quote_tag = item.find('span', class_='inq') name = title_tag.text.strip() if title_tag else "未知电影" rating = float(rating_tag.text.strip()) if rating_tag else 0.0 year_match = re.search(r'(19\d{2}|20[012]\d)', info_p) lines = [line.strip() for line in info_p.split('\n') if line.strip()] country_line = "" for line in reversed(lines): if '/' in line and not re.search(r'\d{4}\s*$', line): country_line = line break parts = [p.strip() for p in country_line.split('/')] country = "未知" for p in parts: p = re.sub(r'^\d{4}', '', p).strip() if len(p) > 1 and not p.isdigit() and re.match(r'^[a-zA-Z\u4e00-\u9fa5]', p): country = p break director_actor = info_p.split('\n')[0].strip() summary = quote_tag.text.strip() if quote_tag else "" movie = { "name": name, "rating": rating, "year": int(year_match.group(1)) if year_match else 0, "country": country, "director_actor": director_actor, "summary": summary, "full_text": f"{name} {summary}" } movies.append(movie) except Exception as e: continue current_count = len(movies) self.progress_signal.emit(min(int(current_count / 250 * 100), 100)) self.log_signal.emit(f"第{page+1}页完成 → 抓取 {current_count}/250") except Exception as e: self.log_signal.emit(f"网络错误: {str(e)[:30]}") # 补全数据至250条 while len(movies) < 250: movies.append({ "name": "数据缺失", "rating": 0.0, "year": 0, "country": "未知", "director_actor": "", "summary": "", "full_text": "" }) # ✅ 关键修复:按评分降序 + 年份升序 排列(确保高分靠前,同分按年份) movies.sort(key=lambda x: (-x["rating"], x["year"] if x["year"] > 0 else 9999)) self.data_signal.emit(movies) self.finish_signal.emit() # ==================== 可视化图表生成器(增强版,多图+防重叠)==================== from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas def generate_rating_line_chart(movies): """折线图:8.0~10.0 每0.1一格,趋势清晰""" ratings = [m["rating"] for m in movies if 8.0 <= m["rating"] <= 10.0] bins = np.arange(8.0, 10.1, 0.1) hist, _ = np.histogram(ratings, bins=bins) fig, ax = plt.subplots(figsize=(10, 4), dpi=100) ax.plot(bins[:-1], hist, marker='o', linestyle='-', color=C["primary"].name(), linewidth=2, markersize=4) ax.set_title("📈 评分分布趋势图(8.0~10.0,步长0.1)", fontsize=14, fontweight='bold', pad=10) ax.set_xlabel("评分", fontsize=12) ax.set_ylabel("数量", fontsize=12) ax.grid(True, alpha=0.3) ax.set_xticks(bins[::2]) plt.setp(ax.get_xticklabels(), rotation=0) plt.tight_layout(pad=1.0) return FigureCanvas(fig) def generate_wordcloud(movies): """词云图:关键词提取""" texts = [m["full_text"] for m in movies if m["full_text"].strip()] text = " ".join(texts) words = jieba.lcut(text) stopwords = {"电影", "影片", "故事", "这部", "一个", "没有", "我们", "自己", "导演", "主演", "一部"} words = [w for w in words if len(w) > 1 and w not in stopwords and re.match(r'^[\u4e00-\u9fa5a-zA-Z]+$', w)] freq = Counter(words).most_common(100) if not freq: freq = [("经典", 10)] wc = WordCloud( font_path=FONT_PATH, width=800, height=400, background_color="white", max_words=100 ).generate_from_frequencies(dict(freq)) fig, ax = plt.subplots(figsize=(10, 4), dpi=100) ax.imshow(wc, interpolation='bilinear') ax.axis("off") ax.set_title("☁️ 高频词汇词云", fontsize=14, pad=10) plt.tight_layout(pad=1.0) return FigureCanvas(fig) def generate_year_histogram(movies): """新增柱状图:年份分布""" years = [m["year"] for m in movies if m["year"] > 0] fig, ax = plt.subplots(figsize=(10, 4), dpi=100) ax.hist(years, bins=range(1950, 2030, 5), color=C["secondary"].name(), edgecolor='black', alpha=0.7) ax.set_title("📅 电影年份分布直方图(每5年一档)", fontsize=14, fontweight='bold', pad=10) ax.set_xlabel("年份", fontsize=12) ax.set_ylabel("数量", fontsize=12) ax.grid(True, axis='y', alpha=0.3) plt.tight_layout(pad=1.0) return FigureCanvas(fig) def generate_country_pie_chart(movies): """新增饼图:国家/地区占比""" countries = [m["country"] for m in movies if m["country"] != "未知"] counter = Counter(countries).most_common(8) others = sum(count for _, count in Counter(countries).items()) - sum(count for _, count in counter) labels = [c[0] for c in counter] + (["其他"] if others > 0 else []) sizes = [c[1] for c in counter] + ([others] if others > 0 else []) fig, ax = plt.subplots(figsize=(10, 4), dpi=100) ax.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90, colors=plt.cm.Pastel1.colors) ax.set_title("🌍 主要出品国家/地区占比", fontsize=14, pad=10) plt.tight_layout(pad=1.0) return FigureCanvas(fig) def generate_rating_boxplot(movies): """新增箱型图:评分分布统计""" ratings = [m["rating"] for m in movies if m["rating"] > 0] fig, ax = plt.subplots(figsize=(10, 4), dpi=100) ax.boxplot(ratings, vert=False, patch_artist=True, boxprops=dict(facecolor=C["primary"].name()), medianprops=dict(color="red")) ax.set_title("📦 评分箱型图(中位数、异常值等)", fontsize=14, fontweight='bold', pad=10) ax.set_xlabel("评分") plt.tight_layout(pad=1.0) return FigureCanvas(fig) # ==================== 主窗口 ==================== class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("豆瓣Top250 - 评分趋势分析") self.resize(1000, 700) self.movies_data = [] self.filtered_data = [] self.crawl_thread = None self.init_ui() def init_ui(self): container = QWidget() self.setCentralWidget(container) layout = QVBoxLayout(container) title = QLabel("📊 豆瓣Top250 评分趋势分析") title.setAlignment(Qt.AlignmentFlag.AlignCenter) title.setStyleSheet(f"color: {rgb(C['primary'])}; font-size: 24px; font-weight: bold;") layout.addWidget(title) # 控制按钮 ctrl_layout = QHBoxLayout() self.start_btn = create_button("开始爬取") self.stop_btn = create_button("停止") self.export_btn = create_button("导出Excel") self.filter_btn = create_button("应用筛选") self.reset_btn = create_button("重置") self.stop_btn.setEnabled(False) self.export_btn.setEnabled(False) self.filter_btn.setEnabled(False) self.reset_btn.setEnabled(False) ctrl_layout.addWidget(self.start_btn) ctrl_layout.addWidget(self.stop_btn) ctrl_layout.addWidget(self.export_btn) ctrl_layout.addWidget(self.filter_btn) ctrl_layout.addWidget(self.reset_btn) layout.addLayout(ctrl_layout) self.progress_bar = QProgressBar() layout.addWidget(self.progress_bar) self.log_text = QTextEdit() self.log_text.setMaximumHeight(80) self.log_text.setReadOnly(True) layout.addWidget(self.log_text) # 筛选条件 filter_layout = QHBoxLayout() filter_layout.addWidget(QLabel("最低评分:")) self.min_rate = QComboBox() self.min_rate.addItems([f"{x:.1f}" for x in np.arange(8.0, 10.0, 0.1)]) self.min_rate.setCurrentText("8.0") filter_layout.addWidget(self.min_rate) filter_layout.addWidget(QLabel("最高评分:")) self.max_rate = QComboBox() self.max_rate.addItems([f"{x:.1f}" for x in np.arange(8.1, 10.1, 0.1)]) self.max_rate.setCurrentText("10.0") filter_layout.addWidget(self.max_rate) filter_layout.addWidget(QLabel("起始年份:")) self.min_year = QComboBox() self.min_year.addItems([str(y) for y in range(1900, 2025)]) self.min_year.setCurrentText("1900") filter_layout.addWidget(self.min_year) filter_layout.addWidget(QLabel("结束年份:")) self.max_year = QComboBox() self.max_year.addItems([str(y) for y in range(1901, 2031)]) self.max_year.setCurrentText("2030") filter_layout.addWidget(self.max_year) layout.addLayout(filter_layout) # 表格 self.table = QTableWidget() self.table.setColumnCount(7) self.table.setHorizontalHeaderLabels(["排名", "名称", "评分", "年份", "地区", "主创", "简介"]) header = self.table.horizontalHeader() header.setSectionResizeMode(QHeaderView.ResizeMode.Stretch) layout.addWidget(self.table) # 多标签页 self.tabs = QTabWidget() self.tabs.addTab(self.table, "数据") log_tab = QWidget() log_tab.setLayout(QVBoxLayout()) log_tab.layout().addWidget(self.log_text) self.tabs.addTab(log_tab, "日志") chart_tab = QWidget() scroll = QScrollArea() scroll.setWidgetResizable(True) content = QWidget() self.chart_layout = QVBoxLayout(content) scroll.setWidget(content) self.tabs.addTab(scroll, "📊 可视化") layout.addWidget(self.tabs) # 信号连接 self.start_btn.clicked.connect(self.start_crawl) self.stop_btn.clicked.connect(self.stop_crawl) self.export_btn.clicked.connect(self.export_excel) self.filter_btn.clicked.connect(self.apply_filter) self.reset_btn.clicked.connect(self.reset_filter) def start_crawl(self): if self.crawl_thread and self.crawl_thread.isRunning(): return self.crawl_thread = CrawlThread() self.crawl_thread.progress_signal.connect(self.progress_bar.setValue) self.crawl_thread.log_signal.connect(self.log_text.append) self.crawl_thread.data_signal.connect(self.on_data_received) self.crawl_thread.finish_signal.connect(self.on_crawl_finished) self.crawl_thread.start() self.start_btn.setEnabled(False) self.stop_btn.setEnabled(True) def on_data_received(self, movies): self.movies_data = movies self.filtered_data = movies.copy() self.update_table() def update_table(self): self.table.setRowCount(0) for i, m in enumerate(self.filtered_data): self.table.insertRow(i) self.table.setItem(i, 0, QTableWidgetItem(str(i + 1))) # 正确排名 self.table.setItem(i, 1, QTableWidgetItem(m["name"])) self.table.setItem(i, 2, QTableWidgetItem(f"{m['rating']:.1f}")) self.table.setItem(i, 3, QTableWidgetItem(str(m["year"]) if m["year"] > 0 else "未知")) self.table.setItem(i, 4, QTableWidgetItem(m["country"])) self.table.setItem(i, 5, QTableWidgetItem(m["director_actor"][:20])) self.table.setItem(i, 6, QTableWidgetItem(m["summary"][:30])) def on_crawl_finished(self): self.start_btn.setEnabled(True) self.stop_btn.setEnabled(False) self.export_btn.setEnabled(True) self.filter_btn.setEnabled(True) self.reset_btn.setEnabled(True) self.log_text.append("🎉 爬取完成!共获取 250 部电影") self.apply_filter() # 自动刷新筛选和图表 def stop_crawl(self): if self.crawl_thread and self.crawl_thread.isRunning(): self.crawl_thread.is_running = False self.log_text.append("🛑 正在停止...") def apply_filter(self): try: min_r = float(self.min_rate.currentText()) max_r = float(self.max_rate.currentText()) min_y = int(self.min_year.currentText()) max_y = int(self.max_year.currentText()) self.filtered_data = [ m for m in self.movies_data if min_r <= m["rating"] <= max_r and (m["year"] == 0 or min_y <= m["year"] <= max_y) ] # ✅ 再次排序:确保筛选后仍有序 self.filtered_data.sort(key=lambda x: (-x["rating"], x["year"] if x["year"] > 0 else 9999)) self.update_table() self.show_visualization() self.log_text.append(f"🔍 筛选完成:{len(self.filtered_data)} 部符合条件") except Exception as e: QMessageBox.warning(self, "筛选错误", str(e)) def reset_filter(self): self.filtered_data = self.movies_data.copy() self.update_table() self.show_visualization() self.log_text.append("🔄 已重置筛选") def show_visualization(self): # 清除旧图表 while self.chart_layout.count(): child = self.chart_layout.takeAt(0) if child.widget(): child.widget().deleteLater() if not self.filtered_data: return # 添加多个图表,并用空白间隔开(避免紧贴) self.chart_layout.addWidget(generate_rating_line_chart(self.filtered_data)) self.chart_layout.addSpacing(20) self.chart_layout.addWidget(generate_year_histogram(self.filtered_data)) self.chart_layout.addSpacing(20) self.chart_layout.addWidget(generate_country_pie_chart(self.filtered_data)) self.chart_layout.addSpacing(20) self.chart_layout.addWidget(generate_rating_boxplot(self.filtered_data)) self.chart_layout.addSpacing(20) self.chart_layout.addWidget(generate_wordcloud(self.filtered_data)) self.chart_layout.addStretch() def export_excel(self): if not self.filtered_data: QMessageBox.warning(self, "警告", "暂无数据可导出") return path, _ = QFileDialog.getSaveFileName(self, "导出Excel", "豆瓣Top250.xlsx", "Excel文件 (*.xlsx)") if not path: return try: wb = Workbook() ws = wb.active ws.title = "豆瓣Top250" ws.append(["排名", "名称", "评分", "年份", "地区", "主创", "简介"]) for i, m in enumerate(self.filtered_data, 1): ws.append([ i, m["name"], m["rating"], m["year"] if m["year"] > 0 else "未知", m["country"], m["director_actor"], m["summary"] ]) for col in ws.columns: col_letter = col[0].column_letter ws.column_dimensions[col_letter].width = 15 wb.save(path) QMessageBox.information(self, "成功", f"✅ 已导出至:\n{path}") except PermissionError: QMessageBox.critical(self, "权限错误", "请关闭同名文件再试") except Exception as e: QMessageBox.critical(self, "导出失败", str(e)) # ==================== 启动 ==================== if __name__ == "__main__": app = QApplication(sys.argv) app.setStyle("Fusion") app.setFont(QFont("微软雅黑", 9)) win = MainWindow() win.show() sys.exit(app.exec())
12-24
下载方式:https://pan.quark.cn/s/b4d8292ba69a 在构建食品品牌的市场整合营销推广方案时,我们必须首先深入探究品牌的由来、顾客的感知以及市场环境。 此案例聚焦于一款名为“某饼干产品”的食品,该产品自1998年进入河南市场以来,经历了销售业绩的波动。 1999至2000年期间,其销售额取得了明显的上升,然而到了2001年则出现了下滑。 在先前的宣传活动中,品牌主要借助大型互动活动如ROAD SHOW来吸引顾客,但收效甚微,这揭示了宣传信息与顾客实际认同感之间的偏差。 通过市场环境剖析,我们了解到消费者对“3+2”苏打夹心饼干的印象是美味、时尚且充满活力,但同时亦存在口感腻、价位偏高、饼身坚硬等负面评价。 实际上,该产品可以塑造为兼具美味、深度与创新性的休闲食品,适宜在多种情境下分享。 这暗示着品牌需更精确地传递产品特性,同时消解消费者的顾虑。 在策略制定上,我们可考虑将新产品与原有的3+2苏打夹心进行协同推广。 这种策略的长处在于能够借助既有产品的声誉和市场占有率,同时通过新产品的加入,刷新品牌形象,吸引更多元化的消费群体。 然而,这也可能引发一些难题,例如如何合理分配新旧产品间的资源,以及如何保障新产品的独特性和吸引力不被既有产品所掩盖。 为了提升推广成效,品牌可以实施以下举措:1. **定位修正**:基于消费者反馈,重新确立产品定位,突出其美味、创新与共享的特性,减少消费者感知的缺陷。 2. **创新宣传**:宣传信息应与消费者的实际体验相契合,运用更具魅力的创意手段,例如叙事式营销,让消费者体会到产品带来的愉悦和情感共鸣。 3. **渠道选择**:在目标消费者常去的场所开展活动,例如商业中心、影院或在线平台,以提高知名度和参与度。 4. **媒体联...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值