From 4c2ee604313bcf0097f9161babf52a036a480ef2 Mon Sep 17 00:00:00 2001 From: xiaji Date: Thu, 27 Nov 2025 07:54:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86AI=E5=88=86=E6=9E=90?= =?UTF-8?q?=E4=BA=A7=E5=93=81=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __pycache__/sqlite_viewer.cpython-313.pyc | Bin 0 -> 22378 bytes check_product_db.log | 106 +++ fix_chrome_debug.py | 124 ---- ...ight_behavior_records_20251126_192417.json | 44 -- product/product_ai_analysis.log | 672 ++++++++++++++++++ product/product_ai_analysis.py | 346 +++++++++ product/products.db | Bin 286720 -> 356352 bytes product/sqlite_viewer.log | 75 ++ product/sqlite_viewer.py | 431 +++++++++++ requirements_gui.txt | 8 + run_viewer.py | 60 ++ sqlite_viewer.log | 17 + 12 files changed, 1715 insertions(+), 168 deletions(-) create mode 100644 __pycache__/sqlite_viewer.cpython-313.pyc create mode 100644 check_product_db.log delete mode 100644 fix_chrome_debug.py delete mode 100644 playwright_behavior_records_20251126_192417.json create mode 100644 product/product_ai_analysis.log create mode 100644 product/product_ai_analysis.py create mode 100644 product/sqlite_viewer.log create mode 100644 product/sqlite_viewer.py create mode 100644 requirements_gui.txt create mode 100644 run_viewer.py create mode 100644 sqlite_viewer.log diff --git a/__pycache__/sqlite_viewer.cpython-313.pyc b/__pycache__/sqlite_viewer.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..701e8a3bc2fbdf3f43b0021f6226fc5eb9fda8b9 GIT binary patch literal 22378 zcmdUXYj_jao%iVKu|2+kEhb@e z@1mX(w!}&mP!*ITrHFFS+vyI2&|kLHky=EtW!tTev=!SZ%0*Mu_Oc49Lwp=7HKHHU z+tNdiLPVNl^9E1QJ$qtu_Q^Bxv&UyozjEpMC*seBt&6Sk(`PT9eQMJ_hiBNmG%bGU z^yMFZ^rsJ>n;knI|M0+W);}B?3SK&OCjQZhi&IZrdhSs%s%iLsxCuPy#d@E4{Ne}i z{prKQv*W+K^zzxuFNWhkIxI8>c`lteeC36g(ym~~`fWPi?Dda~xLI2c&um`3d$-p! zhTTx_rT5Fo&LQWT>Jc?Al$51Zc;XH^$d@=F?LfZu(LB13_@XM z^A^|rUbmQ(t-iY}=-vgHH+TX;F*!?hLsC;WJD?xIpqqt^Ydv1~8jlNl$(uKCaNY0r z@@bnLL02#o=yYL*K6fDC8gWCH;AXwsHSA{Z_PF=(shhj}yYBa6d@8iyb9WDWf)HHC z`a`=R7M@+dIS78r=G8;^$V1{-k&9luxqcV|--MaCoq`!i;p~H%r)P}=R6ESSBI*H} zH93qh_GX6({1k^7d>ZEmo9dv!Pa`uU-H`@i#*q$whJyh=(~$vwmLn7VY)2OO3mne&1$%9hJB!u-LfbhB6Tpo5`u@aCIU7pFeB^v22f zk)I6d#D~HQPF(z5KLp+?-3e+Cu7ca58=&rkg{Vy4L8FvrH%lSq3d=#)A=V8`g&Lr| za(;3rEow-yXDH>QkkZZwWz+YQhl!8A5P$87_~B>fdn#KBZwiEV!yKWVPLIzMbUIml z9^UBj?eOy%!}mLf{63$X3@x8A6k=JoFX#;7yyR0zfsn_h=ZyikcL$&9bPjo4fq>H) zz>&8e_+k@Wx>jr-2)Nn6_Prk01D@?`{6nE#P)T6>#i=(go;@opwD`-v*dBlR$dxx< z*}l^s+!?}!ID6=L{Kzxgh4tushzwNI?tN@2b}&~xd`(j43n3}c4&Gl6P#5Tou@sIj zh|)zD=*%POhteaKWl{RJnUeBRBSgwqnmNLZF;8Y*hjLWH@ZvC$;T?y8Q4g+Nn%P)H zbcq89gCq}_lmlj$UKwJ@k)otXA%_9}j1E(WIQUj-FjY#EhS&#F%bqU9D%Szk1R9j5 zh!X45L%A#^zZ4os?9~D(Ub!aa$Wc~7&R624kdm$}qIur4S$T^i zDK>2SZ!?ce1Nvin$|WfY$cFZOCG;qO)7)ycvej^9D=@nyVTFln-3lpzGvFPdE=r54 zAxbyS&`gXj=IG+cqNP!K8M!Th#W$WaJ~YuD&WX~Czc5op<);cqGf83zN0*HIgy{T= ziKgki*3tBhvC`7V^@B3CO-AU_`3%}_*iojzeKkjOc$}-YDX6DlX^16Mx zyK1`lPGk-WWB`lNR*tsD=sJ$B3tM8g+c_H`(H%2%u832{n6-townXVxsH3btTsK|X zhE){HkQk#YIl3}NH*j=AculNvCD*tzO0QC-*Kl;rL{I5p&oE%H%SZw(if&M5TNIpBR+PfDNUI1|sf?`JD5!;e4jIp26>9cFyDp_C+ND&z zmoZ5xGbpn~s@}^84=rlop%x>gU=%?~tx_6j?@%#C+P&BAfvW!sN~TD$2`EX~HSo~B zNE%z^s)>7)-Kyk}LQ*?77$=Q3l~O8Xr=?ivv;2PGnUR@Ea$lj(RY^tRR%(Zgj#ll+ zY$Q2Hs;bm{Gg70jgi^MoO5s+TX{0Y2Jc9jZebJtkH!r0zDmQIQr-cX#W`KGp4U{_y zJ-qbxsVfJ@W}kWuG%@kRXXB?&hZuNfQ42#7LrBAgeDSwW&Yt=g8OYb? z3Dovw^ThY3ttiDK!LB}ador;mWE!Q71WGCr==mxtsX5h)6%`VaT*%RdF}jhX8zYM! zjMA(s(#FxYaAlNkN~pb+v$jU*B}k`j;ezRsB_f#yiKvL9i()j&FEP5FqwB-%v4%Ui zhC8D4%7iLJG7eQ#HHW`9UAYRYu#%P)_9I5OaCFP0CDz)>wRT47E>$|=ExMr>3Dw@l zS#OKd%hlY0C?p|&An+vdp{>~op7SahDQxdCr6 ztN;6Xi$PgCrX@2;hzNX#%u7fp=rbrB#DL5L_>2QaEw#s>OeGX@rnV5`Zdc;HT2Ic7Jb5zvsRWlj?oQl8?_kV7|%wkQG8op9W=FM|#^pXJus)^bOT= zNzPuL6alvhyqRV{wChrO0&sd5kr z4$)c=(&D3!#m9aMMhH>;gmXw1ywHX)dJ_B;}nASA8W!mfvV&!fPV_uCe#s0R{s z7A0anZM~oM?DzYEE-%p}5xo(s&=jPLd>YZqI=x^O;M2i|v8ziE;skQA!0!_jx1dY% zf;I`{G3*APhGLqi(#;1Vvl{hHKCoZ-M(qAQU4E~h4e&H1I6V8^0b7cSG|;jZD&6be z5tJ26s#i>xq7-+bsQT36QRCq( zz`7$@V_C_PS|`^i;H+45eE78gSwG-E)L&3`d>>FL&|Td@=+)=6B}Str>iLHzywhbU zxm_r#In|U?-2eC3}OF5GmWsP(#9kv3+ zXzjBZLb%k0#dYb>d$R|AFgtNXP;yMZd+CEW`fX;_dO<)3g@d9AWpLUmX0E_a{~poO z^}3GC;{d)iaq_id=~B(fC)aoy@&s_%sW_MFC|z@*sOnUsIMgwk3{}1MQ}%2o{*-SV5TC; zN%}N08?PKV5P$q^zb#FfjeH8~-_g990xGUP2(u{K@ToySL@*Wu{vw;s-cBMRi(o+I zO+#LPz-=>;`6*O_O8rz|q1{fbQqd)+Ol{w^oM3N;wCZ&LyLvP;Njkx7`43OHEVr7}Zh+^qEwfinjq|kvU|!Fz{B= zlfjCiM`&AWs2EZ;wbA0DLC!E~(TG;xcYbmCofl?be2h;Kw_xl~Q}$x8%b;8cWlCWN z%Z!vz*jiT)8#b)wMjEWLhE5P%RZXv4u$=af|8qOi;}!|olfkT*z_+v0L173-V z(l#t-_%E7baxX9okMthujTE&`+9r!51uLV>su?Cb##C`k)xGx9o}fz6+6z(1PM(?|2x;fA%RGltRw8RRThg$UjpD4~Kpb6+6Ufn#@o@NODU5<}A8f6e1mlueDVTfmF-9m0*mcv=qI{7>&^7YUs5l zy#lyRp~S-)a;NTO`J(qstop<`Sh#xBeZbc zq1!>NG?Y<0bO2-JQ2z2i|9JMPSK_0Svxgs#pLq^;p(J4s*$W^ek&qoW0>B=#5Rw5o zWrdYsQ2hO$Dpg8b)mB)y2mmETh*@5K_wvtQSz-MWDczN)pS}FfJ3_XT6SF@%k%VT5 zV;0s5IoVy{@hJidwh251J}}vnz4q==dy_M*p5|GdG z`d!1Kv`EBBQ#Z@{S>EKv3yX{vPY%N1L)Vz|en# zTmcH``T^>yF*|jWZYC=ymQ@b_fMBug3NE`MmR-kX*G04IFP7K6R`ycaWZ`uA?W1d= zOsND_1&s~L5#6-jP*QI@z;x4c}KoG_TB509=eZR+Hhz?q>tto5aA}lTc7e%<+`nV2 z6PAg+k+z;sTR*LgwD!lCjU2P_f3BLa+}{GoB0kc!at%eDqb%JE4d*QF-5G{|$uL1U zVQ-R*vwI27YGbH`vcvFdM|AUH@r{s{2oM?qhGM=WY7*>Cl9O^hRyw~|Rq`jg5) zo%$SVeItq?Dis#&jl!--3h=m0fkG}baziKuB(^?UNvSRTILqUa%;KV+E{KAm)kU*+cLU#1|X<^tUprRA7wVq zlj_V@3{gh2!-vJHmNC8Jy^w%#Rc-k1{I~uryr%bH`NP&ju&^KF*KS z-F2Sn`8v_@>8^!cwbUoImNm-_pWNQQrrmI^-2~x;?OjHAQ#gOGzY5+7;0Eo0fXPlm z8{j{1(h+CJl1UEv!estqo7F3>55m97Dio!tf*62i+WR%tq7>@7Q2iPI=r~N|%P+kN zrq7F0Z_l25PnhWtDK4-d9TrTTf-;$~9YS8nf^31AHwMu8>S{I%I4^nuS1?dCwMSla|=)2bL^hugQIEUM%<&{ zo~WN#6tSSqZ>FRcH{;^|L9(y!UZU@WJF*Kf?L6HuSH?hULPmExj24XU4JC|!fcEJ@ zX{1T}26R9L3<*>~ucc;_XBcdU$)WBx2K3ET04W;)ntzSMh?Q^B_rdHzdgLSh2hJs?z1{ACSI6On**AZ`1CFXCFkpfKXqW)VGZ~W$TPDeV+HGVZ__EZy;Sd zxniaIKq-u4*tbcz&QlMnN|Ky3N3^7^L7A@Gtx3h;R(b+>PZd(Cy-ErxSHcP4jHOj$ zB)?CHPi<4ZbLe@EQdSDtZBj_NI?~8GNS~)xMqTTOE}*x<-1ixGP{7~ROSRa6$4ho1 zf5WJr3OKf82TzqTz$C7UK}1mE?5O)pa)iHu6C+|N5xmncU26!*koEG5he-gl?$?84h`Ml$6-o#aro{V*wIuL9MTGYu+z9(5 zlx3en?;h~T=5%7;3&D1DQ~{Ii>tOB+?ecZ`A(AfyC6v zK?XrIeFQ^V{8om*Z#{?Ugz16}tM~{3-T(of5$>FXi19{3_flB@9;at`FHd7Yh=Ob+ zDs{;1^^!OiX`sNZAy0_Bn(PFoZ5*QKXWFpd|A8EVe}l=8%(Ruw!?dAHnM2sNlIF>@ z@WWHBkz%5G%Ug8Pf6PCfS3A1y_e~ws#Y^89nFvhio?rX=$S8=JOXY&&-wgMJ?}!xN z7G;*t6L2-s+l6eXBom&){_N_M>ZN zECpybh*=sqOG9|w)bMAPuInbMVbz}vR87ZK2Bc@9KaCvIIL$Q0m@ba#I@cRp^DS=8 zx1x;eqM(8tub;SgVnd{5%{j-XmQU{axcAdwq+m;w8Ib#k@_V={yg%%ns`$|HvE_q% zrg}f#AE~pSXB_{1t$|v!?5|%mQQ7%lhkV`uL;Cb;!@5H1SNWOiEXH3|cQ?V!Z!9MC z3oYv#48K|2zP`rrX^jcO2~4TXw)`s)um2@%i?)ER!A3}A3|IzWb=Fsc=nmF9fyMEe z<{^i;vXb0@iWK!#-;kJUFd<+ALF@%bSQlBq7vj*@QVt}QLTN6^-K^9g0y%M7$^al$ zWMs8eibCliKOxt6#!;ct1_b?^@G9#wy6W=Bm!xb(&tqGz)?%FR3d>iuQn3` z30DePRquJN@hn&?Ng~^w4@PsyVS!-+tkhCdPmVC8y$Nw)Q28E zrywXH9b`3V4WF{d#rj}JjI{klrZ?=HPz008|B#GO2T!mTs%zTt$0#vEg5e zO%P5{p5olXQNJDq3G8M_>;fSL@Cz_I+0}^=f`|!ukZVhFHxvu;Ag7jibpw3W+J7No zFQ{5|QlDHf^%M*6FbFV%nNrv68n{T9W3+n9tNsQGV_7VdYE*Wj`{4Mh_S*uSBVw$> z+y;WKV4QKJv>AXp80@)9O;TuEce_rq!6Q}CIx8VRxmmjo2R(0JQWM1Y8`qvPkOJa6 zogluCI?|HF_f#LYh6>RnXV(z>l>L;7Xq!O0wCX8Ksu*rn^~h%oZdy;4bDsC6A#`qgYammZqZ(qj z)_ZG^+E=d9%&{+4;-t`^ENOJapKuFw`M?BEJJrVE$B(fH!BSaBm) z+!!lv<%(OUi`!zw%ems^)5W((=z`g-lFzC;5_zGP?NR1FsO_q;F11gSHw4Zvzj|Uz zta=$&y$sHgPNjUD8M$-gr+Xrnt)tzLT3Qw&< zFVeJXYG-8K*2vmzk!?GoBM(Hr?Vldm9og|<C$)O#kN-eB}HYd)BpE18_{Gmsx%pW3b))msS5`p zg>6x$J!z-Gx=(c<2O_P#F=hkDY#_{XA27?Pc>Q%|C*6}voy*JY$u^#=>TZCW-)5W8 z&$aZ{8h&eQ@2xQWdxZ(Y2@EvKHMnZ8{~9Lw%@}Bv1qRC`IUu4*F?g$xOGy8UY&9W> zx5!qj_c7dTo24VMSJ`vBZP#Fofu)9bgbp@L>Oi!YRRtos)T!Fl*hv875;lb?JVjtgk zIK;xIEx<+|AA3?Xf-rKlI6GnMJU*ae`W9x^L5$l0p8CuOva?B(LjCm$+-e?J26^P2 zxJUb=bmdHXRxG^){)xInJul8P?ebiKU14)g*YfK}-so$6{YB*UYYdRqkSMPfwnp6I zt3UX&D<|K+c=icAjs5bYmySLrj0lMq`P}dm@v}$z69&Kv&nawCG`ogy$=ZYk3VR73 zLUWI5iSlXW=tDQg^Z5yUm6Gry9FjKmkVGu_A&todKjSXKKO{V;7nTt$|uicSrs=L!wThd!R)MxyBoejh|(cWc=%)?90uf#czOzb_Ym~Y0OA{O z7QQr?08B>wR2ut#q1!#^ zu+AU!KH4W&ULQ3!Trj3QzW$N*qmCon#riLi)%tSU5pfgoDLS`cqFl8XC#}6A)MvH zrz}7-F0x)Yhsl>M$hRw4#1lSKX|c=Cx`mU>8A{~3kcS+JBLhO-F8d!)?sZDQhH9*X zzC%FRRqSQBg_lnSkcMA1=ybY2n+-bS^)yPC{s$`c4^-wKslvY)jk=D%P; sqlite3.Connection: + """连接到SQLite数据库""" + try: + conn = sqlite3.connect(self.db_path) + logger.success(f"成功连接到数据库: {self.db_path}") + return conn + except Exception as e: + logger.error(f"连接数据库失败: {e}") + raise + + def get_product_data(self, conn: sqlite3.Connection) -> List[Tuple]: + """ + 从数据库获取产品数据 + + Args: + conn: 数据库连接 + + Returns: + 产品数据列表,每个元素为(id, name, introduction) + """ + try: + cursor = conn.cursor() + + # 查询products表中的name和introduction字段 + cursor.execute(""" + SELECT id, name, introduction + FROM products + WHERE name IS NOT NULL AND introduction IS NOT NULL + AND name != '' AND introduction != '' + """) + + products = cursor.fetchall() + logger.info(f"从数据库获取到 {len(products)} 个产品") + + # 显示前几个产品作为示例 + for i, (id, name, intro) in enumerate(products[:3], 1): + logger.info(f"示例产品{i}: ID={id}, 名称='{name}', 简介='{intro[:50]}...'") + + return products + + except Exception as e: + logger.error(f"获取产品数据失败: {e}") + raise + + def call_zhipu_ai_api(self, name: str, introduction: str) -> Optional[str]: + """ + 调用智谱AI API进行分析 + + Args: + name: 产品名称 + introduction: 产品简介 + + Returns: + API响应内容,失败时返回None + """ + try: + # 构建请求数据 + messages = [ + { + "role": "system", + "content": "你是一个有用的AI助手。" + }, + { + "role": "user", + "content": f"这个是【{name}】,简介内容是【{introduction}】。请把产品的简介翻译成中文,并返回假设一个人加上AI辅助能否开发这个产品,请详细回答。返回的内容是产品名称/产品简介/开发难度。返回的例子一:notion/这个是笔记产品等等/一个人开发难度较高" + } + ] + + data = { + "model": "GLM-4.5-Flash", + "messages": messages, + "temperature": 0.6 + } + + headers = { + "Authorization": f"Bearer {self.api_key}", + "Content-Type": "application/json" + } + + logger.info(f"调用智谱AI API分析产品: {name}") + + response = requests.post( + self.api_url, + headers=headers, + data=json.dumps(data, ensure_ascii=False), + timeout=60 + ) + + if response.status_code == 200: + result = response.json() + content = result["choices"][0]["message"]["content"] + logger.success(f"API调用成功: {name}") + return content + else: + logger.error(f"API调用失败: {response.status_code}, {response.text}") + return None + + except Exception as e: + logger.error(f"调用智谱AI API时出错: {e}") + return None + + def parse_ai_response(self, response: str) -> Tuple[str, str, str]: + """ + 解析AI响应内容 + + Args: + response: AI响应内容 + + Returns: + (产品名称, 产品简介, 开发难度) + """ + try: + # 使用/分割响应内容 + parts = response.split('/') + + if len(parts) >= 3: + product_name = parts[0].strip() + product_intro = parts[1].strip() + difficulty = parts[2].strip() + + logger.info(f"解析结果: 名称='{product_name}', 简介='{product_intro[:30]}...', 难度='{difficulty}'") + return product_name, product_intro, difficulty + else: + logger.warning(f"响应格式不符合预期: {response}") + # 如果格式不符合,返回原始内容 + return "", response, "" + + except Exception as e: + logger.error(f"解析AI响应失败: {e}") + return "", response, "" + + def create_analysis_table(self, conn: sqlite3.Connection): + """创建分析结果表""" + try: + cursor = conn.cursor() + + # 创建分析结果表 + cursor.execute(""" + CREATE TABLE IF NOT EXISTS product_analysis ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + original_id INTEGER, + original_name TEXT, + product_name TEXT, + product_intro TEXT, + development_difficulty TEXT, + ai_response TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (original_id) REFERENCES products (id) + ) + """) + + conn.commit() + logger.success("创建分析结果表成功") + + except Exception as e: + logger.error(f"创建分析结果表失败: {e}") + raise + + def save_analysis_result(self, conn: sqlite3.Connection, + original_id: int, original_name: str, + product_name: str, product_intro: str, + difficulty: str, ai_response: str): + """保存分析结果到数据库""" + try: + cursor = conn.cursor() + + cursor.execute(""" + INSERT INTO product_analysis + (original_id, original_name, product_name, product_intro, development_difficulty, ai_response) + VALUES (?, ?, ?, ?, ?, ?) + """, (original_id, original_name, product_name, product_intro, difficulty, ai_response)) + + conn.commit() + logger.success(f"保存分析结果成功: {product_name}") + + except Exception as e: + logger.error(f"保存分析结果失败: {e}") + raise + + def check_product_exists(self, conn: sqlite3.Connection, original_name: str) -> bool: + """ + 检查产品是否已存在于分析结果表中 + + Args: + conn: 数据库连接 + original_name: 原始产品名称 + + Returns: + 如果产品已存在返回True,否则返回False + """ + try: + cursor = conn.cursor() + cursor.execute(""" + SELECT COUNT(*) FROM product_analysis + WHERE original_name = ? + """, (original_name,)) + + count = cursor.fetchone()[0] + exists = count > 0 + + if exists: + logger.info(f"产品 '{original_name}' 已存在,跳过分析") + + return exists + + except Exception as e: + logger.error(f"检查产品存在性失败: {e}") + return False + + def analyze_products(self, max_products: int = None): + """ + 分析产品数据 + + Args: + max_products: 最大分析产品数量,None表示分析所有产品 + """ + if max_products is None: + logger.info("开始分析所有产品数据") + else: + logger.info(f"开始分析产品数据,最大数量: {max_products}") + + conn = None + try: + # 连接数据库 + conn = self.connect_to_database() + + # 创建分析结果表 + self.create_analysis_table(conn) + + # 获取产品数据 + products = self.get_product_data(conn) + + if not products: + logger.warning("没有找到可分析的产品数据") + return + + # 限制分析数量 + if max_products is not None: + products_to_analyze = products[:max_products] + else: + products_to_analyze = products + + logger.info(f"准备分析 {len(products_to_analyze)} 个产品") + + # 逐个分析产品 + success_count = 0 + skip_count = 0 + for i, (original_id, name, introduction) in enumerate(products_to_analyze, 1): + logger.info(f"\n分析进度: {i}/{len(products_to_analyze)} - {name}") + + # 检查产品是否已存在 + if self.check_product_exists(conn, name): + skip_count += 1 + logger.info(f"跳过已存在产品,当前进度: {i}/{len(products_to_analyze)}") + continue + + # 显示API调用状态 + logger.info(f"正在提交API请求... 进度: {i}/{len(products_to_analyze)}") + + # 调用AI API + ai_response = self.call_zhipu_ai_api(name, introduction) + + if ai_response: + # 显示数据处理状态 + logger.info(f"API调用成功,正在处理数据...") + + # 解析响应 + product_name, product_intro, difficulty = self.parse_ai_response(ai_response) + + # 保存结果 + self.save_analysis_result(conn, original_id, name, + product_name, product_intro, difficulty, ai_response) + success_count += 1 + + # 显示完成状态 + logger.success(f"产品 '{name}' 分析完成,进度: {i}/{len(products_to_analyze)}") + else: + logger.error(f"分析失败: {name}") + + # 处理完数据后延时2秒 + logger.info("数据处理完成,等待2秒后继续...") + time.sleep(2) + + logger.success(f"分析完成! 成功分析 {success_count} 个产品,跳过 {skip_count} 个已存在产品") + + except Exception as e: + logger.error(f"分析过程中出错: {e}") + finally: + if conn: + conn.close() + logger.info("数据库连接已关闭") + +def main(): + """主函数""" + # 配置日志 + logger.add("product_ai_analysis.log", rotation="10 MB", level="INFO") + + # 智谱AI API密钥(请替换为您的实际密钥) + api_key = "fad3d9f9a45f4d939f0e7a7133fa07bf.X4bOO053GAIPKLE5" + + # 创建分析器 + analyzer = ProductAIAnalyzer(api_key) + + # 开始分析(默认分析所有产品) + analyzer.analyze_products(max_products=None) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/product/products.db b/product/products.db index e8d2485908c1859f7e58cc413e0041eb6e29d4cd..bf620888f928be021b980abf6996d52bc69aa140 100644 GIT binary patch delta 57327 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 delta 96 zcmZp8Alh(1aDud84g&*&8xX?)<3t@};T#6NE)ib-9}LXA>lyeM`H%Ci-z;cg%e#3! pZ>SO*BmYMR{*OR`Q~Z-B2*@^nv~T}t&j`d!K+L@Tqdg110svd18zle$ diff --git a/product/sqlite_viewer.log b/product/sqlite_viewer.log new file mode 100644 index 0000000..3acafaa --- /dev/null +++ b/product/sqlite_viewer.log @@ -0,0 +1,75 @@ +2025-11-26 22:30:21.718 | INFO | sqlite_viewer:__init__:26 - 初始化SQLite数据库查看器 +2025-11-26 22:30:21.718 | INFO | sqlite_viewer:init_ui:33 - 设置主窗口界面 +2025-11-26 22:30:21.720 | INFO | sqlite_viewer:create_top_buttons:60 - 创建顶部按钮 +2025-11-26 22:30:21.726 | INFO | sqlite_viewer:create_splitter:83 - 创建分割器界面 +2025-11-26 22:30:21.750 | INFO | sqlite_viewer:create_status_bar:112 - 创建状态栏 +2025-11-26 22:30:21.752 | INFO | sqlite_viewer:create_menubar:119 - 创建菜单栏 +2025-11-26 22:30:21.776 | INFO | sqlite_viewer:init_ui:56 - 界面初始化完成 +2025-11-26 22:30:21.936 | INFO | sqlite_viewer:main:271 - 应用程序启动完成 +2025-11-26 22:38:21.031 | INFO | sqlite_viewer:open_database:135 - 打开数据库文件对话框 +2025-11-26 22:38:29.485 | INFO | sqlite_viewer:open_database:149 - 打开数据库文件: C:/Users/xiaji/Documents/个人文件夹/夏骥/hothub的抓取/product/products.db +2025-11-26 22:38:29.487 | INFO | sqlite_viewer:connect_to_database:159 - 数据库连接成功 +2025-11-26 22:38:29.488 | INFO | sqlite_viewer:load_table_list:187 - 加载了 2 个表 +2025-11-26 22:38:32.263 | INFO | sqlite_viewer:on_table_selected:197 - 选中表: products +2025-11-26 22:38:32.274 | INFO | sqlite_viewer:load_table_data:232 - 加载表 products 数据完成,共 363 行 +2025-11-26 22:38:32.967 | INFO | sqlite_viewer:on_table_selected:197 - 选中表: products +2025-11-26 22:41:28.425 | INFO | sqlite_viewer:__init__:26 - 初始化SQLite数据库查看器 +2025-11-26 22:41:28.425 | INFO | sqlite_viewer:init_ui:33 - 设置主窗口界面 +2025-11-26 22:41:28.425 | INFO | sqlite_viewer:create_top_buttons:60 - 创建顶部按钮 +2025-11-26 22:41:28.426 | INFO | sqlite_viewer:create_splitter:83 - 创建分割器界面 +2025-11-26 22:41:28.431 | INFO | sqlite_viewer:create_status_bar:112 - 创建状态栏 +2025-11-26 22:41:28.431 | INFO | sqlite_viewer:create_menubar:119 - 创建菜单栏 +2025-11-26 22:41:28.443 | INFO | sqlite_viewer:init_ui:56 - 界面初始化完成 +2025-11-26 22:41:28.573 | INFO | sqlite_viewer:main:271 - 应用程序启动完成 +2025-11-26 22:41:30.668 | INFO | sqlite_viewer:open_database:135 - 打开数据库文件对话框 +2025-11-26 22:41:32.817 | INFO | sqlite_viewer:open_database:149 - 打开数据库文件: C:/Users/xiaji/Documents/个人文件夹/夏骥/hothub的抓取/product/products.db +2025-11-26 22:41:32.818 | INFO | sqlite_viewer:connect_to_database:159 - 数据库连接成功 +2025-11-26 22:41:32.819 | INFO | sqlite_viewer:load_table_list:187 - 加载了 2 个表 +2025-11-26 22:41:35.064 | INFO | sqlite_viewer:on_table_selected:197 - 选中表: products +2025-11-26 22:41:35.072 | INFO | sqlite_viewer:load_table_data:232 - 加载表 products 数据完成,共 363 行 +2025-11-26 22:41:56.841 | INFO | sqlite_viewer:on_table_selected:197 - 选中表: sqlite_sequence +2025-11-26 22:41:56.844 | INFO | sqlite_viewer:load_table_data:232 - 加载表 sqlite_sequence 数据完成,共 1 行 +2025-11-26 22:41:58.405 | INFO | sqlite_viewer:on_table_selected:197 - 选中表: products +2025-11-26 22:41:58.431 | INFO | sqlite_viewer:load_table_data:232 - 加载表 products 数据完成,共 363 行 +2025-11-26 22:42:23.194 | INFO | sqlite_viewer:closeEvent:249 - 关闭应用程序 +2025-11-26 22:42:26.912 | INFO | __main__:__init__:26 - 初始化SQLite数据库查看器 +2025-11-26 22:42:26.913 | INFO | __main__:init_ui:33 - 设置主窗口界面 +2025-11-26 22:42:26.913 | INFO | __main__:create_top_buttons:60 - 创建顶部按钮 +2025-11-26 22:42:26.914 | INFO | __main__:create_splitter:83 - 创建分割器界面 +2025-11-26 22:42:26.918 | INFO | __main__:create_status_bar:112 - 创建状态栏 +2025-11-26 22:42:26.919 | INFO | __main__:create_menubar:119 - 创建菜单栏 +2025-11-26 22:42:26.930 | INFO | __main__:init_ui:56 - 界面初始化完成 +2025-11-26 22:42:27.061 | INFO | __main__:main:271 - 应用程序启动完成 +2025-11-26 22:42:30.851 | INFO | __main__:open_database:135 - 打开数据库文件对话框 +2025-11-26 22:42:32.254 | INFO | __main__:open_database:149 - 打开数据库文件: C:/Users/xiaji/Documents/个人文件夹/夏骥/hothub的抓取/product/products.db +2025-11-26 22:42:32.254 | INFO | __main__:connect_to_database:159 - 数据库连接成功 +2025-11-26 22:42:32.255 | INFO | __main__:load_table_list:187 - 加载了 2 个表 +2025-11-26 22:42:33.747 | INFO | __main__:on_table_selected:197 - 选中表: products +2025-11-26 22:42:33.756 | INFO | __main__:load_table_data:232 - 加载表 products 数据完成,共 363 行 +2025-11-26 22:46:51.405 | INFO | __main__:closeEvent:249 - 关闭应用程序 +2025-11-26 22:50:54.320 | INFO | __main__:__init__:27 - 初始化SQLite数据库查看器 +2025-11-26 22:50:54.320 | INFO | __main__:init_ui:34 - 设置主窗口界面 +2025-11-26 22:50:54.321 | INFO | __main__:create_top_buttons:64 - 创建顶部按钮 +2025-11-26 22:50:54.321 | INFO | __main__:create_filter_section:87 - 创建筛选控件区域 +2025-11-26 22:50:54.330 | INFO | __main__:create_splitter:132 - 创建分割器界面 +2025-11-26 22:50:54.331 | INFO | __main__:create_status_bar:161 - 创建状态栏 +2025-11-26 22:50:54.331 | INFO | __main__:create_menubar:168 - 创建菜单栏 +2025-11-26 22:50:54.341 | INFO | __main__:init_ui:60 - 界面初始化完成 +2025-11-26 22:50:54.448 | INFO | __main__:main:426 - 应用程序启动完成 +2025-11-26 22:53:25.181 | INFO | __main__:__init__:27 - 初始化SQLite数据库查看器 +2025-11-26 22:53:25.182 | INFO | __main__:init_ui:34 - 设置主窗口界面 +2025-11-26 22:53:25.182 | INFO | __main__:create_top_buttons:64 - 创建顶部按钮 +2025-11-26 22:53:25.183 | INFO | __main__:create_filter_section:87 - 创建筛选控件区域 +2025-11-26 22:53:25.187 | INFO | __main__:create_splitter:132 - 创建分割器界面 +2025-11-26 22:53:25.188 | INFO | __main__:create_status_bar:161 - 创建状态栏 +2025-11-26 22:53:25.188 | INFO | __main__:create_menubar:168 - 创建菜单栏 +2025-11-26 22:53:25.199 | INFO | __main__:init_ui:60 - 界面初始化完成 +2025-11-26 22:53:25.340 | INFO | __main__:main:426 - 应用程序启动完成 +2025-11-26 22:53:27.659 | INFO | __main__:open_database:184 - 打开数据库文件对话框 +2025-11-26 22:53:29.435 | INFO | __main__:open_database:198 - 打开数据库文件: C:/Users/xiaji/Documents/个人文件夹/夏骥/hothub的抓取/product/products.db +2025-11-26 22:53:29.435 | INFO | __main__:connect_to_database:208 - 数据库连接成功 +2025-11-26 22:53:29.436 | INFO | __main__:load_table_list:236 - 加载了 2 个表 +2025-11-26 22:53:36.155 | INFO | __main__:on_table_selected:246 - 选中表: products +2025-11-26 22:53:36.163 | INFO | __main__:load_table_data:282 - 加载表 products 数据完成,共 363 行 +2025-11-26 22:53:36.164 | INFO | __main__:update_field_combo:312 - 更新字段下拉框: products, 共 9 个字段 +2025-11-26 22:54:02.226 | INFO | __main__:closeEvent:404 - 关闭应用程序 diff --git a/product/sqlite_viewer.py b/product/sqlite_viewer.py new file mode 100644 index 0000000..74a4836 --- /dev/null +++ b/product/sqlite_viewer.py @@ -0,0 +1,431 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +SQLite数据库查看器 - 基于PySide6 +功能:打开product目录下的product.db的sqlite文件,显示表和数据的界面 +""" + +import sys +import os +import sqlite3 +from loguru import logger + +from PySide6.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, + QWidget, QPushButton, QTableWidget, QTableWidgetItem, + QListWidget, QListWidgetItem, QSplitter, QFileDialog, + QLabel, QStatusBar, QMessageBox, QHeaderView, QComboBox, + QLineEdit, QGroupBox) +from PySide6.QtCore import Qt +from PySide6.QtGui import QAction + + +class SQLiteViewer(QMainWindow): + """SQLite数据库查看器主窗口""" + + def __init__(self): + super().__init__() + logger.info("初始化SQLite数据库查看器") + self.db_connection = None + self.current_table = None + self.init_ui() + + def init_ui(self): + """初始化用户界面""" + logger.info("设置主窗口界面") + self.setWindowTitle("SQLite数据库查看器") + self.setGeometry(100, 100, 1200, 800) + + # 创建中央部件 + central_widget = QWidget() + self.setCentralWidget(central_widget) + + # 创建主布局 + main_layout = QVBoxLayout(central_widget) + + # 创建顶部按钮布局 + self.create_top_buttons(main_layout) + + # 创建筛选控件区域 + self.create_filter_section(main_layout) + + # 创建分割器(左侧表列表,右侧数据表格) + self.create_splitter(main_layout) + + # 创建状态栏 + self.create_status_bar() + + # 创建菜单栏 + self.create_menubar() + + logger.info("界面初始化完成") + + def create_top_buttons(self, layout): + """创建顶部按钮布局""" + logger.info("创建顶部按钮") + button_layout = QHBoxLayout() + + # 打开数据库按钮 + self.open_button = QPushButton("打开SQLite数据库") + self.open_button.clicked.connect(self.open_database) + button_layout.addWidget(self.open_button) + + # 刷新按钮 + self.refresh_button = QPushButton("刷新") + self.refresh_button.clicked.connect(self.refresh_data) + self.refresh_button.setEnabled(False) + button_layout.addWidget(self.refresh_button) + + # 数据库路径显示 + self.db_path_label = QLabel("未打开数据库") + button_layout.addWidget(self.db_path_label) + + button_layout.addStretch() + layout.addLayout(button_layout) + + def create_filter_section(self, layout): + """创建筛选控件区域""" + logger.info("创建筛选控件区域") + + # 创建筛选分组框 + filter_group = QGroupBox("数据筛选") + filter_layout = QHBoxLayout(filter_group) + + # 字段选择标签 + filter_layout.addWidget(QLabel("筛选字段:")) + + # 字段选择下拉框 + self.field_combo = QComboBox() + self.field_combo.setMinimumWidth(150) + filter_layout.addWidget(self.field_combo) + + # 筛选条件标签 + filter_layout.addWidget(QLabel("筛选条件:")) + + # 筛选条件输入框 + self.filter_input = QLineEdit() + self.filter_input.setPlaceholderText("输入筛选条件,如:name='test' 或 created_at>'2024-01-01'") + self.filter_input.setMinimumWidth(300) + filter_layout.addWidget(self.filter_input) + + # 筛选按钮 + self.filter_button = QPushButton("筛选") + self.filter_button.clicked.connect(self.apply_filter) + filter_layout.addWidget(self.filter_button) + + # 清除筛选按钮 + self.clear_filter_button = QPushButton("清除筛选") + self.clear_filter_button.clicked.connect(self.clear_filter) + self.clear_filter_button.setEnabled(False) + filter_layout.addWidget(self.clear_filter_button) + + filter_layout.addStretch() + + # 初始状态下禁用筛选控件 + self.field_combo.setEnabled(False) + self.filter_input.setEnabled(False) + self.filter_button.setEnabled(False) + + layout.addWidget(filter_group) + + def create_splitter(self, layout): + """创建分割器界面""" + logger.info("创建分割器界面") + splitter = QSplitter(Qt.Horizontal) + + # 左侧:表列表 + left_widget = QWidget() + left_layout = QVBoxLayout(left_widget) + + left_layout.addWidget(QLabel("数据库表列表:")) + self.table_list = QListWidget() + self.table_list.itemClicked.connect(self.on_table_selected) + left_layout.addWidget(self.table_list) + + # 右侧:数据表格 + right_widget = QWidget() + right_layout = QVBoxLayout(right_widget) + + right_layout.addWidget(QLabel("表数据:")) + self.data_table = QTableWidget() + self.data_table.setAlternatingRowColors(True) + right_layout.addWidget(self.data_table) + + splitter.addWidget(left_widget) + splitter.addWidget(right_widget) + splitter.setSizes([300, 900]) + + layout.addWidget(splitter) + + def create_status_bar(self): + """创建状态栏""" + logger.info("创建状态栏") + self.status_bar = QStatusBar() + self.setStatusBar(self.status_bar) + self.status_bar.showMessage("就绪") + + def create_menubar(self): + """创建菜单栏""" + logger.info("创建菜单栏") + menubar = self.menuBar() + + # 文件菜单 + file_menu = menubar.addMenu("文件") + + open_action = QAction("打开数据库", self) + open_action.triggered.connect(self.open_database) + file_menu.addAction(open_action) + + exit_action = QAction("退出", self) + exit_action.triggered.connect(self.close) + file_menu.addAction(exit_action) + + def open_database(self): + """打开SQLite数据库文件""" + logger.info("打开数据库文件对话框") + + # 默认打开product目录下的product.db + default_path = os.path.join('product', 'product.db') + if os.path.exists(default_path): + file_path, _ = QFileDialog.getOpenFileName( + self, "打开SQLite数据库", default_path, "SQLite数据库文件 (*.db *.sqlite *.sqlite3)" + ) + else: + file_path, _ = QFileDialog.getOpenFileName( + self, "打开SQLite数据库", "", "SQLite数据库文件 (*.db *.sqlite *.sqlite3)" + ) + + if file_path: + logger.info(f"打开数据库文件: {file_path}") + self.connect_to_database(file_path) + + def connect_to_database(self, file_path): + """连接到指定的SQLite数据库""" + try: + if self.db_connection: + self.db_connection.close() + + self.db_connection = sqlite3.connect(file_path) + logger.info("数据库连接成功") + + self.db_path_label.setText(f"数据库: {os.path.basename(file_path)}") + self.status_bar.showMessage(f"已连接到数据库: {os.path.basename(file_path)}") + self.refresh_button.setEnabled(True) + + # 加载表列表 + self.load_table_list() + + except sqlite3.Error as e: + logger.error(f"数据库连接失败: {e}") + QMessageBox.critical(self, "错误", f"无法打开数据库: {e}") + + def load_table_list(self): + """加载数据库表列表""" + if not self.db_connection: + return + + try: + cursor = self.db_connection.cursor() + cursor.execute("SELECT name FROM sqlite_master WHERE type='table'") + tables = cursor.fetchall() + + self.table_list.clear() + for table in tables: + item = QListWidgetItem(table[0]) + self.table_list.addItem(item) + + logger.info(f"加载了 {len(tables)} 个表") + self.status_bar.showMessage(f"已加载 {len(tables)} 个表") + + except sqlite3.Error as e: + logger.error(f"加载表列表失败: {e}") + QMessageBox.critical(self, "错误", f"加载表列表失败: {e}") + + def on_table_selected(self, item): + """当表被选中时加载表数据和字段列表""" + table_name = item.text() + logger.info(f"选中表: {table_name}") + self.current_table = table_name + self.load_table_data(table_name) + self.update_field_combo(table_name) + + def load_table_data(self, table_name): + """加载指定表的数据""" + if not self.db_connection: + return + + try: + cursor = self.db_connection.cursor() + + # 获取表结构 + cursor.execute(f"PRAGMA table_info({table_name})") + columns = cursor.fetchall() + column_names = [col[1] for col in columns] + + # 获取数据 + cursor.execute(f"SELECT * FROM {table_name}") + data = cursor.fetchall() + + # 设置表格 + self.data_table.setRowCount(len(data)) + self.data_table.setColumnCount(len(column_names)) + self.data_table.setHorizontalHeaderLabels(column_names) + + # 填充数据 + for row_idx, row_data in enumerate(data): + for col_idx, cell_data in enumerate(row_data): + item = QTableWidgetItem(str(cell_data) if cell_data is not None else "") + self.data_table.setItem(row_idx, col_idx, item) + + # 调整列宽 + self.data_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) + + logger.info(f"加载表 {table_name} 数据完成,共 {len(data)} 行") + self.status_bar.showMessage(f"表 {table_name}: {len(data)} 行数据") + + except sqlite3.Error as e: + logger.error(f"加载表数据失败: {e}") + QMessageBox.critical(self, "错误", f"加载表数据失败: {e}") + + def update_field_combo(self, table_name): + """更新字段选择下拉框""" + if not self.db_connection: + return + + try: + cursor = self.db_connection.cursor() + cursor.execute(f"PRAGMA table_info({table_name})") + columns = cursor.fetchall() + + # 清空当前字段列表 + self.field_combo.clear() + + # 添加所有字段到下拉框 + for column in columns: + field_name = column[1] # 字段名在第二个位置 + self.field_combo.addItem(field_name) + + # 启用筛选控件 + self.field_combo.setEnabled(True) + self.filter_input.setEnabled(True) + self.filter_button.setEnabled(True) + + logger.info(f"更新字段下拉框: {table_name}, 共 {len(columns)} 个字段") + + except sqlite3.Error as e: + logger.error(f"获取表字段信息失败: {e}") + QMessageBox.warning(self, "错误", f"获取表字段信息失败: {e}") + + def apply_filter(self): + """应用筛选条件""" + if not self.db_connection or not self.current_table: + return + + selected_field = self.field_combo.currentText() + filter_condition = self.filter_input.text().strip() + + if not selected_field or not filter_condition: + QMessageBox.warning(self, "警告", "请选择筛选字段并输入筛选条件") + return + + try: + cursor = self.db_connection.cursor() + + # 构建SQL查询语句 + query = f"SELECT * FROM {self.current_table} WHERE {selected_field} LIKE ?" + + # 处理筛选条件(支持模糊匹配) + filter_value = f"%{filter_condition}%" + + # 执行查询 + cursor.execute(query, (filter_value,)) + data = cursor.fetchall() + + # 获取表结构 + cursor.execute(f"PRAGMA table_info({self.current_table})") + columns = cursor.fetchall() + column_names = [col[1] for col in columns] + + # 更新表格显示 + self.data_table.setRowCount(len(data)) + self.data_table.setColumnCount(len(column_names)) + self.data_table.setHorizontalHeaderLabels(column_names) + + # 填充筛选后的数据 + for row_idx, row_data in enumerate(data): + for col_idx, cell_data in enumerate(row_data): + item = QTableWidgetItem(str(cell_data) if cell_data is not None else "") + self.data_table.setItem(row_idx, col_idx, item) + + # 调整列宽 + self.data_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) + + # 启用清除筛选按钮 + self.clear_filter_button.setEnabled(True) + + logger.info(f"应用筛选条件: {selected_field} LIKE '%{filter_condition}%', 匹配到 {len(data)} 行数据") + self.status_bar.showMessage(f"筛选结果: {len(data)} 行数据 (条件: {selected_field} 包含 '{filter_condition}')") + + except sqlite3.Error as e: + logger.error(f"筛选数据失败: {e}") + QMessageBox.critical(self, "错误", f"筛选数据失败: {e}") + + def clear_filter(self): + """清除筛选条件,显示所有数据""" + if not self.current_table: + return + + try: + # 重新加载完整数据 + self.load_table_data(self.current_table) + + # 清空筛选条件 + self.filter_input.clear() + + # 禁用清除筛选按钮 + self.clear_filter_button.setEnabled(False) + + logger.info("清除筛选条件,显示所有数据") + self.status_bar.showMessage("已清除筛选条件,显示所有数据") + + except Exception as e: + logger.error(f"清除筛选失败: {e}") + QMessageBox.critical(self, "错误", f"清除筛选失败: {e}") + + def refresh_data(self): + """刷新当前数据""" + logger.info("刷新数据") + if self.current_table: + self.load_table_data(self.current_table) + else: + self.load_table_list() + + def closeEvent(self, event): + """关闭事件处理""" + logger.info("关闭应用程序") + if self.db_connection: + self.db_connection.close() + event.accept() + + +def main(): + """主函数""" + logger.info("启动SQLite数据库查看器") + + # 配置日志 + logger.add("sqlite_viewer.log", rotation="10 MB", level="INFO") + + app = QApplication(sys.argv) + + # 设置应用程序信息 + app.setApplicationName("SQLite数据库查看器") + app.setApplicationVersion("1.0.0") + + viewer = SQLiteViewer() + viewer.show() + + logger.info("应用程序启动完成") + sys.exit(app.exec()) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/requirements_gui.txt b/requirements_gui.txt new file mode 100644 index 0000000..7996e9b --- /dev/null +++ b/requirements_gui.txt @@ -0,0 +1,8 @@ +# SQLite数据库查看器依赖包 +PySide6>=6.5.0 +loguru>=0.7.0 +pyqt-test>=1.0.0 + +# 可选:用于GUI测试的额外依赖 +pytest>=7.0.0 +pytest-qt>=4.0.0 \ No newline at end of file diff --git a/run_viewer.py b/run_viewer.py new file mode 100644 index 0000000..fee0095 --- /dev/null +++ b/run_viewer.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +SQLite数据库查看器启动脚本 +""" + +import sys +import os +from loguru import logger + +def check_dependencies(): + """检查依赖包""" + missing_deps = [] + + try: + import PySide6 + except ImportError: + missing_deps.append("PySide6") + + try: + import loguru + except ImportError: + missing_deps.append("loguru") + + if missing_deps: + print("❌ 缺少以下依赖包:") + for dep in missing_deps: + print(f" - {dep}") + print("\n请使用以下命令安装:") + print("pip install -r requirements_gui.txt") + return False + + return True + +def main(): + """主函数""" + logger.info("启动SQLite数据库查看器") + + # 检查依赖 + if not check_dependencies(): + sys.exit(1) + + # 导入主程序 + try: + from sqlite_viewer import main as viewer_main + except ImportError as e: + logger.error(f"导入主程序失败: {e}") + print("❌ 无法导入主程序,请检查文件是否存在") + sys.exit(1) + + # 运行主程序 + try: + viewer_main() + except Exception as e: + logger.error(f"程序运行错误: {e}") + print(f"❌ 程序运行错误: {e}") + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/sqlite_viewer.log b/sqlite_viewer.log new file mode 100644 index 0000000..a00f079 --- /dev/null +++ b/sqlite_viewer.log @@ -0,0 +1,17 @@ +2025-11-26 23:10:59.326 | INFO | __main__:__init__:27 - 初始化SQLite数据库查看器 +2025-11-26 23:10:59.327 | INFO | __main__:init_ui:34 - 设置主窗口界面 +2025-11-26 23:10:59.327 | INFO | __main__:create_top_buttons:64 - 创建顶部按钮 +2025-11-26 23:10:59.327 | INFO | __main__:create_filter_section:87 - 创建筛选控件区域 +2025-11-26 23:10:59.334 | INFO | __main__:create_splitter:132 - 创建分割器界面 +2025-11-26 23:10:59.335 | INFO | __main__:create_status_bar:161 - 创建状态栏 +2025-11-26 23:10:59.335 | INFO | __main__:create_menubar:168 - 创建菜单栏 +2025-11-26 23:10:59.344 | INFO | __main__:init_ui:60 - 界面初始化完成 +2025-11-26 23:10:59.494 | INFO | __main__:main:426 - 应用程序启动完成 +2025-11-26 23:11:01.008 | INFO | __main__:open_database:184 - 打开数据库文件对话框 +2025-11-26 23:11:03.377 | INFO | __main__:open_database:198 - 打开数据库文件: C:/Users/xiaji/Documents/个人文件夹/夏骥/hothub的抓取/product/products.db +2025-11-26 23:11:03.378 | INFO | __main__:connect_to_database:208 - 数据库连接成功 +2025-11-26 23:11:03.379 | INFO | __main__:load_table_list:236 - 加载了 3 个表 +2025-11-26 23:11:06.700 | INFO | __main__:on_table_selected:246 - 选中表: product_analysis +2025-11-26 23:11:06.701 | INFO | __main__:load_table_data:282 - 加载表 product_analysis 数据完成,共 5 行 +2025-11-26 23:11:06.701 | INFO | __main__:update_field_combo:312 - 更新字段下拉框: product_analysis, 共 8 个字段 +2025-11-26 23:11:49.497 | INFO | __main__:closeEvent:404 - 关闭应用程序