[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site-settings":3,"footer-friends":48,"post-rag-knowledge-base":63,"related-rag-knowledge-base":112,"nav-rag-knowledge-base":292,"comments-6":304,"ad-article_top":308,"ad-article_bottom":310},{"alipayQr":4,"beianNumber":5,"commentsRequireApproval":6,"contactEmail":7,"copyrightNotice":5,"donationEnabled":6,"emailCodeExpireMinutes":8,"enableRegistration":6,"followBilibiliUrl":9,"followGithubUrl":10,"followQqGroup":11,"followQqQr":12,"followWechatQr":13,"footerHtml":5,"gaTrackingId":5,"heroEnabled":6,"heroSearchPlaceholder":5,"heroSubtitle":14,"heroTitle":15,"homeCategoryTabsCount":16,"homePageSize":17,"homeProjectsCount":18,"homeSpecialsCount":19,"homeToolsCount":18,"icpNumber":20,"inviteDefaultQuota":21,"inviteRequired":22,"logoUrl":23,"mailPasswordChangedBody":24,"mailPasswordChangedSubject":25,"mailRegisterCodeBody":26,"mailRegisterCodeSubject":27,"mailResetCodeBody":28,"mailResetCodeSubject":29,"mailWelcomeBody":30,"mailWelcomeSubject":31,"miniappAppid":32,"miniappEnabled":6,"profileAvatar":33,"profileBio":34,"profileName":35,"profileWechatQr":36,"promoteEnabled":6,"promoteNotice":5,"promoteWechat":5,"requireEmailVerify":6,"sectionArticleEnabled":6,"sectionArticleTitle":37,"sectionProjectsEnabled":6,"sectionProjectsTitle":38,"sectionSpecialEnabled":6,"sectionSpecialTitle":39,"sectionToolsEnabled":6,"sectionToolsTitle":40,"seoAuthor":41,"seoBaiduTongjiId":5,"seoBaiduVerify":5,"seoCanonicalEnabled":6,"seoDescription":42,"seoGoogleVerify":5,"seoKeywords":5,"seoOgImage":23,"seoRobots":5,"seoSitemapEnabled":6,"seoTitle":43,"seoTitleTemplate":44,"showHeroStats":6,"showSidebarDonations":6,"showSidebarFeedback":6,"showSidebarFollowUs":6,"showSidebarProfile":6,"showSidebarTagCloud":6,"siteDescription":42,"siteFavicon":45,"siteName":41,"siteUrl":46,"wechatQr":47},"\u002Fstatic\u002Fuploads\u002Fimages\u002F2026\u002F09\u002F29\u002F1790628031878385634.png","","true","enghin110@qq.com","10","https:\u002F\u002Fspace.bilibili.com\u002F470616503","https:\u002F\u002Fgithub.com\u002Fitwuge","606262780","\u002Fstatic\u002Fuploads\u002Fimages\u002F2026\u002F09\u002F29\u002F1790627647190853446.png","\u002Fstatic\u002Fuploads\u002Fimages\u002F2026\u002F09\u002F29\u002F1790627638856258082.jpg","精选 AI 教程、开源项目与实用工具，助你从入门到精通","探索人工智能的无限可能","6","7","4","3","赣ICP备2023014108号-2","5","false","\u002Fimages\u002Flogo\u002Flogo.png","\u003C!DOCTYPE html>\n\u003Chtml lang=\"zh-CN\">\n\u003Chead>\u003Cmeta charset=\"UTF-8\">\u003Cmeta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\u003C\u002Fhead>\n\u003Cbody style=\"margin:0;padding:24px 0;background:#f1f5f9;font-family:'Microsoft YaHei','PingFang SC',Helvetica,Arial,sans-serif;\">\n  \u003Ctable role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#f1f5f9;\">\n    \u003Ctr>\n      \u003Ctd align=\"center\" style=\"padding:0 16px;\">\n        \u003Ctable role=\"presentation\" width=\"600\" cellpadding=\"0\" cellspacing=\"0\" style=\"max-width:600px;width:100%;\">\n          \u003C!-- 品牌头部 -->\n          \u003Ctr>\n            \u003Ctd style=\"background:#3b82f6;border-radius:12px 12px 0 0;padding:20px 28px;\">\n              \u003Cspan style=\"font-size:18px;font-weight:bold;color:#ffffff;\">\u003Ca href=\"{site_url}\" style=\"color:#ffffff;text-decoration:none;\">{site_name}\u003C\u002Fa>\u003C\u002Fspan>\n              \u003Cspan style=\"float:right;font-size:12px;color:#dbeafe;line-height:26px;\">{site_description}\u003C\u002Fspan>\n            \u003C\u002Ftd>\n          \u003C\u002Ftr>\n          \u003C!-- 正文卡片 -->\n          \u003Ctr>\n            \u003Ctd style=\"background:#ffffff;padding:32px 28px;border-radius:0 0 12px 12px;box-shadow:0 2px 8px rgba(15,23,42,0.06);\">\n              \n  \u003Ch2 style=\"margin:0 0 16px;font-size:20px;color:#1e293b;\">🔒 账户密码修改成功\u003C\u002Fh2>\n  \u003Cp style=\"margin:0 0 16px;font-size:14px;line-height:1.8;color:#475569;\">\n    你好，\u003Cstrong style=\"color:#1e293b;\">{username}\u003C\u002Fstrong>，你的账户密码已于刚刚修改成功。\n  \u003C\u002Fp>\n  \u003Ctable role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#f8fafc;border-radius:10px;margin:8px 0 4px;\">\n    \u003Ctr>\u003Ctd style=\"padding:14px 18px;font-size:13px;line-height:2;color:#475569;\">\n      账户用户名：\u003Cstrong style=\"color:#1e293b;\">{username}\u003C\u002Fstrong>\u003Cbr>\n      账户邮箱：\u003Cstrong style=\"color:#1e293b;\">{email}\u003C\u002Fstrong>\u003Cbr>\n      修改时间：\u003Cstrong style=\"color:#1e293b;\">{change_time}\u003C\u002Fstrong>\n    \u003C\u002Ftd>\u003C\u002Ftr>\n  \u003C\u002Ftable>\n  \u003Cp style=\"margin:16px 0 0;font-size:13px;line-height:1.8;color:#64748b;\">\n    如果这是你本人的操作，请忽略本邮件；\u003Cstrong style=\"color:#dc2626;\">如果不是你本人操作，说明账户密码可能已泄露\u003C\u002Fstrong>，\n    请立即使用「忘记密码」功能重置密码，并通过页脚联系邮箱告知站长。\n  \u003C\u002Fp>\n  \n  \u003Cp style=\"text-align:center;margin:28px 0 8px;\">\n    \u003Ca href=\"{site_url}\" style=\"display:inline-block;padding:12px 36px;background:#3b82f6;color:#ffffff;font-size:15px;font-weight:bold;text-decoration:none;border-radius:8px;\">进入{site_name}\u003C\u002Fa>\n  \u003C\u002Fp>\n            \u003C\u002Ftd>\n          \u003C\u002Ftr>\n          \u003C!-- 页脚 -->\n          \u003Ctr>\n            \u003Ctd align=\"center\" style=\"padding:20px 16px 0;\">\n              \n              \u003Cp style=\"margin:6px 0 0;font-size:12px;color:#94a3b8;\">联系邮箱：\u003Ca href=\"mailto:{contact_email}\" style=\"color:#64748b;text-decoration:none;\">{contact_email}\u003C\u002Fa>\u003C\u002Fp>\n              \u003Cp style=\"margin:6px 0 0;font-size:12px;color:#94a3b8;\">此邮件由系统自动发送，请勿直接回复 · © {year} {site_name}\u003C\u002Fp>\n            \u003C\u002Ftd>\n          \u003C\u002Ftr>\n        \u003C\u002Ftable>\n      \u003C\u002Ftd>\n    \u003C\u002Ftr>\n  \u003C\u002Ftable>\n\u003C\u002Fbody>\n\u003C\u002Fhtml>","【{site_name}】你的账户密码已修改","\u003C!DOCTYPE html>\n\u003Chtml lang=\"zh-CN\">\n\u003Chead>\u003Cmeta charset=\"UTF-8\">\u003Cmeta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\u003C\u002Fhead>\n\u003Cbody style=\"margin:0;padding:24px 0;background:#f1f5f9;font-family:'Microsoft YaHei','PingFang SC',Helvetica,Arial,sans-serif;\">\n  \u003Ctable role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#f1f5f9;\">\n    \u003Ctr>\n      \u003Ctd align=\"center\" style=\"padding:0 16px;\">\n        \u003Ctable role=\"presentation\" width=\"600\" cellpadding=\"0\" cellspacing=\"0\" style=\"max-width:600px;width:100%;\">\n          \u003C!-- 品牌头部 -->\n          \u003Ctr>\n            \u003Ctd style=\"background:#3b82f6;border-radius:12px 12px 0 0;padding:20px 28px;\">\n              \u003Cspan style=\"font-size:18px;font-weight:bold;color:#ffffff;\">\u003Ca href=\"{site_url}\" style=\"color:#ffffff;text-decoration:none;\">{site_name}\u003C\u002Fa>\u003C\u002Fspan>\n              \u003Cspan style=\"float:right;font-size:12px;color:#dbeafe;line-height:26px;\">{site_description}\u003C\u002Fspan>\n            \u003C\u002Ftd>\n          \u003C\u002Ftr>\n          \u003C!-- 正文卡片 -->\n          \u003Ctr>\n            \u003Ctd style=\"background:#ffffff;padding:32px 28px;border-radius:0 0 12px 12px;box-shadow:0 2px 8px rgba(15,23,42,0.06);\">\n              \n  \u003Ch2 style=\"margin:0 0 16px;font-size:20px;color:#1e293b;\">注册邮箱验证码\u003C\u002Fh2>\n  \u003Cp style=\"margin:0 0 16px;font-size:14px;line-height:1.8;color:#475569;\">欢迎注册 \u003Cstrong>{site_name}\u003C\u002Fstrong>！感谢你的加入，请使用下方验证码完成邮箱验证：\u003C\u002Fp>\n  \u003Ctable role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:8px 0 4px;\">\n    \u003Ctr>\n      \u003Ctd align=\"center\" style=\"background:#f8fafc;border:1px dashed #cbd5e1;border-radius:10px;padding:22px 0;\">\n        \u003Cspan style=\"font-size:32px;font-weight:bold;letter-spacing:10px;color:#3b82f6;font-family:Consolas,Menlo,monospace;\">{code}\u003C\u002Fspan>\n      \u003C\u002Ftd>\n    \u003C\u002Ftr>\n  \u003C\u002Ftable>\n  \u003Cp style=\"margin:12px 0 0;font-size:13px;color:#64748b;\">\n    验证码 \u003Cstrong style=\"color:#dc2626;\">{expire} 分钟\u003C\u002Fstrong> 内有效，为保障账户安全，请勿向他人泄露。\n  \u003C\u002Fp>\n  \u003Chr style=\"border:none;border-top:1px solid #e2e8f0;margin:24px 0;\">\n  \n  \u003Cp style=\"margin:0 0 12px;font-size:14px;line-height:1.8;color:#475569;\">\n    这里是 \u003Cstrong style=\"color:#1e293b;\">{site_name}\u003C\u002Fstrong>，{site_description}\n  \u003C\u002Fp>\n  \u003Ctable role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:8px 0 4px;\">\n    \u003Ctr>\n      \u003Ctd width=\"33%\" valign=\"top\" style=\"padding:6px;\">\n        \u003Cdiv style=\"background:#eff6ff;border-radius:8px;padding:14px 10px;text-align:center;\">\n          \u003Cdiv style=\"font-size:15px;\">📚\u003C\u002Fdiv>\n          \u003Cdiv style=\"font-size:13px;font-weight:bold;color:#1e40af;margin-top:4px;\">精选教程\u003C\u002Fdiv>\n          \u003Cdiv style=\"font-size:12px;color:#64748b;margin-top:2px;\">从入门到实战\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Ftd>\n      \u003Ctd width=\"33%\" valign=\"top\" style=\"padding:6px;\">\n        \u003Cdiv style=\"background:#ecfdf5;border-radius:8px;padding:14px 10px;text-align:center;\">\n          \u003Cdiv style=\"font-size:15px;\">🚀\u003C\u002Fdiv>\n          \u003Cdiv style=\"font-size:13px;font-weight:bold;color:#065f46;margin-top:4px;\">开源项目\u003C\u002Fdiv>\n          \u003Cdiv style=\"font-size:12px;color:#64748b;margin-top:2px;\">精选可部署方案\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Ftd>\n      \u003Ctd width=\"34%\" valign=\"top\" style=\"padding:6px;\">\n        \u003Cdiv style=\"background:#fff7ed;border-radius:8px;padding:14px 10px;text-align:center;\">\n          \u003Cdiv style=\"font-size:15px;\">🧰\u003C\u002Fdiv>\n          \u003Cdiv style=\"font-size:13px;font-weight:bold;color:#9a3412;margin-top:4px;\">在线工具\u003C\u002Fdiv>\n          \u003Cdiv style=\"font-size:12px;color:#64748b;margin-top:2px;\">即开即用提效\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Ftd>\n    \u003C\u002Ftr>\n  \u003C\u002Ftable>\n  \u003Cp style=\"margin:20px 0 0;font-size:12px;line-height:1.8;color:#94a3b8;\">\n    如果这不是你本人的操作，请忽略本邮件，你的账户安全不会受到影响。\n  \u003C\u002Fp>\n            \u003C\u002Ftd>\n          \u003C\u002Ftr>\n          \u003C!-- 页脚 -->\n          \u003Ctr>\n            \u003Ctd align=\"center\" style=\"padding:20px 16px 0;\">\n              \n              \u003Cp style=\"margin:6px 0 0;font-size:12px;color:#94a3b8;\">联系邮箱：\u003Ca href=\"mailto:{contact_email}\" style=\"color:#64748b;text-decoration:none;\">{contact_email}\u003C\u002Fa>\u003C\u002Fp>\n              \u003Cp style=\"margin:6px 0 0;font-size:12px;color:#94a3b8;\">此邮件由系统自动发送，请勿直接回复 · © {year} {site_name}\u003C\u002Fp>\n            \u003C\u002Ftd>\n          \u003C\u002Ftr>\n        \u003C\u002Ftable>\n      \u003C\u002Ftd>\n    \u003C\u002Ftr>\n  \u003C\u002Ftable>\n\u003C\u002Fbody>\n\u003C\u002Fhtml>","【{site_name}】注册验证码","\u003C!DOCTYPE html>\n\u003Chtml lang=\"zh-CN\">\n\u003Chead>\u003Cmeta charset=\"UTF-8\">\u003Cmeta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\u003C\u002Fhead>\n\u003Cbody style=\"margin:0;padding:24px 0;background:#f1f5f9;font-family:'Microsoft YaHei','PingFang SC',Helvetica,Arial,sans-serif;\">\n  \u003Ctable role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#f1f5f9;\">\n    \u003Ctr>\n      \u003Ctd align=\"center\" style=\"padding:0 16px;\">\n        \u003Ctable role=\"presentation\" width=\"600\" cellpadding=\"0\" cellspacing=\"0\" style=\"max-width:600px;width:100%;\">\n          \u003C!-- 品牌头部 -->\n          \u003Ctr>\n            \u003Ctd style=\"background:#3b82f6;border-radius:12px 12px 0 0;padding:20px 28px;\">\n              \u003Cspan style=\"font-size:18px;font-weight:bold;color:#ffffff;\">\u003Ca href=\"{site_url}\" style=\"color:#ffffff;text-decoration:none;\">{site_name}\u003C\u002Fa>\u003C\u002Fspan>\n              \u003Cspan style=\"float:right;font-size:12px;color:#dbeafe;line-height:26px;\">{site_description}\u003C\u002Fspan>\n            \u003C\u002Ftd>\n          \u003C\u002Ftr>\n          \u003C!-- 正文卡片 -->\n          \u003Ctr>\n            \u003Ctd style=\"background:#ffffff;padding:32px 28px;border-radius:0 0 12px 12px;box-shadow:0 2px 8px rgba(15,23,42,0.06);\">\n              \n  \u003Ch2 style=\"margin:0 0 16px;font-size:20px;color:#1e293b;\">找回密码验证码\u003C\u002Fh2>\n  \u003Cp style=\"margin:0 0 16px;font-size:14px;line-height:1.8;color:#475569;\">你正在找回账户密码，请使用下方验证码完成重置操作：\u003C\u002Fp>\n  \u003Ctable role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:8px 0 4px;\">\n    \u003Ctr>\n      \u003Ctd align=\"center\" style=\"background:#f8fafc;border:1px dashed #cbd5e1;border-radius:10px;padding:22px 0;\">\n        \u003Cspan style=\"font-size:32px;font-weight:bold;letter-spacing:10px;color:#3b82f6;font-family:Consolas,Menlo,monospace;\">{code}\u003C\u002Fspan>\n      \u003C\u002Ftd>\n    \u003C\u002Ftr>\n  \u003C\u002Ftable>\n  \u003Cp style=\"margin:12px 0 0;font-size:13px;color:#64748b;\">\n    验证码 \u003Cstrong style=\"color:#dc2626;\">{expire} 分钟\u003C\u002Fstrong> 内有效，为保障账户安全，请勿向他人泄露。\n  \u003C\u002Fp>\n  \u003Chr style=\"border:none;border-top:1px solid #e2e8f0;margin:24px 0;\">\n  \n  \u003Cp style=\"margin:0 0 12px;font-size:14px;line-height:1.8;color:#475569;\">\n    这里是 \u003Cstrong style=\"color:#1e293b;\">{site_name}\u003C\u002Fstrong>，{site_description}\n  \u003C\u002Fp>\n  \u003Ctable role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:8px 0 4px;\">\n    \u003Ctr>\n      \u003Ctd width=\"33%\" valign=\"top\" style=\"padding:6px;\">\n        \u003Cdiv style=\"background:#eff6ff;border-radius:8px;padding:14px 10px;text-align:center;\">\n          \u003Cdiv style=\"font-size:15px;\">📚\u003C\u002Fdiv>\n          \u003Cdiv style=\"font-size:13px;font-weight:bold;color:#1e40af;margin-top:4px;\">精选教程\u003C\u002Fdiv>\n          \u003Cdiv style=\"font-size:12px;color:#64748b;margin-top:2px;\">从入门到实战\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Ftd>\n      \u003Ctd width=\"33%\" valign=\"top\" style=\"padding:6px;\">\n        \u003Cdiv style=\"background:#ecfdf5;border-radius:8px;padding:14px 10px;text-align:center;\">\n          \u003Cdiv style=\"font-size:15px;\">🚀\u003C\u002Fdiv>\n          \u003Cdiv style=\"font-size:13px;font-weight:bold;color:#065f46;margin-top:4px;\">开源项目\u003C\u002Fdiv>\n          \u003Cdiv style=\"font-size:12px;color:#64748b;margin-top:2px;\">精选可部署方案\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Ftd>\n      \u003Ctd width=\"34%\" valign=\"top\" style=\"padding:6px;\">\n        \u003Cdiv style=\"background:#fff7ed;border-radius:8px;padding:14px 10px;text-align:center;\">\n          \u003Cdiv style=\"font-size:15px;\">🧰\u003C\u002Fdiv>\n          \u003Cdiv style=\"font-size:13px;font-weight:bold;color:#9a3412;margin-top:4px;\">在线工具\u003C\u002Fdiv>\n          \u003Cdiv style=\"font-size:12px;color:#64748b;margin-top:2px;\">即开即用提效\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Ftd>\n    \u003C\u002Ftr>\n  \u003C\u002Ftable>\n  \u003Cp style=\"margin:20px 0 0;font-size:12px;line-height:1.8;color:#94a3b8;\">\n    如果这不是你本人的操作，请忽略本邮件，你的账户安全不会受到影响。\n  \u003C\u002Fp>\n            \u003C\u002Ftd>\n          \u003C\u002Ftr>\n          \u003C!-- 页脚 -->\n          \u003Ctr>\n            \u003Ctd align=\"center\" style=\"padding:20px 16px 0;\">\n              \n              \u003Cp style=\"margin:6px 0 0;font-size:12px;color:#94a3b8;\">联系邮箱：\u003Ca href=\"mailto:{contact_email}\" style=\"color:#64748b;text-decoration:none;\">{contact_email}\u003C\u002Fa>\u003C\u002Fp>\n              \u003Cp style=\"margin:6px 0 0;font-size:12px;color:#94a3b8;\">此邮件由系统自动发送，请勿直接回复 · © {year} {site_name}\u003C\u002Fp>\n            \u003C\u002Ftd>\n          \u003C\u002Ftr>\n        \u003C\u002Ftable>\n      \u003C\u002Ftd>\n    \u003C\u002Ftr>\n  \u003C\u002Ftable>\n\u003C\u002Fbody>\n\u003C\u002Fhtml>","【{site_name}】找回密码验证码","\u003C!DOCTYPE html>\n\u003Chtml lang=\"zh-CN\">\n\u003Chead>\u003Cmeta charset=\"UTF-8\">\u003Cmeta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\u003C\u002Fhead>\n\u003Cbody style=\"margin:0;padding:24px 0;background:#f1f5f9;font-family:'Microsoft YaHei','PingFang SC',Helvetica,Arial,sans-serif;\">\n  \u003Ctable role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#f1f5f9;\">\n    \u003Ctr>\n      \u003Ctd align=\"center\" style=\"padding:0 16px;\">\n        \u003Ctable role=\"presentation\" width=\"600\" cellpadding=\"0\" cellspacing=\"0\" style=\"max-width:600px;width:100%;\">\n          \u003C!-- 品牌头部 -->\n          \u003Ctr>\n            \u003Ctd style=\"background:#3b82f6;border-radius:12px 12px 0 0;padding:20px 28px;\">\n              \u003Cspan style=\"font-size:18px;font-weight:bold;color:#ffffff;\">\u003Ca href=\"{site_url}\" style=\"color:#ffffff;text-decoration:none;\">{site_name}\u003C\u002Fa>\u003C\u002Fspan>\n              \u003Cspan style=\"float:right;font-size:12px;color:#dbeafe;line-height:26px;\">{site_description}\u003C\u002Fspan>\n            \u003C\u002Ftd>\n          \u003C\u002Ftr>\n          \u003C!-- 正文卡片 -->\n          \u003Ctr>\n            \u003Ctd style=\"background:#ffffff;padding:32px 28px;border-radius:0 0 12px 12px;box-shadow:0 2px 8px rgba(15,23,42,0.06);\">\n              \n  \u003Ch2 style=\"margin:0 0 4px;font-size:20px;color:#1e293b;\">🎉 欢迎加入 {site_name}！\u003Cspan style=\"display:inline-block;margin-left:8px;padding:2px 8px;background:#dcfce7;color:#15803d;font-size:12px;border-radius:10px;vertical-align:middle;\">邮箱已验证\u003C\u002Fspan>\u003C\u002Fh2>\n  \u003Cp style=\"margin:0 0 16px;font-size:14px;line-height:1.8;color:#475569;\">\n    你好，\u003Cstrong style=\"color:#1e293b;\">{username}\u003C\u002Fstrong>，你的账户已注册成功。\n  \u003C\u002Fp>\n  \u003Ctable role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#f8fafc;border-radius:10px;margin:8px 0 4px;\">\n    \u003Ctr>\u003Ctd style=\"padding:14px 18px;font-size:13px;line-height:2;color:#475569;\">\n      账户用户名：\u003Cstrong style=\"color:#1e293b;\">{username}\u003C\u002Fstrong>\u003Cbr>\n      注册邮箱：\u003Cstrong style=\"color:#1e293b;\">{email}\u003C\u002Fstrong>\u003Cbr>\n      注册奖励：\u003Cstrong style=\"color:#dc2626;\">+{register_points} 积分\u003C\u002Fstrong>已发放\n    \u003C\u002Ftd>\u003C\u002Ftr>\n  \u003C\u002Ftable>\n  \u003Chr style=\"border:none;border-top:1px solid #e2e8f0;margin:24px 0;\">\n  \n  \u003Cp style=\"margin:0 0 12px;font-size:14px;line-height:1.8;color:#475569;\">\n    这里是 \u003Cstrong style=\"color:#1e293b;\">{site_name}\u003C\u002Fstrong>，{site_description}\n  \u003C\u002Fp>\n  \u003Ctable role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:8px 0 4px;\">\n    \u003Ctr>\n      \u003Ctd width=\"33%\" valign=\"top\" style=\"padding:6px;\">\n        \u003Cdiv style=\"background:#eff6ff;border-radius:8px;padding:14px 10px;text-align:center;\">\n          \u003Cdiv style=\"font-size:15px;\">📚\u003C\u002Fdiv>\n          \u003Cdiv style=\"font-size:13px;font-weight:bold;color:#1e40af;margin-top:4px;\">精选教程\u003C\u002Fdiv>\n          \u003Cdiv style=\"font-size:12px;color:#64748b;margin-top:2px;\">从入门到实战\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Ftd>\n      \u003Ctd width=\"33%\" valign=\"top\" style=\"padding:6px;\">\n        \u003Cdiv style=\"background:#ecfdf5;border-radius:8px;padding:14px 10px;text-align:center;\">\n          \u003Cdiv style=\"font-size:15px;\">🚀\u003C\u002Fdiv>\n          \u003Cdiv style=\"font-size:13px;font-weight:bold;color:#065f46;margin-top:4px;\">开源项目\u003C\u002Fdiv>\n          \u003Cdiv style=\"font-size:12px;color:#64748b;margin-top:2px;\">精选可部署方案\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Ftd>\n      \u003Ctd width=\"34%\" valign=\"top\" style=\"padding:6px;\">\n        \u003Cdiv style=\"background:#fff7ed;border-radius:8px;padding:14px 10px;text-align:center;\">\n          \u003Cdiv style=\"font-size:15px;\">🧰\u003C\u002Fdiv>\n          \u003Cdiv style=\"font-size:13px;font-weight:bold;color:#9a3412;margin-top:4px;\">在线工具\u003C\u002Fdiv>\n          \u003Cdiv style=\"font-size:12px;color:#64748b;margin-top:2px;\">即开即用提效\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Ftd>\n    \u003C\u002Ftr>\n  \u003C\u002Ftable>\n  \u003Cp style=\"margin:16px 0 0;font-size:14px;line-height:1.8;color:#475569;\">登录后你可以：\u003C\u002Fp>\n  \u003Cul style=\"margin:8px 0 0;padding-left:22px;font-size:14px;line-height:2;color:#475569;\">\n    \u003Cli>浏览、收藏优质内容与实用工具\u003C\u002Fli>\n    \u003Cli>投稿教程与项目，审核通过后获得积分奖励\u003C\u002Fli>\n    \u003Cli>参与评论、打分，与其他用户交流\u003C\u002Fli>\n    \u003Cli>通过签到、投稿、互动持续累积积分与等级\u003C\u002Fli>\n  \u003C\u002Ful>\n  \n  \u003Cp style=\"text-align:center;margin:28px 0 8px;\">\n    \u003Ca href=\"{site_url}\" style=\"display:inline-block;padding:12px 36px;background:#3b82f6;color:#ffffff;font-size:15px;font-weight:bold;text-decoration:none;border-radius:8px;\">进入{site_name}\u003C\u002Fa>\n  \u003C\u002Fp>\n            \u003C\u002Ftd>\n          \u003C\u002Ftr>\n          \u003C!-- 页脚 -->\n          \u003Ctr>\n            \u003Ctd align=\"center\" style=\"padding:20px 16px 0;\">\n              \n              \u003Cp style=\"margin:6px 0 0;font-size:12px;color:#94a3b8;\">联系邮箱：\u003Ca href=\"mailto:{contact_email}\" style=\"color:#64748b;text-decoration:none;\">{contact_email}\u003C\u002Fa>\u003C\u002Fp>\n              \u003Cp style=\"margin:6px 0 0;font-size:12px;color:#94a3b8;\">此邮件由系统自动发送，请勿直接回复 · © {year} {site_name}\u003C\u002Fp>\n            \u003C\u002Ftd>\n          \u003C\u002Ftr>\n        \u003C\u002Ftable>\n      \u003C\u002Ftd>\n    \u003C\u002Ftr>\n  \u003C\u002Ftable>\n\u003C\u002Fbody>\n\u003C\u002Fhtml>","【{site_name}】欢迎加入{site_name}，账户注册成功","7111479@qq.com","\u002Fstatic\u002Fuploads\u002Fimages\u002F2026\u002F09\u002F29\u002F1790627623420492239.jpg","全栈开发者，专注 AI 应用与效率工具","武哥","\u002Fstatic\u002Fuploads\u002Fimages\u002F2026\u002F09\u002F29\u002F1790629453503799753.png","热门爆款","精选项目","精选专题","精选工具","AI导航工具站","精选优质 AI 教程、AI 工具与开源项目，助力开发者快速上手人工智能","AI导航工具站 - AI 教程 · AI 项目 · AI 工具","%s - AI导航工具站","\u002Fimages\u002Flogo\u002Ffavicon.ico","https:\u002F\u002Fwuge.fun","\u002Fstatic\u002Fuploads\u002Fimages\u002F2026\u002F09\u002F29\u002F1790628026196296604.png",[49,58],{"id":50,"name":51,"url":52,"logo":53,"description":51,"sortOrder":54,"status":55,"createdAt":56,"updatedAt":57},2,"哔哩哔哩","https:\u002F\u002Fwww.bilibili.com\u002F","\\images\\social\\bilibili.png",0,1,"0001-01-01T00:00:00Z","2026-09-29T04:44:41.867019475+08:00",{"id":55,"name":59,"url":60,"logo":61,"description":59,"sortOrder":54,"status":55,"createdAt":62,"updatedAt":62},"GitHub","https:\u002F\u002Fgithub.com","\\images\\social\\github.png","2026-09-29T04:32:39.65740234+08:00",{"id":64,"title":65,"slug":66,"summary":67,"content":68,"coverImage":69,"categoryId":70,"authorId":55,"viewCount":54,"likeCount":54,"commentCount":54,"isTop":54,"isRecommend":54,"status":55,"rejectReason":5,"publishedAt":71,"createdAt":72,"updatedAt":73,"category":74,"tags":79,"author":105,"wordCount":111,"readingTime":50},6,"RAG 实战：从零搭建一个本地知识库问答系统","rag-knowledge-base","大模型不知道你的私有文档怎么办？用 RAG 技术把本地文档喂给 AI。从原理到代码，手把手用 Python + LangChain 搭建一个可运行的知识库问答系统。","## 为什么需要 RAG\n\n大模型有两个天然的短板：\n\n1. **知识截止**：训练数据有截止日期，之后的事它不知道\n2. **不懂私有信息**：你公司的内部文档、你的产品手册，它从没见过\n\n把全部文档塞进提示词？不行——一份几百页的手册远超上下文窗口限制，而且费用爆炸。\n\n**RAG（检索增强生成）** 的思路很朴素：**先从文档库里找出相关的几段，再让 AI 基于这几段内容回答**。相当于开卷考试——AI 不需要背下整本书，只要会查资料就行。\n\n## RAG 工作原理拆解\n\n整个系统分两个阶段：\n\n**入库阶段（离线做一次）**：\n\n```text\n文档 → 切分成小块 → 每块算一个向量（Embedding）→ 存进向量数据库\n```\n\n**问答阶段（每次提问）**：\n\n```text\n用户问题 → 算问题的向量 → 在向量库里找最相似的几个文档块\n→ 把「问题 + 找到的文档块」一起发给大模型 → 生成回答\n```\n\n核心概念只有三个：\n\n- **Embedding（向量嵌入）**：把一段文字变成一串数字（如 1536 维的向量），语义相近的文字向量距离也近。「猫」和「猫咪」的向量很近，「猫」和「汽车」很远\n- **向量数据库**：专门存向量并支持「找最相似」查询的数据库，轻量级的有 Chroma、FAISS\n- **检索召回**：根据问题找出最相关的 top-k 个文档块（一般取 3-5 个）\n\n## 环境准备\n\n```bash\npip install langchain langchain-community langchain-text-splitters \\\n    chromadb sentence-transformers openai\n```\n\n说明：\n\n- `sentence-transformers`：本地跑 Embedding，免费且不用调 API\n- `chromadb`：轻量向量数据库，数据存本地文件，零配置\n- `openai`：调用大模型（可换成 DeepSeek 等兼容接口，下文以 DeepSeek 为例，便宜）\n\n## 完整代码（约 60 行，可直接运行）\n\n### 第一步：文档入库\n\n```python\n# build_kb.py —— 把 docs\u002F 目录下的所有 txt\u002Fmd 文档存入向量库\nfrom pathlib import Path\n\nfrom langchain_community.vectorstores import Chroma\nfrom langchain_community.embeddings import HuggingFaceEmbeddings\nfrom langchain_text_splitters import RecursiveCharacterTextSplitter\n\n# 1. 读取本地文档\ndocs_dir = Path(\"docs\")  # 把你的 txt\u002Fmd 文件放这个目录\ntexts, metadatas = [], []\nfor f in docs_dir.rglob(\"*\"):\n    if f.suffix in (\".txt\", \".md\"):\n        texts.append(f.read_text(encoding=\"utf-8\"))\n        metadatas.append({\"source\": f.name})\n\n# 2. 切分文档：每块约 500 字，块间重叠 50 字防止切断语义\nsplitter = RecursiveCharacterTextSplitter(\n    chunk_size=500, chunk_overlap=50, separators=[\"\\n\\n\", \"\\n\", \"。\", \"！\", \"？\"]\n)\nall_chunks = []\nfor text, meta in zip(texts, metadatas):\n    for chunk in splitter.split_text(text):\n        all_chunks.append((chunk, meta))\n\n# 3. 加载本地 Embedding 模型（首次运行自动下载，约 400MB）\nembeddings = HuggingFaceEmbeddings(model_name=\"BAAI\u002Fbge-small-zh-v1.5\")\n\n# 4. 写入向量数据库（持久化到 .\u002Fchroma_db 目录）\ndb = Chroma(\n    collection_name=\"my_kb\",\n    embedding_function=embeddings,\n    persist_directory=\".\u002Fchroma_db\",\n)\ndb.add_texts(\n    texts=[c for c, _ in all_chunks],\n    metadatas=[m for _, m in all_chunks],\n)\nprint(f\"入库完成，共 {len(all_chunks)} 个文档块\")\n```\n\n运行一次即可，之后问答不需要重新入库。\n\n### 第二步：问答\n\n```python\n# ask.py —— 基于知识库回答问题\nfrom openai import OpenAI\nfrom langchain_community.vectorstores import Chroma\nfrom langchain_community.embeddings import HuggingFaceEmbeddings\n\n# 加载已有的向量库\nembeddings = HuggingFaceEmbeddings(model_name=\"BAAI\u002Fbge-small-zh-v1.5\")\ndb = Chroma(\n    collection_name=\"my_kb\",\n    embedding_function=embeddings,\n    persist_directory=\".\u002Fchroma_db\",\n)\n\n# DeepSeek 的兼容 OpenAI 接口（换成你自己的 key）\nclient = OpenAI(\n    api_key=\"sk-你的key\",\n    base_url=\"https:\u002F\u002Fapi.deepseek.com\",\n)\n\ndef ask(question: str) -> str:\n    # 1. 检索：找最相关的 4 个文档块\n    docs = db.similarity_search(question, k=4)\n    context = \"\\n\\n\".join(\n        f\"【来源：{d.metadata.get('source', '未知')}】\\n{d.page_content}\"\n        for d in docs\n    )\n\n    # 2. 组装提示词：把检索结果作为参考资料\n    prompt = f\"\"\"请仅根据下面的参考资料回答问题。\n如果资料里没有相关信息，直接回答「知识库中没有相关内容」，不要编造。\n\n【参考资料】\n{context}\n\n【问题】{question}\n\n回答时请注明信息来源文件名。\"\"\"\n\n    # 3. 调用大模型生成回答\n    resp = client.chat.completions.create(\n        model=\"deepseek-chat\",\n        messages=[{\"role\": \"user\", \"content\": prompt}],\n        temperature=0.1,  # 低温减少发挥，让答案更贴资料\n    )\n    return resp.choices[0].message.content\n\nif __name__ == \"__main__\":\n    while True:\n        q = input(\"\\n请输入问题（q 退出）：\").strip()\n        if q.lower() == \"q\":\n            break\n        print(\"\\n\" + ask(q))\n```\n\n## 实测效果\n\n把本站的 40 篇 AI 工具介绍文档放进 `docs\u002F` 目录入库后：\n\n- 问「有哪些免费的 AI 绘画工具？」→ 回答准确列出免费工具并注明来源文件\n- 问「Midjourney 多少钱一个月？」→ 从文档里找到价格信息作答\n- 问「怎么申请高新技术企业认定？」→ 如实回答「知识库中没有相关内容」——这正是我们要的效果，**宁可说不知道，也不许瞎编**\n\n## 调优方向（效果不满意时看这里）\n\n**检索不准** → 调切分策略：`chunk_size` 太小会切断语义，太大会稀释相关性，500 字左右通常合适；也可以换更强的 Embedding 模型（如 `bge-large-zh-v1.5`）。\n\n**回答啰嗦\u002F跑题** → 调提示词：强调「仅根据参考资料」；把 `temperature` 降到 0。\n\n**召回的内容不全** → 把 `k=4` 调大到 6-8，注意别超过模型上下文。\n\n**想支持 PDF\u002FWord** → 用 `langchain-community` 的文档加载器：\n\n```python\nfrom langchain_community.document_loaders import PyPDFLoader, Docx2txtLoader\n\ndocs = PyPDFLoader(\"手册.pdf\").load()        # 加载 PDF\ndocs = Docx2txtLoader(\"文档.docx\").load()    # 加载 Word\n```\n\n## 生产环境还需要什么\n\n上面的 60 行代码是「能跑的 demo」，真正上线还要考虑：\n\n- **权限隔离**：不同用户只能检索自己有权限的文档（按 metadata 过滤）\n- **增量更新**：文档变更后只更新对应块，不要全量重建\n- **混合检索**：向量检索 + 关键词检索（BM25）融合，兼顾语义和精确匹配\n- **评估体系**：准备一批标准问答对，量化每次调优的效果\n\n但对个人知识库、小团队内部问答场景，本文这套方案已经够用。把代码跑起来，喂给它你的第一批文档吧。\n","\u002Fstatic\u002Fuploads\u002Fimages\u002F2026\u002F09\u002F29\u002F1790635573941737564.png",4,"2026-09-29T06:46:36.332298489+08:00","2026-09-29T06:46:13.941901418+08:00","2026-09-29T06:46:36.332355916+08:00",{"id":70,"name":75,"slug":76,"description":5,"icon":77,"sortOrder":70,"postCount":54,"parentId":54,"createdAt":78,"updatedAt":78},"机器学习","machine-learning","i-ri-bar-chart-box-line","2026-09-29T04:25:29.69293374+08:00",[80,85,90,95,100],{"id":64,"name":81,"slug":82,"color":83,"postCount":54,"createdAt":84},"Python","python","#e2e8f0","2026-09-29T04:25:30.347030306+08:00",{"id":86,"name":87,"slug":88,"color":83,"postCount":54,"createdAt":89},25,"RAG","rag","2026-09-29T04:25:30.487589815+08:00",{"id":91,"name":92,"slug":93,"color":83,"postCount":54,"createdAt":94},101,"LangChain","langchain","2026-09-29T06:46:13.916898801+08:00",{"id":96,"name":97,"slug":98,"color":83,"postCount":54,"createdAt":99},102,"向量数据库","xiang-liang-shu-ju-ku","2026-09-29T06:46:13.925847319+08:00",{"id":101,"name":102,"slug":103,"color":83,"postCount":54,"createdAt":104},103,"Embedding","embedding","2026-09-29T06:46:13.931530378+08:00",{"id":55,"username":106,"nickname":107,"avatarUrl":108,"bio":5,"rewardWechatQr":109,"rewardAlipayQr":110},"wuge","站长","\u002Fstatic\u002Fuploads\u002Favatars\u002F2026\u002F09\u002F29\u002F1790630804049538805.jpg","\u002Fstatic\u002Fuploads\u002Fimages\u002F2026\u002F09\u002F29\u002F1790630754672518549.png","\u002Fstatic\u002Fuploads\u002Fimages\u002F2026\u002F09\u002F29\u002F1790630762294788382.png",413,[113,145,177,204,232,264],{"id":114,"title":115,"slug":116,"summary":117,"content":118,"coverImage":119,"categoryId":120,"authorId":55,"viewCount":55,"likeCount":54,"commentCount":54,"isTop":54,"isRecommend":54,"status":55,"rejectReason":5,"publishedAt":121,"createdAt":122,"updatedAt":123,"category":124,"tags":127},11,"Python 虚拟环境完全指南：venv、conda、uv 怎么选","venv","为什么每个 Python 项目都需要虚拟环境？venv、conda、uv 三大方案深度对比，附常见坑排查清单，看完彻底告别依赖地狱。","## 为什么需要虚拟环境\n\n假设你同时在开发两个项目：\n\n- 项目 A 是去年写的，依赖 `Django 3.2`\n- 项目 B 是新项目，要用 `Django 5.0`\n\nPython 默认把第三方包装在**同一个全局目录**里。装了 Django 5.0，项目 A 直接跑不起来；退回 3.2，项目 B 又报错。这就是「依赖地狱」。\n\n虚拟环境的解决思路很直接：**给每个项目造一个独立的 Python 运行空间**，各装各的包，互不干扰。\n\n```text\n电脑\n├── 全局 Python 3.12\n├── 项目A\u002Fvenv\u002F   ← Django 3.2\n├── 项目B\u002Fvenv\u002F   ← Django 5.0\n└── 项目C\u002Fvenv\u002F   ← 一堆 AI 库\n```\n\n## 方案一：venv（官方内置，默认首选）\n\nPython 3.3+ 自带，零安装，适合 90% 的纯 Python 项目。\n\n**创建与激活**：\n\n```bash\n# 在项目目录下创建虚拟环境（目录名约定俗成叫 .venv 或 venv）\npython -m venv .venv\n\n# Windows 激活\n.venv\\Scripts\\activate\n\n# macOS\u002FLinux 激活\nsource .venv\u002Fbin\u002Factivate\n```\n\n激活成功的标志：命令行提示符前面出现 `(.venv)`。\n\n**日常使用**：\n\n```bash\n# 装包（装在虚拟环境里，不污染全局）\npip install requests pandas\n\n# 导出依赖清单（项目交接、部署时用）\npip freeze > requirements.txt\n\n# 别人拿到项目后还原环境\npip install -r requirements.txt\n\n# 退出虚拟环境\ndeactivate\n```\n\n**优点**：官方标准、零依赖、任何 Python 环境都有。\n\n**缺点**：只能管理 Python 包，管不了 CUDA、MySQL 客户端这类系统级依赖；不能切换 Python 版本（venv 创建时用什么版本就是什么版本）。\n\n## 方案二：conda（数据科学标配）\n\nAnaconda\u002FMiniconda 提供的环境管理器，**不仅能管 Python 包，还能管 Python 解释器本身和非 Python 依赖**。\n\n**常用命令**：\n\n```bash\n# 创建环境，同时指定 Python 版本\nconda create -n myproject python=3.11\n\n# 激活 \u002F 退出\nconda activate myproject\nconda deactivate\n\n# 装包\nconda install numpy pandas\n# conda 仓库没有的包，可以混用 pip\npip install some-package\n\n# 导出与还原\nconda env export > environment.yml\nconda env create -f environment.yml\n\n# 查看所有环境 \u002F 删除环境\nconda env list\nconda remove -n myproject --all\n```\n\n**建议装 Miniconda 而不是 Anaconda**：Anaconda 预装了几百个包，体积 3G+；Miniconda 只有核心，按需安装。\n\n**什么时候必须选 conda**：需要特定版本的 CUDA\u002FcuDNN（深度学习训练）、需要 GDAL 这类难装的地理信息库、需要在同一台机器维护多个 Python 版本。\n\n## 方案三：uv（2024 年后的新王）\n\nAstral 公司（ruff 的作者）用 Rust 写的包管理器，**速度比 pip 快 10-100 倍**，正在迅速成为新标准。\n\n```bash\n# 安装 uv（Windows PowerShell）\npowershell -c \"irm https:\u002F\u002Fastral.sh\u002Fuv\u002Finstall.ps1 | iex\"\n\n# 创建项目（自动生成 .venv、pyproject.toml）\nuv init myproject\ncd myproject\n\n# 装包（秒装，第一次用会惊到）\nuv add requests pandas\n\n# 运行脚本（自动用项目环境，不用手动激活）\nuv run main.py\n\n# 锁定依赖（生成 uv.lock，保证全团队环境一致）\nuv lock\n```\n\nuv 最大的改进是**把 venv、pip、pyproject.toml、锁定文件整合成一套连贯的工作流**，以前要四五个工具配合的事，现在一个命令搞定。\n\n## 怎么选：一张决策表\n\n| 你的情况 | 推荐 |\n|---------|------|\n| 纯 Python 项目，图省心 | venv |\n| 深度学习\u002F数据科学，要 CUDA | conda |\n| 新项目，追求速度和现代工作流 | uv |\n| 团队协作、要锁定依赖版本 | uv |\n| 老旧项目维护 | 跟着项目原来的方案走 |\n\n**新手建议**：从 venv 学起理解原理，新项目直接上 uv，碰到 CUDA 相关的再上 conda。三者概念相通，切换成本很低。\n\n## 常见坑排查清单\n\n**1. 装了包却提示 ModuleNotFoundError**\n\n90% 的原因是：包装在了 A 环境，程序却在 B 环境运行。排查：\n\n```bash\n# 看当前 python 在哪\npython -c \"import sys; print(sys.executable)\"\n\n# 看包装在了哪\npip show 包名 | findstr Location\n```\n\n两个路径对不上就是环境问题。\n\n**2. Windows 激活脚本报错「禁止运行脚本」**\n\nPowerShell 执行策略限制，管理员身份运行一次：\n\n```powershell\nSet-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser\n```\n\n**3. 虚拟环境目录要不要提交 Git**\n\n**不要**。`.venv\u002F` 动辄几百 MB，应该加进 `.gitignore`，用 `requirements.txt` 或 `uv.lock` 传递依赖信息。\n\n**4. IDE 里代码飘红但命令行能跑**\n\nIDE 用的解释器和命令行不是同一个。VS Code 按 `Ctrl+Shift+P` → `Python: Select Interpreter`，选项目 `.venv` 里的那个。\n\n**5. requirements.txt 写成一团乱**\n\n手动维护比 `pip freeze` 更好——freeze 会把所有间接依赖都写进去。推荐只写直接依赖：\n\n```text\nrequests>=2.31\npandas>=2.0\n```\n\n## 写在最后\n\n虚拟环境是 Python 工程化的第一课。养成习惯：**每个新项目第一件事就是建虚拟环境**。项目越多的老司机越清楚——今天你省下的 30 秒，就是明天某次神秘报错时要还的债。\n","\u002Fstatic\u002Fuploads\u002Fimages\u002F2026\u002F09\u002F29\u002F1790636138336381447.png",8,"2026-09-29T06:56:46.91774372+08:00","2026-09-29T06:55:38.336536895+08:00","2026-09-29T06:56:46.917805846+08:00",{"id":120,"name":125,"slug":81,"description":125,"icon":5,"sortOrder":54,"postCount":54,"parentId":54,"createdAt":126,"updatedAt":126},"Python专题","2026-09-29T06:55:05.938571812+08:00",[128,129,134,137,141],{"id":64,"name":81,"slug":82,"color":83,"postCount":54,"createdAt":84},{"id":130,"name":131,"slug":132,"color":83,"postCount":54,"createdAt":133},118,"虚拟环境","xu-ni-huan-jing","2026-09-29T06:55:38.308255024+08:00",{"id":135,"name":116,"slug":116,"color":83,"postCount":54,"createdAt":136},119,"2026-09-29T06:55:38.316270691+08:00",{"id":138,"name":139,"slug":139,"color":83,"postCount":54,"createdAt":140},120,"conda","2026-09-29T06:55:38.323353011+08:00",{"id":142,"name":143,"slug":143,"color":83,"postCount":54,"createdAt":144},121,"uv","2026-09-29T06:55:38.330626142+08:00",{"id":146,"title":147,"slug":148,"summary":149,"content":150,"coverImage":151,"categoryId":120,"authorId":55,"viewCount":54,"likeCount":54,"commentCount":54,"isTop":54,"isRecommend":54,"status":55,"rejectReason":5,"publishedAt":152,"createdAt":153,"updatedAt":154,"category":155,"tags":156},12,"Python requests 爬虫入门：从发送请求到解析 HTML","requests-spider","Python 爬虫第一课：用 requests + BeautifulSoup 抓取网页数据。从 GET\u002FPOST 请求、请求头伪装到数据提取，附完整实战案例和反爬应对策略。","## 先立规矩：合法合规爬取\n\n写代码之前必须说清楚：\n\n- 只爬**公开数据**，不碰需要登录才能看的内容\n- 遵守网站的 `robots.txt` 协议（在域名后加 `\u002Frobots.txt` 可查看）\n- 控制频率，别把人家服务器压垮（就是讲礼貌）\n- 数据仅供个人学习，不用于商业用途，不爬个人隐私信息\n\n技术无罪，用法有责。下面的练习我们以公开的静态页面为例。\n\n## 爬虫的本质：三步走\n\n```text\n1. 发送请求（requests）→ 服务器返回 HTML 文本\n2. 解析内容（BeautifulSoup）→ 从 HTML 里提取要的数据\n3. 保存数据（csv\u002Fjson\u002F数据库）→ 落盘\n```\n\n安装依赖：\n\n```bash\npip install requests beautifulsoup4 lxml\n```\n\n## 第一步：requests 发送请求\n\n**最基本的 GET 请求**：\n\n```python\nimport requests\n\nresp = requests.get(\"https:\u002F\u002Fexample.com\")\nprint(resp.status_code)  # 200 表示成功\nprint(resp.text[:200])   # 网页 HTML 前 200 字符\n```\n\n**带参数的 GET**：\n\n```python\n# 等价于访问 https:\u002F\u002Fexample.com\u002Fsearch?q=python&page=2\nparams = {\"q\": \"python\", \"page\": 2}\nresp = requests.get(\"https:\u002F\u002Fexample.com\u002Fsearch\", params=params)\nprint(resp.url)  # 自动拼好的完整 URL\n```\n\n**POST 请求**（模拟表单提交）：\n\n```python\ndata = {\"username\": \"test\", \"password\": \"123456\"}\nresp = requests.post(\"https:\u002F\u002Fexample.com\u002Flogin\", data=data)\n```\n\n**响应对象常用属性**：\n\n```python\nresp.status_code   # 状态码：200 成功，404 不存在，403 被拒绝\nresp.text          # 文本内容（自动按推测编码解码）\nresp.content       # 二进制内容（下载图片\u002F文件用这个）\nresp.json()        # 接口返回 JSON 时直接转成字典\nresp.encoding      # 乱码时手动指定：resp.encoding = \"utf-8\"\n```\n\n## 第二步：请求头伪装（过不了反爬就看这里）\n\n很多网站会检查请求头，发现是程序访问就拒绝。加上请求头伪装成浏览器：\n\n```python\nheaders = {\n    \"User-Agent\": \"Mozilla\u002F5.0 (Windows NT 10.0; Win64; x64) \"\n                  \"AppleWebKit\u002F537.36 (KHTML, like Gecko) \"\n                  \"Chrome\u002F120.0.0.0 Safari\u002F537.36\",\n    \"Referer\": \"https:\u002F\u002Fexample.com\u002F\",  # 有些站检查来源页\n}\nresp = requests.get(url, headers=headers, timeout=10)\n```\n\n`User-Agent` 可以直接从浏览器复制：F12 打开开发者工具 → Network → 随便点一个请求 → Headers → Request Headers。\n\n**超时设置是必须的**：不加 `timeout`，目标服务器不响应时程序会永远卡住。\n\n## 第三步：BeautifulSoup 解析 HTML\n\n拿到 HTML 后，用 CSS 选择器提取数据：\n\n```python\nfrom bs4 import BeautifulSoup\n\nhtml = \"\"\"\n\u003Chtml>\u003Cbody>\n  \u003Cdiv class=\"article\">\n    \u003Ch2>标题一\u003C\u002Fh2>\n    \u003Cp class=\"intro\">简介一\u003C\u002Fp>\n  \u003C\u002Fdiv>\n  \u003Cdiv class=\"article\">\n    \u003Ch2>标题二\u003C\u002Fh2>\n    \u003Cp class=\"intro\">简介二\u003C\u002Fp>\n  \u003C\u002Fdiv>\n\u003C\u002Fbody>\u003C\u002Fhtml>\n\"\"\"\n\nsoup = BeautifulSoup(html, \"lxml\")\n\n# 最常用的 4 个方法\nsoup.select(\"h2\")            # CSS 选择器，返回所有匹配的列表\nsoup.select_one(\"h2\")        # 只返回第一个\nsoup.find(\"div\", class_=\"article\")   # 按标签+属性查找\nsoup.find_all(\"h2\")          # 返回所有匹配\n\n# 提取内容\nfor article in soup.select(\"div.article\"):\n    title = article.select_one(\"h2\").text.strip()\n    intro = article.select_one(\"p.intro\").text.strip()\n    print(title, \"|\", intro)\n```\n\n**提取属性**（如链接地址、图片地址）：\n\n```python\nlink = soup.select_one(\"a\")\nprint(link[\"href\"])     # 取 href 属性\nprint(link.get_text())  # 取链接文字\n```\n\n## 完整实战：抓取本站文章列表\n\n下面是一个可以直接改造的实战模板：\n\n```python\nimport csv\nimport time\n\nimport requests\nfrom bs4 import BeautifulSoup\n\nHEADERS = {\n    \"User-Agent\": \"Mozilla\u002F5.0 (Windows NT 10.0; Win64; x64) \"\n                  \"AppleWebKit\u002F537.36 Chrome\u002F120.0.0.0 Safari\u002F537.36\"\n}\n\ndef fetch_page(url: str) -> str:\n    \"\"\"下载页面，带重试\"\"\"\n    for attempt in range(3):\n        try:\n            resp = requests.get(url, headers=HEADERS, timeout=10)\n            resp.raise_for_status()  # 4xx\u002F5xx 直接抛异常\n            resp.encoding = \"utf-8\"\n            return resp.text\n        except requests.RequestException as e:\n            print(f\"第 {attempt + 1} 次请求失败: {e}\")\n            time.sleep(2)\n    return \"\"\n\ndef parse_page(html: str) -> list[dict]:\n    \"\"\"解析页面，提取文章列表（选择器按目标网站实际结构调整）\"\"\"\n    soup = BeautifulSoup(html, \"lxml\")\n    articles = []\n    for item in soup.select(\"div.post-item\"):\n        title_tag = item.select_one(\"h2 a\")\n        if not title_tag:\n            continue\n        articles.append({\n            \"title\": title_tag.get_text(strip=True),\n            \"url\": title_tag.get(\"href\", \"\"),\n            \"date\": item.select_one(\".date\").get_text(strip=True)\n                    if item.select_one(\".date\") else \"\",\n        })\n    return articles\n\ndef save_csv(data: list[dict], filename: str = \"articles.csv\"):\n    \"\"\"保存到 CSV，Excel 可直接打开\"\"\"\n    with open(filename, \"w\", newline=\"\", encoding=\"utf-8-sig\") as f:\n        writer = csv.DictWriter(f, fieldnames=[\"title\", \"url\", \"date\"])\n        writer.writeheader()\n        writer.writerows(data)\n    print(f\"已保存 {len(data)} 条到 {filename}\")\n\nif __name__ == \"__main__\":\n    all_data = []\n    for page in range(1, 4):  # 抓前 3 页\n        url = f\"https:\u002F\u002Fexample.com\u002Fposts?page={page}\"\n        html = fetch_page(url)\n        if html:\n            all_data.extend(parse_page(html))\n        time.sleep(1.5)  # 礼貌间隔，别给人家服务器压力\n    save_csv(all_data)\n```\n\n## 动态渲染页面怎么办\n\n上面的方法只适合**服务端渲染**的页面（HTML 里直接有数据）。如果数据是 JS 动态加载的（`resp.text` 里找不到要的内容），两条路：\n\n**路线一：找接口（优先）**\n\nF12 → Network → Fetch\u002FXHR，刷新页面，找返回 JSON 的接口。很多网站前端就是调这些接口拿数据的，直接请求接口比解析 HTML 省事十倍：\n\n```python\nresp = requests.get(\"https:\u002F\u002Fexample.com\u002Fapi\u002Fposts?page=1\", headers=HEADERS)\ndata = resp.json()  # 直接拿到结构化数据，连解析都不用\n```\n\n**路线二：上浏览器自动化**\n\n接口藏得太深时，用 Playwright 驱动真实浏览器：\n\n```bash\npip install playwright && playwright install chromium\n```\n\n```python\nfrom playwright.sync_api import sync_playwright\n\nwith sync_playwright() as p:\n    browser = p.chromium.launch(headless=True)\n    page = browser.new_page()\n    page.goto(\"https:\u002F\u002Fexample.com\")\n    page.wait_for_selector(\"div.post-item\")  # 等内容渲染出来\n    html = page.content()\n    browser.close()\n# 之后照常 BeautifulSoup 解析\n```\n\n## 常见反爬与应对\n\n| 反爬手段 | 表现 | 应对 |\n|---------|------|------|\n| UA 检测 | 403 拒绝 | 加真实 User-Agent |\n| 频率限制 | 429 或封 IP | time.sleep 降速、换 IP |\n| 登录墙 | 跳转登录页 | requests.Session() 保持 Cookie |\n| 验证码 | 弹验证 | 降低频率避开触发点，不建议硬刚 |\n| 动态渲染 | HTML 里没数据 | 找接口或用 Playwright |\n\n## 写在最后\n\n爬虫 80% 的工作量是「分析目标网站」而不是写代码：数据在 HTML 里还是接口里？分页参数长什么样？有没有反爬？先花 20 分钟用 F12 把网站看透，代码往往 20 行就够了。\n","\u002Fstatic\u002Fuploads\u002Fimages\u002F2026\u002F09\u002F29\u002F1790636138387839504.png","2026-09-29T06:56:45.03460923+08:00","2026-09-29T06:55:38.387959519+08:00","2026-09-29T06:56:45.034666561+08:00",{"id":120,"name":125,"slug":81,"description":125,"icon":5,"sortOrder":54,"postCount":54,"parentId":54,"createdAt":126,"updatedAt":126},[157,158,163,167,172],{"id":64,"name":81,"slug":82,"color":83,"postCount":54,"createdAt":84},{"id":159,"name":160,"slug":161,"color":83,"postCount":54,"createdAt":162},122,"爬虫","pa-chong","2026-09-29T06:55:38.359394779+08:00",{"id":164,"name":165,"slug":165,"color":83,"postCount":54,"createdAt":166},123,"requests","2026-09-29T06:55:38.366815934+08:00",{"id":168,"name":169,"slug":170,"color":83,"postCount":54,"createdAt":171},124,"BeautifulSoup","beautifulsoup","2026-09-29T06:55:38.372320794+08:00",{"id":173,"name":174,"slug":175,"color":83,"postCount":54,"createdAt":176},125,"数据采集","shu-ju-cai-ji","2026-09-29T06:55:38.379703082+08:00",{"id":178,"title":179,"slug":180,"summary":181,"content":182,"coverImage":183,"categoryId":120,"authorId":55,"viewCount":54,"likeCount":54,"commentCount":54,"isTop":54,"isRecommend":54,"status":55,"rejectReason":5,"publishedAt":184,"createdAt":185,"updatedAt":186,"category":187,"tags":188},13,"Pandas 数据处理 10 个高频操作，一篇就够","pandas","日常数据分析 80% 的工作只用这 10 个操作：读写文件、筛选、分组聚合、缺失值处理、表合并。每个操作附可直接套用的代码模板。","## 为什么是 Pandas\n\n处理表格数据，Excel 能做的事 Pandas 都能做，而且：\n\n- **快**：几十万行数据秒级处理，Excel 早就卡死\n- **可复现**：代码写一次，下个月的新数据重新跑一遍就行\n- **可串联**：读数据 → 清洗 → 分析 → 出图一条龙\n\n```bash\npip install pandas openpyxl  # openpyxl 用于读写 Excel\n```\n\n## 准备演示数据\n\n后面的例子都用这份「销售数据」：\n\n```python\nimport pandas as pd\n\ndf = pd.DataFrame({\n    \"姓名\": [\"张三\", \"李四\", \"王五\", \"张三\", \"李四\", \"赵六\"],\n    \"部门\": [\"华东\", \"华北\", \"华东\", \"华东\", \"华北\", \"华南\"],\n    \"销售额\": [12000, 8500, 15000, 13500, None, 9000],\n    \"入职日期\": [\"2023-01-15\", \"2023-03-20\", \"2022-11-05\",\n                 \"2023-01-15\", \"2023-03-20\", \"2024-02-01\"],\n})\n```\n\n## 操作 1：读写 CSV \u002F Excel\n\n```python\n# 读取\ndf = pd.read_csv(\"sales.csv\")                      # CSV\ndf = pd.read_excel(\"sales.xlsx\", sheet_name=\"1月\")  # Excel 指定工作表\n\n# 中文乱码？指定编码\ndf = pd.read_csv(\"sales.csv\", encoding=\"gbk\")\n\n# 保存（index=False 去掉行号，utf-8-sig 保证 Excel 打开不乱码）\ndf.to_csv(\"output.csv\", index=False, encoding=\"utf-8-sig\")\ndf.to_excel(\"output.xlsx\", index=False)\n```\n\n## 操作 2：快速查看数据\n\n拿到任何数据，先做这四个动作：\n\n```python\ndf.head()        # 前 5 行长什么样\ndf.info()        # 每列的类型、有没有缺失\ndf.describe()    # 数值列的统计：均值\u002F最值\u002F分位数\ndf.shape         # 几行几列\n```\n\n## 操作 3：选列与筛选行\n\n```python\n# 选列\ndf[\"销售额\"]                    # 单列（Series）\ndf[[\"姓名\", \"销售额\"]]          # 多列（DataFrame）\n\n# 条件筛选（最常用的操作，没有之一）\ndf[df[\"销售额\"] > 10000]\ndf[df[\"部门\"] == \"华东\"]\ndf[(df[\"销售额\"] > 10000) & (df[\"部门\"] == \"华东\")]  # 多条件用 & |，必须加括号\n\n# 模糊匹配\ndf[df[\"姓名\"].str.contains(\"张\")]\n\n# 在列表内匹配\ndf[df[\"部门\"].isin([\"华东\", \"华南\"])]\n```\n\n## 操作 4：排序与去重\n\n```python\n# 排序\ndf.sort_values(\"销售额\", ascending=False)           # 按销售额降序\ndf.sort_values([\"部门\", \"销售额\"], ascending=[True, False])  # 先部门升序再销售额降序\n\n# 去重\ndf.drop_duplicates()                      # 整行重复才删\ndf.drop_duplicates(subset=[\"姓名\"])       # 同一人只留一条\ndf.drop_duplicates(subset=[\"姓名\"], keep=\"last\")  # 留最后一次出现的\n```\n\n## 操作 5：缺失值处理\n\n```python\n# 查看缺失情况\ndf.isna().sum()      # 每列有几个缺失\n\n# 处理：三选一\ndf.dropna()                              # 1. 删掉含缺失的行\ndf[\"销售额\"].fillna(0)                   # 2. 填固定值\ndf[\"销售额\"].fillna(df[\"销售额\"].mean()) # 3. 填均值（数值列常用）\n```\n\n## 操作 6：分组聚合（groupby，核心中的核心）\n\n「按部门统计销售额」这类需求的标准答案：\n\n```python\n# 按部门分组，算销售额总和\ndf.groupby(\"部门\")[\"销售额\"].sum()\n\n# 一次算多个统计量\ndf.groupby(\"部门\")[\"销售额\"].agg([\"sum\", \"mean\", \"count\"])\n\n# 多列分组 + 不同列不同聚合\ndf.groupby(\"部门\").agg(\n    总销售额=(\"销售额\", \"sum\"),\n    人均销售额=(\"销售额\", \"mean\"),\n    人数=(\"姓名\", \"count\"),\n).reset_index()  # 把分组键变回普通列，方便后续处理\n```\n\n## 操作 7：新增计算列\n\n```python\n# 直接运算\ndf[\"提成\"] = df[\"销售额\"] * 0.05\n\n# 条件赋值：销售额过万的标记为「高」\nimport numpy as np\ndf[\"等级\"] = np.where(df[\"销售额\"] > 10000, \"高\", \"普通\")\n\n# 对文本列做处理\ndf[\"姓名长度\"] = df[\"姓名\"].str.len()\n```\n\n## 操作 8：表合并（merge 与 concat）\n\n```python\n# concat：上下堆叠（合并多个月份的同结构表格）\ndf_all = pd.concat([df_1月, df_2月, df_3月], ignore_index=True)\n\n# merge：左右拼接（类似 SQL 的 JOIN）\ndf_员工 = pd.DataFrame({\"姓名\": [\"张三\", \"李四\"], \"职级\": [\"P5\", \"P6\"]})\ndf.merge(df_员工, on=\"姓名\", how=\"left\")  # 左连接：保留左表全部行\n```\n\n`how` 的四种取值：`left`（保留左表）、`right`、`inner`（交集）、`outer`（并集）。日常 90% 用 `left`。\n\n## 操作 9：日期处理\n\n```python\n# 字符串转日期\ndf[\"入职日期\"] = pd.to_datetime(df[\"入职日期\"])\n\n# 提取年\u002F月\u002F星期几\ndf[\"入职年\"] = df[\"入职日期\"].dt.year\ndf[\"入职月\"] = df[\"入职日期\"].dt.month\n\n# 按月统计\ndf.groupby(df[\"入职日期\"].dt.to_period(\"M\"))[\"销售额\"].sum()\n\n# 日期差（算工龄）\ndf[\"工龄天数\"] = (pd.Timestamp.now() - df[\"入职日期\"]).dt.days\n```\n\n## 操作 10：透视表（pivot_table）\n\nExcel 透视表的代码版，多维交叉统计一把梭：\n\n```python\n# 行=部门，列=等级，值=销售额总和\ndf.pivot_table(\n    index=\"部门\",\n    columns=\"等级\",\n    values=\"销售额\",\n    aggfunc=\"sum\",\n    fill_value=0,      # 空位填 0\n    margins=True,      # 加合计行列\n)\n```\n\n## 避坑指南\n\n**1. SettingWithCopyWarning 警告**\n\n```python\n# 错误：先筛选再赋值，改的可能不是原表\ndf[df[\"部门\"] == \"华东\"][\"销售额\"] = 0\n\n# 正确：用 loc 一步定位\ndf.loc[df[\"部门\"] == \"华东\", \"销售额\"] = 0\n```\n\n**2. 链式赋值结果没生效**\n\nPandas 很多操作**返回新对象而不改原表**。要么接返回值：\n\n```python\ndf = df.dropna()  # 接返回值\n# 或者\ndf.dropna(inplace=True)  # 用 inplace 参数\n```\n\n**3. 数字被读成字符串**\n\nCSV 里带「¥」「,」的数字会被当成文本，先清洗再计算：\n\n```python\ndf[\"销售额\"] = df[\"销售额\"].str.replace(\"¥\", \"\").str.replace(\",\", \"\").astype(float)\n```\n\n## 写在最后\n\nPandas 的学习曲线不在语法而在**思维转换**：别写 for 循环逐行处理，学会用「向量化」的批量操作。遇到需求先问自己——这能不能用筛选、分组、合并这三板斧解决？90% 的情况答案是能。\n","\u002Fstatic\u002Fuploads\u002Fimages\u002F2026\u002F09\u002F29\u002F1790636138431846763.png","2026-09-29T06:56:42.1304477+08:00","2026-09-29T06:55:38.43196821+08:00","2026-09-29T06:56:42.13052425+08:00",{"id":120,"name":125,"slug":81,"description":125,"icon":5,"sortOrder":54,"postCount":54,"parentId":54,"createdAt":126,"updatedAt":126},[189,190,194,199],{"id":64,"name":81,"slug":82,"color":83,"postCount":54,"createdAt":84},{"id":191,"name":192,"slug":180,"color":83,"postCount":54,"createdAt":193},126,"Pandas","2026-09-29T06:55:38.411798198+08:00",{"id":195,"name":196,"slug":197,"color":83,"postCount":54,"createdAt":198},127,"数据分析","shu-ju-fen-xi","2026-09-29T06:55:38.419436825+08:00",{"id":200,"name":201,"slug":202,"color":83,"postCount":54,"createdAt":203},128,"数据处理","shu-ju-chu-li","2026-09-29T06:55:38.426699835+08:00",{"id":205,"title":206,"slug":207,"summary":208,"content":209,"coverImage":210,"categoryId":120,"authorId":55,"viewCount":54,"likeCount":54,"commentCount":54,"isTop":54,"isRecommend":54,"status":55,"rejectReason":5,"publishedAt":211,"createdAt":212,"updatedAt":213,"category":214,"tags":215},14,"Python 装饰器详解：从原理到实战的 5 个经典用法","decorator","装饰器是 Python 最优雅也最劝退的语法。从闭包原理讲起，用计时、缓存、权限校验等 5 个真实场景把装饰器彻底讲透。","## 装饰器解决什么问题\n\n先看一个真实痛点。项目里有一堆函数，现在要给**每一个**都加上「记录执行时间」的功能：\n\n```python\ndef get_user_list():\n    # ... 业务逻辑\n    pass\n\ndef create_order():\n    # ... 业务逻辑\n    pass\n```\n\n笨办法：每个函数内部都加两行计时代码。20 个函数改 20 处，以后要加「记录日志」再改 20 处——烦且容易漏。\n\n装饰器的思路：**写一个「包装机」，把原函数包一层，在不改原函数代码的前提下增加新功能**。\n\n```python\n@timer   # 这一行就是装饰器，自动给函数加上计时功能\ndef get_user_list():\n    pass\n```\n\n## 原理：装饰器就是「函数套函数」\n\n理解装饰器只需要知道两件事：\n\n1. **函数可以当参数传递**，也可以当返回值返回\n2. **闭包**：内部函数可以记住外部函数的变量\n\n最简装饰器长这样：\n\n```python\nimport functools\nimport time\n\ndef timer(func):\n    \"\"\"装饰器：统计函数执行时间\"\"\"\n    @functools.wraps(func)  # 保留原函数的名字和文档，好习惯\n    def wrapper(*args, **kwargs):\n        start = time.time()\n        result = func(*args, **kwargs)   # 执行原函数\n        print(f\"{func.__name__} 耗时 {time.time() - start:.3f} 秒\")\n        return result\n    return wrapper\n\n@timer\ndef slow_function():\n    time.sleep(1)\n\nslow_function()  # 输出：slow_function 耗时 1.002 秒\n```\n\n`@timer` 这行代码等价于 `slow_function = timer(slow_function)`——把原函数传进 timer，用返回的 wrapper 替换掉原来的名字。之后每次调用 `slow_function()`，实际执行的是包装后的 wrapper。\n\n## 实战 1：日志记录\n\n```python\nimport functools\nimport logging\n\nlogging.basicConfig(level=logging.INFO, format=\"%(asctime)s %(message)s\")\n\ndef log_calls(func):\n    \"\"\"记录函数调用的参数和返回值\"\"\"\n    @functools.wraps(func)\n    def wrapper(*args, **kwargs):\n        logging.info(f\"调用 {func.__name__}，args={args}, kwargs={kwargs}\")\n        result = func(*args, **kwargs)\n        logging.info(f\"{func.__name__} 返回 {result}\")\n        return result\n    return wrapper\n\n@log_calls\ndef add(a, b):\n    return a + b\n\nadd(3, 5)\n# 输出两行日志：调用 add，args=(3, 5), kwargs={} \u002F add 返回 8\n```\n\n## 实战 2：缓存（记忆化）\n\n递归算斐波那契数列，不加缓存时 fib(35) 要算好几秒，因为重复计算了大量子问题。加个缓存装饰器：\n\n```python\nimport functools\n\ndef cache(func):\n    \"\"\"把算过的结果存起来，相同参数直接返回\"\"\"\n    saved = {}\n    @functools.wraps(func)\n    def wrapper(*args):\n        if args not in saved:\n            saved[args] = func(*args)\n        return saved[args]\n    return wrapper\n\n@cache\ndef fib(n):\n    if n \u003C 2:\n        return n\n    return fib(n - 1) + fib(n - 2)\n\nprint(fib(100))  # 秒出结果\n```\n\n**其实不用自己写**：标准库 `functools.lru_cache` 就是现成的缓存装饰器，还带容量淘汰策略：\n\n```python\nfrom functools import lru_cache\n\n@lru_cache(maxsize=128)\ndef fib(n):\n    if n \u003C 2:\n        return n\n    return fib(n - 1) + fib(n - 2)\n```\n\n## 实战 3：权限校验（Web 开发天天用）\n\n```python\nimport functools\n\n# 模拟当前登录用户\ncurrent_user = {\"name\": \"张三\", \"role\": \"admin\"}\n\ndef require_role(role):\n    \"\"\"带参数的装饰器：要求特定角色才能访问\"\"\"\n    def decorator(func):\n        @functools.wraps(func)\n        def wrapper(*args, **kwargs):\n            if current_user.get(\"role\") != role:\n                raise PermissionError(f\"需要 {role} 权限\")\n            return func(*args, **kwargs)\n        return wrapper\n    return decorator\n\n@require_role(\"admin\")\ndef delete_all_data():\n    print(\"数据已删除\")\n\ndelete_all_data()  # 正常执行\n# 把 current_user[\"role\"] 改成 \"guest\" 再调用 → PermissionError: 需要 admin 权限\n```\n\n注意这里变成了**三层函数嵌套**：`require_role(\"admin\")` 先拿到参数返回装饰器，装饰器再接收函数。规律：**装饰器带参数，就多套一层**。\n\nFlask 里的 `@app.route(\"\u002Fapi\")`、登录校验 `@login_required`，全都是这个模式。\n\n## 实战 4：重试机制\n\n调用不稳定的网络接口时特别好用：\n\n```python\nimport functools\nimport time\n\ndef retry(times=3, delay=1):\n    \"\"\"失败自动重试\"\"\"\n    def decorator(func):\n        @functools.wraps(func)\n        def wrapper(*args, **kwargs):\n            for attempt in range(1, times + 1):\n                try:\n                    return func(*args, **kwargs)\n                except Exception as e:\n                    print(f\"第 {attempt} 次失败：{e}\")\n                    if attempt == times:\n                        raise\n                    time.sleep(delay)\n        return wrapper\n    return decorator\n\n@retry(times=3, delay=2)\ndef call_unstable_api():\n    import random\n    if random.random() \u003C 0.7:   # 70% 概率失败，模拟不稳定接口\n        raise ConnectionError(\"网络超时\")\n    return \"成功\"\n```\n\n## 实战 5：函数注册表（进阶但极其有用）\n\n插件系统、命令分发器的标准写法：\n\n```python\nCOMMANDS = {}  # 命令注册表\n\ndef register(name):\n    \"\"\"把函数注册到命令表\"\"\"\n    def decorator(func):\n        COMMANDS[name] = func\n        return func   # 不改函数行为，只登记\n    return decorator\n\n@register(\"hello\")\ndef cmd_hello():\n    print(\"你好！\")\n\n@register(\"bye\")\ndef cmd_bye():\n    print(\"再见！\")\n\n# 根据用户输入动态调用\nuser_input = \"hello\"\nCOMMANDS[user_input]()   # 输出：你好！\n```\n\n新增命令只需要加新函数并装饰，调用处的分发逻辑一行不用改——这就是「开放封闭原则」。\n\n## 避坑指南\n\n**1. 别忘了 @functools.wraps**\n\n不加的话，被装饰函数的 `__name__`、`__doc__` 都会变成 wrapper 的，调试和文档生成时会错乱。\n\n**2. 装饰顺序很重要**\n\n```python\n@timer\n@log_calls\ndef f(): ...\n# 等价于 f = timer(log_calls(f))\n# 执行顺序：先 timer 的包装，进去后先记日志再执行，出来后计时\n```\n\n**3. 装饰器在导入时就执行**\n\n`@register` 这类装饰器在**模块导入时**就跑完了，不是调用时才跑。利用这个特性做自动注册，也要注意别在装饰器里写耗时操作拖慢启动。\n\n## 写在最后\n\n装饰器的学习路径：先会用 `@lru_cache`、`@app.route` 这些现成的 → 再读懂别人的装饰器 → 最后才会写自己的。别反过来。先把本文的 5 个例子各敲一遍，用到的场景出现时，你自然会长出「这里该用装饰器」的直觉。\n","\u002Fstatic\u002Fuploads\u002Fimages\u002F2026\u002F09\u002F29\u002F1790636138476604049.png","2026-09-29T06:56:39.89932248+08:00","2026-09-29T06:55:38.476769783+08:00","2026-09-29T06:56:39.899403489+08:00",{"id":120,"name":125,"slug":81,"description":125,"icon":5,"sortOrder":54,"postCount":54,"parentId":54,"createdAt":126,"updatedAt":126},[216,217,222,227],{"id":64,"name":81,"slug":82,"color":83,"postCount":54,"createdAt":84},{"id":218,"name":219,"slug":220,"color":83,"postCount":54,"createdAt":221},129,"装饰器","zhuang-shi-qi","2026-09-29T06:55:38.452280181+08:00",{"id":223,"name":224,"slug":225,"color":83,"postCount":54,"createdAt":226},130,"闭包","bi-bao","2026-09-29T06:55:38.460791287+08:00",{"id":228,"name":229,"slug":230,"color":83,"postCount":54,"createdAt":231},131,"进阶","jin-jie","2026-09-29T06:55:38.468837931+08:00",{"id":233,"title":234,"slug":235,"summary":236,"content":237,"coverImage":238,"categoryId":120,"authorId":55,"viewCount":54,"likeCount":54,"commentCount":54,"isTop":54,"isRecommend":54,"status":55,"rejectReason":5,"publishedAt":239,"createdAt":240,"updatedAt":241,"category":242,"tags":243},15,"FastAPI 快速上手：30 分钟写一个可部署的 REST API","fastapi","Python 写接口最快的框架。从第一个路由到参数校验、自动文档、数据库连接，再到生产部署，一篇讲完整条链路。","## 为什么选 FastAPI\n\nPython Web 框架里，Flask 简单但太原始，Django 功能全但太重。FastAPI 是新一代的选择：\n\n- **快**：性能接近 Node.js 和 Go（基于 Starlette + Pydantic）\n- **自动文档**：写完代码自动生成交互式 API 文档，前端看了直呼内行\n- **类型安全**：用 Python 类型注解做参数校验，类型错了直接返回 422\n- **异步原生**：天生支持 async\u002Fawait，IO 密集场景性能碾压\n\n```bash\npip install fastapi uvicorn\n```\n\n## 第一个接口：5 行代码\n\n```python\n# main.py\nfrom fastapi import FastAPI\n\napp = FastAPI(title=\"我的第一个API\")\n\n@app.get(\"\u002F\")\ndef hello():\n    return {\"message\": \"Hello FastAPI\"}\n\n@app.get(\"\u002Fitems\u002F{item_id}\")\ndef get_item(item_id: int):   # 声明 int 类型，自动转换和校验\n    return {\"item_id\": item_id, \"name\": f\"商品{item_id}\"}\n```\n\n启动：\n\n```bash\nuvicorn main:app --reload --port 8000\n```\n\n- `--reload`：改代码自动重启，开发必备\n- `main:app`：指 main.py 文件里的 app 对象\n\n现在访问：\n\n- `http:\u002F\u002F127.0.0.1:8000\u002Fitems\u002F42` → 返回 JSON\n- `http:\u002F\u002F127.0.0.1:8000\u002Fitems\u002Fabc` → 自动返回 422 错误（因为 abc 不是 int）\n- `http:\u002F\u002F127.0.0.1:8000\u002Fdocs` → **自动生成的 Swagger 文档页面**，可以直接在页面上测试接口\n\n## 参数校验：Pydantic 的威力\n\nPOST 请求的数据校验，用 Pydantic 模型声明：\n\n```python\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel, Field\n\napp = FastAPI()\n\nclass ItemCreate(BaseModel):\n    name: str = Field(min_length=1, max_length=50, description=\"商品名\")\n    price: float = Field(gt=0, description=\"价格必须大于0\")\n    stock: int = Field(default=0, ge=0)\n    tags: list[str] = []\n\n@app.post(\"\u002Fitems\")\ndef create_item(item: ItemCreate):\n    return {\"message\": \"创建成功\", \"data\": item}\n```\n\n发请求时：\n\n- 字段缺失\u002F类型错误\u002F不满足约束 → 自动返回详细的 422 错误信息\n- 一切正常 → `item` 是一个类型安全的对象，IDE 有完整提示\n\n**再也不用手写 `if not name: return error` 这类校验代码**。\n\n## 查询参数与路径参数\n\n```python\nfrom typing import Optional\n\n@app.get(\"\u002Fitems\")\ndef list_items(\n    keyword: Optional[str] = None,   # 可选查询参数 ?keyword=xxx\n    page: int = 1,                    # 带默认值\n    size: int = Field(default=20, le=100),  # 限制最大100\n):\n    return {\"keyword\": keyword, \"page\": page, \"size\": size}\n```\n\n## 一个完整的小项目：待办事项 API\n\n把前面的知识串起来，写一个带增删改查的完整 API：\n\n```python\n# todo.py\nfrom fastapi import FastAPI, HTTPException\nfrom pydantic import BaseModel, Field\n\napp = FastAPI(title=\"待办事项API\")\n\n# 用列表模拟数据库（下节换成真实数据库）\ntodos = []\nnext_id = 1\n\nclass TodoCreate(BaseModel):\n    title: str = Field(min_length=1, max_length=100)\n    done: bool = False\n\nclass Todo(TodoCreate):\n    id: int\n\n@app.get(\"\u002Ftodos\", response_model=list[Todo])\ndef list_todos():\n    \"\"\"获取所有待办\"\"\"\n    return todos\n\n@app.post(\"\u002Ftodos\", response_model=Todo, status_code=201)\ndef create_todo(item: TodoCreate):\n    \"\"\"创建待办\"\"\"\n    global next_id\n    todo = Todo(id=next_id, **item.model_dump())\n    next_id += 1\n    todos.append(todo)\n    return todo\n\n@app.get(\"\u002Ftodos\u002F{todo_id}\", response_model=Todo)\ndef get_todo(todo_id: int):\n    \"\"\"获取单个待办\"\"\"\n    for t in todos:\n        if t.id == todo_id:\n            return t\n    raise HTTPException(status_code=404, detail=\"待办不存在\")\n\n@app.put(\"\u002Ftodos\u002F{todo_id}\", response_model=Todo)\ndef update_todo(todo_id: int, item: TodoCreate):\n    \"\"\"更新待办\"\"\"\n    for i, t in enumerate(todos):\n        if t.id == todo_id:\n            todos[i] = Todo(id=todo_id, **item.model_dump())\n            return todos[i]\n    raise HTTPException(status_code=404, detail=\"待办不存在\")\n\n@app.delete(\"\u002Ftodos\u002F{todo_id}\", status_code=204)\ndef delete_todo(todo_id: int):\n    \"\"\"删除待办\"\"\"\n    for i, t in enumerate(todos):\n        if t.id == todo_id:\n            todos.pop(i)\n            return\n    raise HTTPException(status_code=404, detail=\"待办不存在\")\n```\n\n`response_model` 的好处：自动过滤返回值（比如不暴露内部字段）、生成准确的文档、做响应校验。\n\n## 连接真实数据库（以 SQLite + SQLAlchemy 为例）\n\n```bash\npip install sqlalchemy\n```\n\n```python\n# database.py\nfrom sqlalchemy import Column, Integer, String, Boolean, create_engine\nfrom sqlalchemy.orm import declarative_base, sessionmaker\n\nengine = create_engine(\"sqlite:\u002F\u002F\u002F.\u002Ftodos.db\", connect_args={\"check_same_thread\": False})\nSessionLocal = sessionmaker(bind=engine)\nBase = declarative_base()\n\nclass TodoModel(Base):\n    __tablename__ = \"todos\"\n    id = Column(Integer, primary_key=True, index=True)\n    title = Column(String(100), nullable=False)\n    done = Column(Boolean, default=False)\n\nBase.metadata.create_all(engine)  # 自动建表\n```\n\n在 FastAPI 中用依赖注入管理数据库会话：\n\n```python\nfrom fastapi import Depends\nfrom sqlalchemy.orm import Session\n\ndef get_db():\n    \"\"\"每个请求一个会话，结束自动关闭\"\"\"\n    db = SessionLocal()\n    try:\n        yield db\n    finally:\n        db.close()\n\n@app.get(\"\u002Ftodos\")\ndef list_todos(db: Session = Depends(get_db)):\n    return db.query(TodoModel).all()\n\n@app.post(\"\u002Ftodos\", status_code=201)\ndef create_todo(item: TodoCreate, db: Session = Depends(get_db)):\n    todo = TodoModel(**item.model_dump())\n    db.add(todo)\n    db.commit()\n    db.refresh(todo)\n    return todo\n```\n\n## 生产部署\n\n开发用 `--reload`，生产环境这样跑：\n\n```bash\n# 4 个 worker 进程，绑定所有网卡\nuvicorn main:app --host 0.0.0.0 --port 8000 --workers 4\n```\n\n更稳的方案是 `gunicorn` 管理 uvicorn worker（Linux）：\n\n```bash\npip install gunicorn\ngunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000\n```\n\n前面再挂 Nginx 反向代理，搞定域名和 HTTPS，就是标准的生产架构。\n\n## 进阶方向\n\n到这里你已经能写可用的 API 了。下一步按需学习：\n\n- **用户认证**：`fastapi-users` 或手写 JWT（`python-jose`）\n- **跨域 CORS**：前端联调必配 `CORSMiddleware`\n- **后台任务**：`BackgroundTasks` 处理发邮件这类慢操作\n- **限流**：`slowapi` 防止接口被刷\n- **测试**：`fastapi.testclient` 基于 httpx，写接口测试很顺手\n\n## 写在最后\n\nFastAPI 的设计哲学是「别让我重复写样板代码」。类型注解即校验、代码即文档——把这两个特性用足，你的接口开发效率会提升一个量级。建议把待办事项的例子亲手跑通，再把它改造成一个你自己真正需要的小工具。\n","\u002Fstatic\u002Fuploads\u002Fimages\u002F2026\u002F09\u002F29\u002F1790636138525796879.png","2026-09-29T06:56:37.777573439+08:00","2026-09-29T06:55:38.52593956+08:00","2026-09-29T06:56:37.777643867+08:00",{"id":120,"name":125,"slug":81,"description":125,"icon":5,"sortOrder":54,"postCount":54,"parentId":54,"createdAt":126,"updatedAt":126},[244,245,249,254,259],{"id":64,"name":81,"slug":82,"color":83,"postCount":54,"createdAt":84},{"id":246,"name":247,"slug":235,"color":83,"postCount":54,"createdAt":248},132,"FastAPI","2026-09-29T06:55:38.496902061+08:00",{"id":250,"name":251,"slug":252,"color":83,"postCount":54,"createdAt":253},133,"Web开发","web-kai-fa","2026-09-29T06:55:38.503802445+08:00",{"id":255,"name":256,"slug":257,"color":83,"postCount":54,"createdAt":258},134,"REST API","rest-api","2026-09-29T06:55:38.511505671+08:00",{"id":260,"name":261,"slug":262,"color":83,"postCount":54,"createdAt":263},135,"后端","hou-duan","2026-09-29T06:55:38.519097242+08:00",{"id":265,"title":266,"slug":267,"summary":268,"content":269,"coverImage":270,"categoryId":120,"authorId":55,"viewCount":54,"likeCount":54,"commentCount":54,"isTop":54,"isRecommend":54,"status":55,"rejectReason":5,"publishedAt":271,"createdAt":272,"updatedAt":273,"category":274,"tags":275},16,"Python 异常处理最佳实践：别再只会 try-except 了","exception","except Exception 一把梭是最常见的错误姿势。本文讲清异常体系、精准捕获、自定义异常、日志记录和重试机制，让你的程序崩溃得体面。","## 为什么「裸 except」是灾难\n\n先看新手最常写的代码：\n\n```python\ntry:\n    data = json.loads(resp.text)\n    save_to_db(data)\nexcept Exception:\n    pass  # 出错了？吞掉，假装没事\n```\n\n这段代码的问题是：**任何错误都被静默吞掉**——JSON 解析失败、数据库连接断开、磁盘写满、甚至代码里的拼写错误，全都悄无声息。出问题时你没有任何线索，只能对着「数据怎么没存进去」发呆两小时。\n\n异常处理的核心原则只有一条：\n\n> **只捕获你能处理的异常，并且处理时必须留下痕迹。**\n\n## Python 异常体系速览\n\n异常是有继承层次的，常用的几个：\n\n```text\nBaseException\n├── KeyboardInterrupt      # Ctrl+C\n├── SystemExit             # sys.exit()\n└── Exception              # 日常说的「异常」都继承它\n    ├── ValueError         # 值不对：int(\"abc\")\n    ├── TypeError          # 类型不对：\"1\" + 1\n    ├── KeyError           # 字典键不存在\n    ├── IndexError         # 列表下标越界\n    ├── FileNotFoundError  # 文件不存在\n    ├── ConnectionError    # 网络连接失败\n    └── TimeoutError       # 超时\n```\n\n了解层次的意义在于：**捕获父类会连带捕获所有子类**。`except OSError` 会同时接住 `FileNotFoundError`、`PermissionError` 等一堆子类。\n\n## 正确姿势一：精准捕获\n\n```python\ntry:\n    with open(\"config.json\", encoding=\"utf-8\") as f:\n        config = json.load(f)\n    port = int(config[\"port\"])\nexcept FileNotFoundError:\n    print(\"配置文件不存在，使用默认配置\")\n    config = {\"port\": 8000}\n    port = 8000\nexcept json.JSONDecodeError as e:\n    print(f\"配置文件格式错误：{e}\")\n    raise  # 处理不了就继续抛，别硬撑\nexcept KeyError:\n    print(\"配置文件缺少 port 字段\")\n    raise\n```\n\n每个 `except` 只处理自己能搞定的场景，搞不定的用 `raise` 原样抛出，让上层或顶层去处理。\n\n## 正确姿势二：try 块越小越好\n\n```python\n# 差：整个流程都包进去，出错时根本不知道是哪一步\ntry:\n    data = download()\n    cleaned = clean(data)\n    save(cleaned)\n    notify()\nexcept Exception as e:\n    print(e)\n\n# 好：每个步骤单独处理，错误定位精确到环节\ndata = download()  # 下载失败应该直接崩溃报错，不用包\ntry:\n    cleaned = clean(data)\nexcept ValueError as e:\n    print(f\"数据清洗失败：{e}\")\n    return\nsave(cleaned)\n```\n\n`try` 块里只放**真正可能抛出你所捕获异常的那几行**。\n\n## 正确姿势三：善用 else 和 finally\n\n完整的异常处理有四个块：\n\n```python\ntry:\n    f = open(\"data.txt\", encoding=\"utf-8\")\nexcept FileNotFoundError:\n    print(\"文件不存在\")\nelse:\n    # 没有异常时执行——把成功后的逻辑放这里，职责更清晰\n    content = f.read()\n    f.close()\nfinally:\n    # 无论是否异常都执行——释放资源的专用位置\n    print(\"清理工作\")\n```\n\n`finally` 的典型用途：关文件、关数据库连接、释放锁。（不过现代 Python 更推荐 `with` 上下文管理器，它内部就是用 finally 实现的。）\n\n## 正确姿势四：自定义异常\n\n业务逻辑的错误，不要只会 `raise Exception(\"xxx\")`，定义自己的异常类型：\n\n```python\nclass BusinessError(Exception):\n    \"\"\"业务异常基类\"\"\"\n    pass\n\nclass InsufficientBalanceError(BusinessError):\n    \"\"\"余额不足\"\"\"\n    def __init__(self, balance: float, amount: float):\n        super().__init__(f\"余额不足：当前 {balance} 元，需支付 {amount} 元\")\n        self.balance = balance\n        self.amount = amount\n\ndef withdraw(balance: float, amount: float) -> float:\n    if amount > balance:\n        raise InsufficientBalanceError(balance, amount)\n    return balance - amount\n\n# 调用方可以精准处理\ntry:\n    withdraw(100, 200)\nexcept InsufficientBalanceError as e:\n    print(f\"支付失败：{e}\")\n    # e.balance 和 e.amount 还能拿出来做后续处理\n```\n\n好处：调用方能区分「业务错误」（余额不足，提示用户）和「系统错误」（数据库挂了，报警给运维）。\n\n## 正确姿势五：记录日志而不是 print\n\n程序上服务器后，`print` 基本等于没写。标准做法：\n\n```python\nimport logging\n\nlogging.basicConfig(\n    level=logging.INFO,\n    format=\"%(asctime)s [%(levelname)s] %(name)s: %(message)s\",\n    handlers=[\n        logging.FileHandler(\"app.log\", encoding=\"utf-8\"),  # 写文件\n        logging.StreamHandler(),                            # 同时输出到控制台\n    ],\n)\nlogger = logging.getLogger(__name__)\n\ntry:\n    result = process(data)\nexcept ValueError as e:\n    logger.warning(f\"数据格式问题：{e}\")\nexcept Exception:\n    # 意料外的异常，用 exception 方法记录完整堆栈\n    logger.exception(\"处理数据时发生未知错误\")\n    raise\n```\n\n**`logger.exception()` 是关键**——它会把完整的错误堆栈（哪一行、调用链）都记进日志，比 `logger.error(str(e))` 信息量多十倍。\n\n## 正确姿势六：失败重试\n\n网络请求、数据库连接这类「可能暂时性失败」的操作，加重试：\n\n```python\nimport time\nimport requests\n\ndef fetch_with_retry(url: str, times: int = 3, delay: float = 1.0):\n    \"\"\"带重试的请求：指数退避\"\"\"\n    for attempt in range(1, times + 1):\n        try:\n            resp = requests.get(url, timeout=10)\n            resp.raise_for_status()\n            return resp\n        except requests.RequestException as e:\n            logger.warning(f\"第 {attempt}\u002F{times} 次请求失败：{e}\")\n            if attempt == times:\n                raise  # 重试耗尽，抛出最后一次异常\n            time.sleep(delay * attempt)  # 每次等更久：1s、2s、3s\n```\n\n**哪些错误不该重试**：参数错误（400）、权限不足（403）、资源不存在（404）——这些是确定性错误，重试一万次也没用，直接抛。\n\n## 一张决策表\n\n遇到异常时问自己三个问题：\n\n| 问题 | 答案 | 做法 |\n|------|------|------|\n| 这个错误我能处理吗？ | 能（如文件不存在→用默认值） | except 住，处理，继续 |\n| | 不能 | 不捕获或 raise 抛给上层 |\n| 是暂时性故障吗？ | 是（网络抖动） | 重试 + 间隔 |\n| | 否（参数错误） | 立即失败 |\n| 用户需要知道吗？ | 需要 | 转成友好的中文提示 |\n| | 不需要 | 记日志即可 |\n\n## 写在最后\n\n异常处理的本质不是「防止程序崩溃」，而是**让程序崩溃时留下足够的信息，并且在能恢复的地方优雅恢复**。记住三条铁律：精准捕获、绝不静默、日志留痕。做到这三点，半夜被报警叫醒的次数会少很多。\n","\u002Fstatic\u002Fuploads\u002Fimages\u002F2026\u002F09\u002F29\u002F1790636138571123376.png","2026-09-29T06:56:36.117024762+08:00","2026-09-29T06:55:38.571251765+08:00","2026-09-29T06:56:36.117090124+08:00",{"id":120,"name":125,"slug":81,"description":125,"icon":5,"sortOrder":54,"postCount":54,"parentId":54,"createdAt":126,"updatedAt":126},[276,277,282,287],{"id":64,"name":81,"slug":82,"color":83,"postCount":54,"createdAt":84},{"id":278,"name":279,"slug":280,"color":83,"postCount":54,"createdAt":281},136,"异常处理","yi-chang-chu-li","2026-09-29T06:55:38.549655932+08:00",{"id":283,"name":284,"slug":285,"color":83,"postCount":54,"createdAt":286},137,"日志","ri-zhi","2026-09-29T06:55:38.556461509+08:00",{"id":288,"name":289,"slug":290,"color":83,"postCount":54,"createdAt":291},138,"最佳实践","zui-jia-shi-jian","2026-09-29T06:55:38.563680858+08:00",{"newer":293,"older":303},{"id":294,"title":295,"slug":296,"summary":297,"content":298,"coverImage":299,"categoryId":55,"authorId":55,"viewCount":54,"likeCount":54,"commentCount":54,"isTop":54,"isRecommend":54,"status":55,"rejectReason":5,"publishedAt":300,"createdAt":301,"updatedAt":302},5,"用 AI 十分钟生成 PPT：Gamma、Kimi、WPS AI 横向评测","ai-ppt","还在手动排版 PPT？实测 3 款主流 AI 生成 PPT 工具，从生成速度、设计质量到编辑自由度全面对比，附「提示词 + AI 工具」的高效工作流。","## 先说结论\n\n- **要好看、要快、英文场景多** → 选 Gamma\n- **中文内容、免费、能直接导出 PPTX** → 选 Kimi PPT 助手\n- **公司已经买了 WPS 会员** → 用 WPS AI，生态内无缝衔接\n\n但真正的效率秘诀不是选工具，而是**先用大模型生成大纲，再让工具排版**。直接让 PPT 工具「一句话生成」，得到的大多是内容空洞的漂亮花瓶。\n\n## 正确的 AI PPT 工作流\n\n无论用哪个工具，都按这个四步流程来：\n\n```text\n第 1 步：用大模型（DeepSeek\u002FKimi）生成结构化大纲\n第 2 步：人工调整大纲逻辑（最重要的一步）\n第 3 步：把大纲喂给 PPT 工具生成排版\n第 4 步：替换关键数据、修细节、导出\n```\n\n为什么第 2 步最重要？因为 PPT 的核心竞争力是**内容逻辑**，而逻辑只有你自己最清楚。AI 生成的大纲通常是「正确的废话」，需要你把真正的业务洞察加进去。\n\n**第 1 步的提示词模板**（实测好用）：\n\n```text\n请为一场「2025 年度部门工作总结」的 PPT 生成大纲。\n\n背景：我是电商运营部门的负责人，听众是公司高管，\n汇报时长 15 分钟，共 12-15 页。\n\n要求：\n1. 按「封面-目录-业绩回顾-亮点项目-问题复盘-明年计划-结尾」结构\n2. 每页给出：标题 + 3-4 个要点 + 建议配的图表类型\n3. 业绩回顾部分要有数据对比的呈现建议\n4. 用 Markdown 表格输出\n```\n\n拿到大纲后，删掉空洞的页面，把「亮点项目」换成你真实的案例，再进入下一步。\n\n## Gamma 实测\n\n**网址**：gamma.app，邮箱注册即用。\n\n**生成流程**：粘贴大纲（或一句话描述）→ 选主题风格 → 约 40 秒生成完毕。\n\n**优点**：\n\n- 设计感是三款里最强的，版式、配图、配色有「高级感」，不像传统 PPT 模板\n- 支持生成后继续对话式修改：「把第 3 页改成时间轴样式」\n- 网页原生，分享链接就能演示，适合远程汇报\n\n**缺点**：\n\n- 免费额度只有 400 积分（约 10 次生成），重度使用要订阅\n- 中文排版偶尔翻车（字间距、换行断句）\n- 导出 PPTX 后动画和部分版式会丢失，**最好直接在 Gamma 里演示**\n\n**适合**：追求设计感的个人汇报、英文演示、快速出原型。\n\n## Kimi PPT 助手实测\n\n**入口**：kimi.com 对话界面左侧「PPT 助手」。\n\n**生成流程**：把大纲粘贴给 Kimi → 说「帮我做成 PPT」→ 选模板 → 生成。\n\n**优点**：\n\n- **完全免费**，生成次数基本不限\n- 中文理解最好，毕竟底层就是中文大模型\n- 能直接导出 PPTX 到本地继续编辑\n- 可以直接把 Word\u002FPDF 文档扔进去让它提炼成 PPT\n\n**缺点**：\n\n- 模板设计偏「安全牌」，中规中矩，谈不上惊艳\n- 配图有时和内容关联度不高，需要手动换\n- 对复杂版式（时间轴、对比表）的支持一般\n\n**适合**：中文办公场景、学生答辩、不想花钱的日常汇报。\n\n## WPS AI 实测\n\n**入口**：WPS Office 内的「智能创作」。\n\n**优点**：\n\n- 与 WPS 深度集成，生成后直接用熟悉的 WPS 编辑器改，零学习成本\n- 模板库符合国内审美，党政、教育、商务风格齐全\n- 支持「文档转 PPT」，把写好的 WPS 文档一键转换\n- 企业版可以统一字体、配色、Logo，保证全公司 PPT 风格一致\n\n**缺点**：\n\n- AI 功能是会员权益，需要付费\n- 生成的内容深度一般，更像是「模板填空」\n- 创意版式不如 Gamma\n\n**适合**：WPS 重度用户、企业统一采购、需要严格符合单位模板规范的场景。\n\n## 横向对比表\n\n| 维度 | Gamma | Kimi | WPS AI |\n|------|-------|------|--------|\n| 设计感 | ★★★★★ | ★★★ | ★★★☆ |\n| 中文内容 | ★★★ | ★★★★★ | ★★★★ |\n| 价格 | 免费额度+订阅 | 免费 | 会员制 |\n| 导出 PPTX | 有损 | 支持 | 原生 |\n| 文档转 PPT | 支持 | 支持 | 支持 |\n| 企业协作 | 一般 | 一般 | 最强 |\n| 上手难度 | 低 | 最低 | 低 |\n\n## 让 AI PPT 更专业的 5 个细节\n\n机器生成的 PPT 都有「AI 味」，花 10 分钟改掉这些细节，观感立刻提升：\n\n1. **替换假数据**：AI 会编造「增长 35%」这类占位数据，全部换成真实数字\n2. **每页只留一个核心观点**：AI 喜欢堆要点，手动砍到每页一个重点\n3. **统一字体**：中文推荐思源黑体\u002F阿里巴巴普惠体，别让宋体和黑体混着出现\n4. **换掉 AI 配图**：AI 自动配的图经常是欧美风格的库存图，换成自己行业相关的图\n5. **加一页「数据出处」**：引用了数据就标明来源，专业度立刻拉开差距\n\n## 写在最后\n\nAI 生成 PPT 解决的是「从 0 到 0.6」的问题——排版、配色、框架这些体力活。但「从 0.6 到 1」的内容深度、数据洞察、演讲表达，仍然取决于你自己。\n\n最忌讳的用法是：一句话生成 → 一页不改 → 直接上台。听众一眼就能看出这是 AI 流水线的产物。**AI 负责快，你负责好**，这才是正确的分工。\n","\u002Fstatic\u002Fuploads\u002Fimages\u002F2026\u002F09\u002F29\u002F1790635573892291337.png","2026-09-29T06:46:39.004494493+08:00","2026-09-29T06:46:13.892430882+08:00","2026-09-29T06:46:39.004552618+08:00",null,{"items":305,"pagination":306},[],{"page":55,"pageSize":307,"total":54,"totalPages":55},20,{"materials":309},[],{"materials":311},[]]