mirror of
https://github.com/LC044/WeChatMsg
synced 2025-02-22 10:52:18 +08:00
新增”统计信息“功能
This commit is contained in:
parent
120a47009c
commit
7048cbf170
@ -240,6 +240,13 @@ class Msg:
|
||||
return parser_chatroom_message(result) if username_.__contains__('@chatroom') else result
|
||||
|
||||
def get_messages_by_type(self, username_, type_, year_='all', time_range=None):
|
||||
"""
|
||||
@param username_:
|
||||
@param type_:
|
||||
@param year_:
|
||||
@param time_range: Tuple(timestamp:开始时间戳,timestamp:结束时间戳)
|
||||
@return:
|
||||
"""
|
||||
if not self.open_flag:
|
||||
return None
|
||||
if time_range:
|
||||
|
@ -73,6 +73,60 @@ def wordcloud(wxid, is_Annual_report=False, year='2023', who='1'):
|
||||
}
|
||||
|
||||
|
||||
def wordcloud_(wxid, is_Annual_report=False, time_range=None):
|
||||
import jieba
|
||||
txt_messages = msg_db.get_messages_by_type(wxid, MsgType.TEXT, time_range=time_range)
|
||||
if not txt_messages:
|
||||
return {
|
||||
'chart_data': None,
|
||||
'keyword': "没有聊天你想分析啥",
|
||||
'max_num': "0",
|
||||
'dialogs': []
|
||||
}
|
||||
# text = ''.join(map(lambda x: x[7], txt_messages))
|
||||
text = ''.join(map(lambda x: x[7], txt_messages)) # 1“我”说的话,0“Ta”说的话
|
||||
|
||||
total_msg_len = len(text)
|
||||
# 使用jieba进行分词,并加入停用词
|
||||
words = jieba.cut(text)
|
||||
# 统计词频
|
||||
word_count = Counter(words)
|
||||
# 过滤停用词
|
||||
stopwords_file = './app/data/stopwords.txt'
|
||||
with open(stopwords_file, "r", encoding="utf-8") as stopword_file:
|
||||
stopwords1 = set(stopword_file.read().splitlines())
|
||||
# 构建 FFmpeg 可执行文件的路径
|
||||
stopwords = set()
|
||||
stopwords_file = './app/resources/data/stopwords.txt'
|
||||
if not os.path.exists(stopwords_file):
|
||||
resource_dir = getattr(sys, '_MEIPASS', os.path.abspath(os.path.dirname(__file__)))
|
||||
stopwords_file = os.path.join(resource_dir, 'app', 'resources', 'data', 'stopwords.txt')
|
||||
with open(stopwords_file, "r", encoding="utf-8") as stopword_file:
|
||||
stopwords = set(stopword_file.read().splitlines())
|
||||
stopwords = stopwords.union(stopwords1)
|
||||
filtered_word_count = {word: count for word, count in word_count.items() if len(word) > 1 and word not in stopwords}
|
||||
|
||||
# 转换为词云数据格式
|
||||
data = [(word, count) for word, count in filtered_word_count.items()]
|
||||
# text_data = data
|
||||
data.sort(key=lambda x: x[1], reverse=True)
|
||||
|
||||
text_data = data[:100] if len(data) > 100 else data
|
||||
# 创建词云图
|
||||
keyword, max_num = text_data[0]
|
||||
w = (
|
||||
WordCloud(init_opts=opts.InitOpts(width=f"{wordcloud_width}px", height=f"{wordcloud_height}px"))
|
||||
.add(series_name="聊天文字", data_pair=text_data, word_size_range=[5, 100])
|
||||
)
|
||||
# return w.render_embed()
|
||||
return {
|
||||
'chart_data': w.dump_options_with_quotes(),
|
||||
'keyword': keyword,
|
||||
'max_num': str(max_num),
|
||||
'dialogs': msg_db.get_messages_by_keyword(wxid, keyword, num=5, max_len=12)
|
||||
}
|
||||
|
||||
|
||||
def wordcloud_christmas(wxid, year='2023'):
|
||||
import jieba
|
||||
txt_messages = msg_db.get_messages_by_type(wxid, MsgType.TEXT, year)
|
||||
@ -99,7 +153,7 @@ def wordcloud_christmas(wxid, year='2023'):
|
||||
stopwords_file = './app/resources/data/stopwords.txt'
|
||||
if not os.path.exists(stopwords_file):
|
||||
resource_dir = getattr(sys, '_MEIPASS', os.path.abspath(os.path.dirname(__file__)))
|
||||
stopwords_file = os.path.join(resource_dir, 'app', 'resources', 'data','stopwords.txt')
|
||||
stopwords_file = os.path.join(resource_dir, 'app', 'resources', 'data', 'stopwords.txt')
|
||||
with open(stopwords_file, "r", encoding="utf-8") as stopword_file:
|
||||
stopwords = set(stopword_file.read().splitlines())
|
||||
stopwords = stopwords.union(stopwords1)
|
||||
|
1
app/resources/icons/down.svg
Normal file
1
app/resources/icons/down.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1706096167224" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7281" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M907.1 299.9c-14.6-14.6-38.4-14.6-53 0L513 640.5 172 299.9c-14.6-14.6-38.4-14.6-53 0-14.6 14.6-14.6 38.4 0 53l364.7 364.2c0.8 1 1.7 2 2.7 3 7.3 7.3 17 11 26.7 10.9 9.7 0 19.3-3.6 26.7-10.9 1-1 1.8-1.9 2.7-3l364.7-364.2c14.4-14.6 14.4-38.4-0.1-53z" fill="" p-id="7282"></path></svg>
|
After Width: | Height: | Size: 612 B |
1
app/resources/icons/up.svg
Normal file
1
app/resources/icons/up.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1706096206963" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8303" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M936.170667 669.952L534.613333 268.394667a32 32 0 0 0-42.986666-2.069334l-2.282667 2.069334L87.829333 669.952a8.533333 8.533333 0 0 0-2.496 6.037333v66.346667a8.533333 8.533333 0 0 0 14.570667 6.058667l412.074667-412.096 412.117333 412.096a8.533333 8.533333 0 0 0 14.570667-6.037334v-66.346666a8.533333 8.533333 0 0 0-2.496-6.058667z" fill="#333333" p-id="8304"></path></svg>
|
After Width: | Height: | Size: 706 B |
1
app/resources/icons/update.svg
Normal file
1
app/resources/icons/update.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1706094276810" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5249" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M549.546667 320v176.64l124.586666 124.586667a21.333333 21.333333 0 0 1 0 30.293333l-22.613333 22.613333a21.333333 21.333333 0 0 1-30.293333 0l-140.373334-140.373333a22.613333 22.613333 0 0 1-6.4-14.933333V320a21.333333 21.333333 0 0 1 21.333334-21.333333h32.426666a21.333333 21.333333 0 0 1 21.333334 21.333333z m346.453333 85.333333v-213.333333a21.333333 21.333333 0 0 0-21.333333-21.333333h-12.373334a20.906667 20.906667 0 0 0-15.36 6.4l-63.573333 63.573333A384 384 0 1 0 896 534.613333a21.333333 21.333333 0 0 0-5.546667-15.786666 22.186667 22.186667 0 0 0-15.36-6.826667h-42.666666a21.333333 21.333333 0 0 0-21.333334 20.053333A298.666667 298.666667 0 1 1 512 213.333333a295.68 295.68 0 0 1 210.346667 88.32l-75.946667 75.946667a20.906667 20.906667 0 0 0-6.4 15.36v12.373333a21.333333 21.333333 0 0 0 21.333333 21.333334h213.333334a21.333333 21.333333 0 0 0 21.333333-21.333334z" p-id="5250" fill="#d81e06"></path></svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -60,7 +60,11 @@ class ContactInfo(QWidget, Ui_Form):
|
||||
self.toolButton_output.showMenu()
|
||||
|
||||
def analysis(self):
|
||||
QDesktopServices.openUrl(QUrl("https://memotrace.lc044.love/"))
|
||||
# QDesktopServices.openUrl(QUrl("https://memotrace.lc044.love/"))
|
||||
self.report_thread = ReportThread(self.contact)
|
||||
# self.report_thread.okSignal.connect(lambda x: QDesktopServices.openUrl(QUrl("http://127.0.0.1:21314")))
|
||||
self.report_thread.start()
|
||||
QDesktopServices.openUrl(QUrl("http://127.0.0.1:21314/charts"))
|
||||
|
||||
def annual_report(self):
|
||||
if 'room' in self.contact.wxid:
|
||||
|
@ -199,7 +199,7 @@ QComboBox::down-arrow:on
|
||||
QLineEdit
|
||||
{
|
||||
background:transparent;
|
||||
border-radius:15px;
|
||||
border-radius:10px;
|
||||
border-top: 0px solid #b2e281;
|
||||
border-bottom: 1px solid rgb(227,228,222);
|
||||
border-right: 1px solid rgb(227,228,222);
|
||||
|
@ -6,12 +6,11 @@ from typing import List
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QTimer, QThread, pyqtSignal, QObject
|
||||
from PyQt5.QtGui import QTextCursor
|
||||
from PyQt5.QtWidgets import QApplication, QDialog, QCheckBox, QMessageBox, QCalendarWidget
|
||||
from PyQt5.QtWidgets import QApplication, QDialog, QCheckBox, QMessageBox
|
||||
|
||||
from app.DataBase import micro_msg_db, misc_db
|
||||
from app.DataBase.output_pc import Output
|
||||
from app.components import ScrollBar
|
||||
from app.components.calendar_dialog import CalendarDialog
|
||||
from app.components.export_contact_item import ContactQListWidgetItem
|
||||
from app.person import Contact
|
||||
from app.ui.menu.exportUi import Ui_Dialog
|
||||
|
@ -1,6 +1,7 @@
|
||||
from datetime import datetime
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QTimer, QThread, pyqtSignal
|
||||
from PyQt5.QtCore import QTimer, QThread, pyqtSignal, Qt, QPoint
|
||||
from PyQt5.QtGui import QMouseEvent
|
||||
from PyQt5.QtWidgets import QApplication, QDialog, QCheckBox, QMessageBox, QCalendarWidget, QWidget, QVBoxLayout, QLabel
|
||||
|
||||
from app.components.calendar_dialog import CalendarDialog
|
||||
@ -33,6 +34,7 @@ class TimeRangeDialog(QDialog, Ui_Dialog):
|
||||
self.calendar = None
|
||||
self.setupUi(self)
|
||||
self.setWindowTitle('选择日期范围')
|
||||
# self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
|
||||
self.setStyleSheet(Stylesheet)
|
||||
self.toolButton_start_time.clicked.connect(self.select_date_start)
|
||||
self.toolButton_end_time.clicked.connect(self.select_date_end)
|
||||
@ -77,6 +79,21 @@ class TimeRangeDialog(QDialog, Ui_Dialog):
|
||||
self.calendar.set_end_date()
|
||||
self.calendar.show()
|
||||
|
||||
def mouseMoveEvent(self, e: QMouseEvent): # 重写移动事件
|
||||
if self._tracking:
|
||||
self._endPos = e.pos() - self._startPos
|
||||
self.move(self.pos() + self._endPos)
|
||||
|
||||
def mousePressEvent(self, e: QMouseEvent):
|
||||
if e.button() == Qt.LeftButton:
|
||||
self._startPos = QPoint(e.x(), e.y())
|
||||
self._tracking = True
|
||||
|
||||
def mouseReleaseEvent(self, e: QMouseEvent):
|
||||
if e.button() == Qt.LeftButton:
|
||||
self._tracking = False
|
||||
self._startPos = None
|
||||
self._endPos = None
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
37
app/web_ui/templates/charts.html
Normal file
37
app/web_ui/templates/charts.html
Normal file
@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>微信年度聊天报告</title>
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts@latest/dist/echarts.min.js"></script>
|
||||
<script type="text/javascript" src="https://assets.pyecharts.org/assets/v5/echarts-wordcloud.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{my_nickname}}——{{ta_nickname}}</h1>
|
||||
<div id="echarts-container" style="width: 600px; height: 400px;"></div>
|
||||
<div id="echarts-wordcloud" style="width: 800px; height: 800px;"></div>
|
||||
<script>
|
||||
// 使用 AJAX 请求获取 Pyecharts 生成的图表配置项数据
|
||||
fetch('/get_chart_options')
|
||||
.then(response => response.json())
|
||||
.then(chartOptions => {
|
||||
// 在这里使用 ECharts 渲染图表
|
||||
var myChart = echarts.init(document.getElementById('echarts-container'));
|
||||
var option = chartOptions.chart_data;
|
||||
myChart.setOption(JSON.parse(option));
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
// 使用 AJAX 请求获取 Pyecharts 生成的图表配置项数据
|
||||
fetch('/wordcloud')
|
||||
.then(response => response.json())
|
||||
.then(chartOptions => {
|
||||
// 在这里使用 ECharts 渲染图表
|
||||
var myChart = echarts.init(document.getElementById('echarts-wordcloud'));
|
||||
var option = chartOptions.chart_data;
|
||||
myChart.setOption(JSON.parse(option));
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,8 +1,10 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
import requests
|
||||
from flask import Flask, render_template, send_file, jsonify, make_response
|
||||
from pyecharts.charts import Bar
|
||||
|
||||
from app.DataBase import msg_db
|
||||
from app.analysis import analysis
|
||||
@ -200,5 +202,47 @@ def get_image(filename):
|
||||
return send_file(os.path.join(f"{os.getcwd()}/data/avatar/", filename), mimetype='image/png')
|
||||
|
||||
|
||||
# 示例数据,实际应用中你需要替换成从数据库或其他数据源获取的数据
|
||||
echarts_data = {
|
||||
'categories': ['Category A', 'Category B', 'Category C', 'Category D', 'Category E'],
|
||||
'data': [20, 50, 80, 45, 60]
|
||||
}
|
||||
|
||||
|
||||
def generate_chart():
|
||||
# 使用 Pyecharts 生成图表
|
||||
bar = Bar()
|
||||
bar.add_xaxis(echarts_data['categories'])
|
||||
bar.add_yaxis('Data', echarts_data['data'])
|
||||
return bar.dump_options_with_quotes()
|
||||
|
||||
|
||||
@app.route('/get_chart_options')
|
||||
def get_chart_options():
|
||||
chart_options = generate_chart()
|
||||
data = {
|
||||
'chart_data': chart_options
|
||||
}
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@app.route('/wordcloud')
|
||||
def get_wordcloud():
|
||||
|
||||
time_range = (0, time.time())
|
||||
print(time_range)
|
||||
world_cloud_data = analysis.wordcloud_(contact.wxid, time_range=time_range)
|
||||
return jsonify(world_cloud_data)
|
||||
|
||||
|
||||
@app.route('/charts')
|
||||
def charts():
|
||||
data = {
|
||||
'my_nickname':Me().name,
|
||||
'ta_nickname':contact.remark,
|
||||
}
|
||||
return render_template('charts.html',**data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=True, host='0.0.0.0')
|
||||
|
Loading…
Reference in New Issue
Block a user