年初二晚上我窝在沙发上刷手机看到朋友圈里都在晒春节档电影票根。有人纠结选哪部、有人看完吐槽、有人求推荐我脑子里突然蹦出一个念头与其翻来翻去看各路点评不如直接做一个自己的影评小助理。于是顺手打开电脑基于QClaw花了半小时捣鼓出一个新春热榜电影网页——把春节档热映电影拉出来让大模型逐部写推荐语、短评和观影建议再渲染成一个干净清爽的静态页面。整个过程没有复杂的后端架构没有数据库没有前后端联调一套Python脚本加上一个模板就把事办了。这篇文章就把我的完整思路、动手过程、翻过的车和最后沉淀下来的经验全部摊开讲给想快速上手大模型API做点小工具的朋友做个参考。我默认读到这篇文章的人至少有基础的Python功力能运行脚本、看得懂JSON就行。前端知识不用太多我会把模板部分说得明明白白。如果你连Python环境都还没装好也没关系先照着我文中给的步骤把工具链备齐后面一步步来。1. 项目到底做了什么一个半小时工具的完整拆解1.1 需求从一句话变成三个动作这个项目的起点特别简单我想要一个能自动生成影评、还能让人一眼看清新春档有哪些热门电影的页面。把这句话拆开本质上就三个需求点第一拿数据。我需要一份“新春热榜电影”清单包括片名、档期、导演/主演、类型、热度这些基础信息。第二生成内容。拿到清单后每部电影要有一段言之有物的推荐语或短评不能是那种干巴巴的简介粘贴得像一个真的看过电影的朋友在跟你聊。第三展示出来。内容生成之后得有一个能看的界面手机打开也能正常浏览。这三个动作对应到技术实现上就是一次数据请求、一批大模型调用、一次页面渲染。想通了这三件事整个项目的技术选型就清晰了不会再被“要不要做个管理系统”这类自我加戏的问题带跑偏。我见过太多人做一个展示页最后却连用户登录都加进去了这个习惯非常要命。1.2 半小时的时间到底花在哪里很多朋友问我半小时是不是有点夸张其实我把时间流水账列出来你们就明白了。前五分钟在想清楚上面说的三个动作同时决定技术路线脚本抓数据、QClaw生成影评、Jinja2渲染静态页不碰后端框架。中间十五分钟集中在调用QClaw调通影评生成接口这部分占了全项目最大比重。后面十分钟做模板、跑脚本、生成网页、手机打开检查效果。时间能压这么短核心原因是砍掉了两个最耗时的环节服务端开发和前端框架。没有用后端框架自然就不用处理路由、会话、跨域这些事没有用前端框架就不用考虑构建工具、组件库和浏览器兼容矩阵。静态页面生成后直接本地打开简单粗暴。很多时候你会觉得自己没时间做工具其实不是没时间是默认了“做个网页必须上全套工程化方案”这个观念才是最大的时间黑洞。1.3 这个项目解决的是什么人的什么需求说句实话这种小工具的服务对象首先是我自己其次才是身边同样纠结选片的朋友。如果你是个电影爱好者不想在选片上花太多时间如果你是个内容创作者需要快速产出对热映电影的短评素材如果你是个开发者正想找个不复杂的项目练手大模型API的接入——那么这套方案你都能直接用。把它当作一个模板来理解更合适。你只需要把电影清单换成其他任何列表式数据比如新春书单、必去展览、热门游戏、新店推荐再改一改提示词里的角色设定和输出要求一套“热榜内容AI解读网页”的流程就齐了。这也是为什么我愿意把它写成一篇文章而不是一个只能跑一次的脚本——它背后是一套可以反复复制的方法。2. 为什么选这套方案工具选型的核心逻辑2.1 QClaw在项目里承担的职责我先说清楚QClaw在这个项目里的角色它就是我的大模型API接入层。我需要一个渠道把电影清单数据发过去按我设定的规则生成影评文本再拿回结构化结果。QClaw在这里做的事相当于一个统一入口我不需要分别对接不同模型厂商的SDK也不用自己维护各种鉴权签名逻辑注册拿到密钥之后用一套HTTPS调用就能把文本生成能力接入进来。用生活里的事打比方QClaw就像一个外卖聚合平台。你只需要打开一个App就能点不同餐厅的菜而不用分别找每家店的老板要菜单、谈配送。我在项目里就是通过这个聚合入口调用模型把“生成影评”这个自然语言任务变成了一个可编程的接口。如果让我直接去研究模型部署、参数微调、推理优化这些东西别说半小时半天都未必能跑通第一个版本。QClaw把大模型能力封装成了开箱即用的工具这是项目能快速落地的一个关键因素。2.2 对比其他方案为什么不是直接手写、不是上框架其实在做这个网页的时候我脑子里快速过了一遍其他几条路线。第一条路线是纯手写HTML把内容一个个复制进页面。这听起来最简单但最大的问题是不可复用。电影数据一更新我又得手动改一遍页面影评生成过程完全没法自动化这个方案在只有三五部电影时还行一旦数量上来就是给自己挖坑。第二条路线是使用后端框架加数据库比如启动一个Web服务连上数据库写一堆接口。这对这个项目来说严重超配。我要的是一个能看的结果不是一套需要维护的服务用框架意味着要处理环境配置、服务部署、进程守护每个环节都是额外的时间成本。第三条路线就是我最终选定的脚本模式加静态页渲染。数据拉取和影评生成全部在本地脚本里完成产出物是一个独立的HTML文件任何设备都能直接打开。这个方案的优点在于成本和收益的平衡点最合适——既能自动化重复过程又省掉了服务端七大姑八大姨的依赖问题。2.3 静态网页可以满足需求吗有人会质疑静态网页能做得好用吗其实对“个人影评小助理”这个场景来说静态页不但够用还有几个动态站点比不了的好处。首先是部署零成本。生成好的HTML文件可以被放在任何静态托管平台上不需要购买服务器、不需要配置数据库。其次是打开速度快。没有数据库查询和接口调用浏览器加载的就是一个完整文件首屏渲染几乎即时完成。最后是安全性好维护没有服务端代码意味着没有可攻击面不存在注入之类的风险也不用天天盯着依赖库的安全公告。当然静态页也有它的边界如果我想让用户实时评论、点赞、根据关键词搜索全站内容那静态页就撑不住了。但回到我最初的需求——给朋友和自己提供一个快速参考的榜单页——这些功能一个都用不上。我们做工具时最怕的不是不会做复杂架构而是分不清什么时候不该复杂。3. 实操过程从零开始把网页揪出来3.1 拉取新春热榜电影数据先解决数据源动手第一步是搞定电影清单数据。我接了一个公开电影信息服务的接口示例地址实际项目里请替换为你能合法使用的数据源它能按热度返回当前档期的电影列表。调用方式非常简单一个带必要参数的GET请求就能返回JSONcurl https://api.example.com/movies/hot?regionCNperiodspring-festivallimit10 \ -H Accept: application/json这段请求的意思是我要中国区、春节档期、热度最高的10部电影。返回的数据大概长这样{ code: 0, data: { movies: [ { title: 示例电影A, release_date: 2025-01-29, director: 示例导演, genres: [喜剧, 家庭], rating: 7.9, hot_score: 95 } ] } }在Python脚本里拉取这段数据特别直接用标准库的urllib就能搞定不必为此引入requests依赖。不过考虑到后面还要请求QClaw的接口我直接用了requests库统一管理HTTP调用代码会更整洁。我写了一个fetch_hot_movies函数负责请求接口、解析JSON、提取电影列表。如果网络请求失败或者返回的数据结构对不上就抛出一个清晰的异常并给出备用方案避免脚本半路崩溃。3.2 设计影评生成提示词把要求说明白拿到电影列表之后下一步就是让QClaw帮我们写影评。这里最关键的环节不是写代码而是写好提示词。我第一次写的时候特别偷懒就一句话“给以下电影写影评”结果返回的内容五花八门有的像营销文案有的像剧情梗概完全没有统一的格式根本没法直接放进网页里展示。于是我把提示词改成了带明确约束的版本你是一位资深电影编辑。请基于以下电影信息为每部电影生成一段适合普通观众阅读的影评推荐语。 要求 1. 开场一句点明这部电影适合什么样的人去看。 2. 中间用两三句话说明电影的看点不得包含剧透。 3. 结尾用一句话提醒观影注意事项。 4. 输出必须为合法JSON数组每个元素包含三个字段suitable适合人群、highlight核心看点、note观影提示。 电影信息{电影名称、导演、类型、热度等}把输出格式限定为JSON数组是我反复踩坑后加上的。一开始让模型输出纯文本虽然读起来很自然但放进网页时需要再手动拆分效率特别低。改成JSON之后代码里一个json.loads就能把内容解析成结构体直接填充模板整个流水线就自动化了。3.3 用QClaw逐部生成影评内容理顺调用逻辑提示词准备好以后就是写代码调用QClaw的接口。我封装了一个生成单部电影影评的函数把电影信息和提示词模板拼在一起发送给QClaw的文本生成接口取回结果再解析成JSON。核心代码这样写import requests import json QCLAW_API_URL https://api.qclaw.example.com/v1/chat/completions QCLAW_API_KEY your-api-key # 请替换为你自己的密钥 def generate_review(movie_info): prompt build_prompt(movie_info) payload { model: qclaw-default, messages: [{role: user, content: prompt}], temperature: 0.7, max_tokens: 500, response_format: {type: json_object} } headers { Authorization: fBearer {QCLAW_API_KEY}, Content-Type: application/json } resp requests.post(QCLAW_API_URL, jsonpayload, headersheaders, timeout30) resp.raise_for_status() data resp.json() content data[choices][0][message][content] return parse_review_json(content)这个函数里面有几个参数值得一提。temperature控制在0.7属于“有点创意但不会乱来”的档位影评毕竟是个创作向内容温度太低会显得机械太高又容易跑题。max_tokens设为500既能保证每段影评有充分篇幅又不会因为生成过长内容导致接口超时。response_format限定为JSON对象这样返回的内容用json.loads能直接解析大大减少了清洗工作。3.4 批量生成并发还是串行热榜电影一般10部左右如果逐个串行调用按每部两三秒计算总共也就30秒左右。对于一个半小时出稿的项目来说这个速度完全能接受。所以我选了最简单可靠的方案循环串行调用每生成一部就打印一条进度日志同时把结果保存到内存列表里最后统一写入一个JSON文件。有人可能会说怎么不用多线程并发提高速度我也想过但并发调用引入了额外的变量接口限流、请求频率限制、异常重试逻辑复杂化。对10部电影的小规模场景省那几秒意义不大却可能给自己增加排查并发问题的成本。如果以后扩展到50部、100部电影我会再考虑用并发加限流的方案。记住一个原则脚本能跑通的价值远大于脚本跑得快的价值。我把每部电影的生成结果都打印出来检查了一遍确认没有明显的人工智障内容后才进入下一步。这一步很重要因为机器生成内容偶尔会有常识性错误人工抽查是最后一层质量保障。3.5 渲染出新春节热榜电影网页用Jinja2套模板内容生成完毕接下来就是把数据变成能看的网页。我用了Jinja2模板引擎先写一个HTML骨架再把电影数据填进去。之所以选择Jinja2而不是直接拼接字符串是因为模板引擎天然解决了三个麻烦转义问题、循环嵌套问题和修改样式问题。直接在Python字符串里写HTML代码会变得没法读改一个样式都得在字符串里小心找位置。我的模板核心结构是这样的!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title新春热榜电影影评/title style body { font-family: system-ui, sans-serif; max-width: 900px; margin: 0 auto; padding: 20px; background: #f8f8f8; } .movie-card { background: #fff; border-radius: 12px; padding: 20px; margin-bottom: 16px; box-shadow: 0 2px 6px rgba(0,0,0,0.06); } .movie-card h2 { margin: 0 0 8px 0; } .meta { color: #666; font-size: 14px; margin-bottom: 12px; } .section { margin: 12px 0; } .tag { display: inline-block; background: #f2f2f2; border-radius: 6px; padding: 2px 8px; margin-right: 6px; font-size: 13px; } /style /head body h1新春热榜电影 · AI 影评小助手/h1 {% for movie in movies %} div classmovie-card h2{{ movie.title }}/h2 div classmeta{{ movie.release_date }} · {{ movie.genres|join( / ) }}/div div classsectionstrong适合人群/strong{{ movie.review.suitable }}/div div classsectionstrong核心看点/strong{{ movie.review.highlight }}/div div classsectionstrong观影提示/strong{{ movie.review.note }}/div /div {% endfor %} /body /html渲染的时候我只需要把电影列表传给模板from jinja2 import Environment, FileSystemLoader env Environment(loaderFileSystemLoader(templates)) template env.get_template(index.html) render_result template.render(moviesmovies_with_reviews) with open(output/index.html, w, encodingutf-8) as f: f.write(render_result)这里有一个容易踩的坑写文件时必须指定encodingutf-8否则在Windows环境下默认用GBK编码写文件页面里所有中文都会变成乱码。我第一次跑的时候就漏了这一步打开网页满屏的锟斤拷把我整不会了。3.6 本地检查与成品效果生成完HTML之后我直接在浏览器里打开本地文件查看效果。整体页面是卡片式布局每部电影一张卡片上面是片名和基础信息下面是AI生成的三个板块适合人群、核心看点、观影提示。手机模式下用系统自带浏览器打开排版也没有明显错乱说明简单的响应式布局已经够用。说实话看到最终页面的那一刻还挺有成就感的。页面上出现的每一句影评都不是我手写的而是我把数据和规则交给大模型让它按我的要求生成出来的。我一个半小时前还在纠结春节档看什么一个半小时后我自己做了一个帮大家解决这个纠结的小网页。工具链的价值就在这个时候体现出来了。4. 影评生成质量提示词工程和输出稳定性4.1 提示词的几个关键层次很多人会低估提示词的作用以为就是把要求写下来就行。实际上要让大模型稳定产出符合需求的内容提示词至少要覆盖四个层次角色设定、任务说明、内容要求、格式约束。角色设定解决“以什么身份说话”的问题。我设定的是“资深电影编辑”这比直接说“帮我写影评”生成的文字更专业用词和语气明显更像样。任务说明解决“具体要干什么”的问题——要为每部电影生成一段影评推荐语。内容要求解决“什么能写什么不能写”的问题——要开篇点明适合人群、不能剧透、语言通俗。格式约束解决“怎么交付内容”的问题——必须是合法的JSON字段名称、含义全部给定。这四个层次缺一个输出的质量就可能打折扣。我试过不加角色设定生成的文字像说明书也试过不明确格式约束返回的内容解析起来非常痛苦。把四个层次全部写清楚以后模型输出的内容基本可以直接用后期几乎不需要人工修改。4.2 控制输出稳定性的几个实用技巧仅靠提示词还不够实际调用过程中我总结出几个对稳定性有明显帮助的技巧。第一给示例比给描述更有效。我在提示词模板里加了一个few-shot示例展示理想输出长什么样。大模型在模仿示例这件事上非常靠谱比抽象描述“请输出自然一点的文字”有用得多。第二把temperature调低反而能减少格式错误。写影评需要一定的创造性这个没错但温度太高会导致模型在JSON格式上犯傻时不时多一个逗号或者少一个引号。实测下来0.6到0.7之间是一个不错的平衡点。第三解析JSON时一定要加兜底。哪怕认为输出格式很稳模型偶尔还是会出幺蛾子返回里多出几句解释文字。我的解析函数里加了一个“清理再解析”的逻辑先把内容里明显的散文段落剥掉只提取最外层的JSON部分再交给json.loads。如果解析失败就返回一个预设的默认结构避免整个流程崩溃。4.3 翻车现场一次差点毁掉整页内容的生成我在这里分享一个真实的翻车经历。第一次批量生成的时候我没有在QClaw的请求参数里指定response_format模型心情不错把所有电影影评写成了长篇大论每一段都很有文采但完全不是JSON格式。解析函数直接炸了提示词写了一大堆要求也没拦住它自由发挥的冲动。后来我加上了response_format参数并且把提示词里的格式约束从“请输出JSON”改成了“只输出JSON数组不要输出任何其他文字”翻车率降到了接近零。这个经历告诉我一个道理在调用大模型时能通过接口参数限制的行为不要依赖提示词去说服模型。参数约束是硬性的提示词是软性的能用硬约束解决的问题就不要留到软约束里去赌运气。5. 常见问题与排查技巧实录5.1 接口调用失败或超时QClaw接口偶尔会抽风尤其是连续调用多次以后可能出现超时或者返回5xx错误。我的处理方式是在请求外层加一个简单的重试机制如果抛出异常或者返回状态码大于等于500等待两秒后重试最多重试三次。连续三次失败就把当前电影标记为“生成失败”用一条备用的中性文案顶上而不是让整个脚本中断。这种设计思路在个人工具里非常实用。日志里会记录哪部电影失败跑完以后我再单独补生成那一条就行不会被一个偶发错误卡住整条流水线。5.2 接口返回的内容解析失败解析失败是高频问题原因通常是模型输出不是合法JSON。我维护了一个解析函数专门处理两类情况一是内容里混了解释性的句子二是JSON里有尾逗号这类不严谨的写法。处理的思路是正则提取JSON块然后在解析前做一次简单的清洗。如果最终还是失败就返回兜底结构同时把原始内容打印出来方便自己去看模型到底输出了什么。5.3 电影信息缺失或者字段对不上公开电影接口偶尔会缺数据有些老电影没有类型信息有些新片没有正式评分。我的处理方式是在模板渲染时给每个字段加默认值Python里用get方法配合默认参数页面就不会出现“None”字样的尴尬。更稳妥的做法是在拉取数据后做一次字段规整把所有缺失字段填充成“信息待补充”再进入后续流程。5.4 中文乱码和样式失效中文乱码的坑前面提过根源是文件编码。解决起来很简单读文件用encodingutf-8写文件也用encodingutf-8整个流程保持编码一致问题自然消失。样式失效多半是CSS路径问题因为我直接把样式内联在HTML里不存在外部资源引用所以基本不会出现样式丢失的情况。如果以后拆成独立CSS文件要记得使用相对路径不要用绝对路径。5.5 常见问题速查表问题现象可能原因排查思路和解决方案接口返回401API密钥错误或过期检查环境变量里的密钥确认复制时没有多余空格接口返回429请求频率超限在循环里加入sleep控制请求间隔或降低并发数内容不是JSON接口参数未开启JSON模式请求参数中加入response_format同时强化提示词约束中文乱码文件读写编码不一致所有文件读写统一使用encodingutf-8字段显示None原数据缺失渲染前做字段规整用默认值填充缺失项生成的影评跟电影对不上循环逻辑中数据串位在调用接口时打印一条包含片名的日志逐一核对对应关系6. 从半小时原型到更完整的小助理6.1 内容维度向历史档期扩展这个项目做的是新春热榜稍微改一下参数就能扩展到历史档期。我可以拉取过去十年的春节档电影列表让QClaw生成“历年春节档值得补看”的盘点页。这样一来过完春节之后页面依然有内容价值不会因为档期过去就废掉。数据库都不用建把每次生成的JSON存档就好长期积累下来就成了一座小型的影评内容库。6.2 从榜单页升级成搜索工具目前的网页只能按榜单浏览如果想让小助理更“助理”一点可以加一个简单的搜索交互。把所有电影数据和AI生成的影评都存在一个JSON文件里前端引入一个轻量搜索组件按片名、导演、类型过滤数据。不需要后端服务纯前端就把搜索做了。这是我接下来准备加的功能也算是不动服务端架构的增量升级。6.3 部署和更新的自动化生成好的HTML文件可以放到任意静态托管平台上免费且稳定。如果想定时更新比如每天凌晨重新抓一次电影热度数据并重新调用QClaw生成影评用系统自带的定时任务就能完成。脚本本身已经是命令行可运行的定时任务只需要按固定时间点执行一次生成命令然后把新文件推到托管平台即可。6.4 一个简单而有价值的个人工具观做完这个项目我最大的感受是个人工具的边界是由需求定义的不是由技术想象力定义的。纠结于用什么框架、要不要加数据库、要不要做用户系统本质上都是在用技术复杂度掩盖需求不清晰。当你把需求拆成“拉取数据、生成内容、展示结果”这样几个具体的动作技术选型的答案自己就浮出水面了。这篇文章里所有代码都是可以直接跑通的你只需要替换成自己的API密钥把数据源地址改成你实际使用的服务即可。如果中间卡在环境搭建上优先检查Python版本和依赖库是否安装完整。我个人现在的习惯是凡是这种内容生成类的小工具都不再去想“要不要做个App”或者“要不要上云”而是老老实实地选择静态页面加本地脚本的组合。先让工具解决自己的问题等它真正体现出持续使用的价值再考虑要不要升级复杂架构也不迟。毕竟对个人工具来说快速见效和易于修改才是第一优先级。
阅读完成 · 觉得有帮助?