ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

飞牛NAS变身AI监控:闲鱼自动盯价+大模型筛选实战

飞牛NAS变身AI监控:闲鱼自动盯价+大模型筛选实战 直接开工。这几年自组NAS、玩HomeLab的人越来越多飞牛fnOS算是国内团队做的轻量NAS系统里上手门槛最低的一个了Docker支持也干净利落。但说实话大部分人拿NAS也就存个照片、挂个迅雷让它跑点“带脑子”的活儿的很少见。今天要聊的这个玩法是我最近在飞牛NAS上搭完、实测跑了两周的一套“闲鱼自动盯价大模型筛选”组合让NAS每半小时自动去刷一遍指定商品的关键词看到“好价”直接推微信遇到拿不准的、有坑的、贩子伪装的用本地大模型先粗筛一遍再给你判断。最后把管理界面和回调入口通过公网暴露出去人在外面也能随时看实时状态、调筛选规则。整个过程不需要额外买云服务器飞牛NAS的Docker沙箱全包了核心涉及三块数据抓取层、大模型推理层、公网入口层。我会把每一步的思考逻辑、实际踩坑、最终的稳定方案全部写出来适合有一定NAS基础、想往“AI Agent”方向迈进的朋友。1. 整体方案拆解NAS跑监控到底跑的是什么1.1 先想清楚“闲鱼监控”在监控什么很多人一听“闲鱼监控”第一反应是做爬虫抓全站商品。真要这么干不仅账号容易风控抓回来的数据90%也是噪音。我搭这套系统的核心思路不是爬全站而是“定向盯盘”你只关心特定型号、特定价格区间的东西比如某个品牌的二手镜头、某款停产的路由器、某种型号的显卡。我把监控拆成了三层采集层定时登录闲鱼Web端按预设关键词搜索只取前几十条结果筛选层用大模型判断商品描述是否真实、价格是否异常偏低、是否存在“骗子话术”通知层筛选结果之后有高价值商品才推送避免每半小时轰炸一次微信这三层明确后技术选型就清晰了采集层用Playwright模拟浏览器最稳筛选层优先跑本地Ollama小模型算不过来再调云端大模型API通知层用Pushplus或者Server酱就够了微信直接收。1.2 为什么选飞牛NAS当载体而不是云服务器云服务器跑这套东西不存在性能问题但有一个现实门槛闲鱼监控是长期高频任务云服务器的带宽费、配置费一年下来千把块而且国内服务器对外发起高频访问很容易被限。飞牛NAS放在家里24小时在线跑Docker容器电费每天也就几毛钱公网入口用隧道解决成本几乎为零。另外一个原因是飞牛fnOS的Docker管理做得确实好用图形化界面里直接编辑compose文件容器日志、端口映射、资源占用一目了然。不像群晖要单独开SSH改权限。对我这种“能界面操作绝不敲命令”的人来说飞牛的学习成本低太多。1.3 我最终选定的技术栈这套系统跑起来后容器分布是这样的playwright-service跑Python脚本负责闲鱼搜索页抓取产出商品JSONdify编排Agent流程承接筛选逻辑内置工作流和提示词管理ollama本地跑大模型推理用qwen2.5系列做“内容粗筛”cloudflared内网穿透隧道把管理端暴露成HTTPS公网地址数据流是一条直线定时触发器唤醒Playwright → 拿到当轮商品列表 → 推给Dify里的工作流 → 工作流调用Ollama逐条打分 → 高分段产物推送微信 → 其余丢弃。这套链路看着简单实际调试的坑点全在接口细节上后面逐个说。2. 数据抓取层闲鱼页面反爬与Playwright实操2.1 闲鱼Web端登录态的获取方式闲鱼网页版强制登录才能看搜索内容所以Playwright不能走裸访问必须先把登录Cookie灌进去。我的做法是在自己电脑上用Chrome登录闲鱼网页版然后用EditThisCookie插件导出Cookie的JSON文件把这个文件拷贝到NAS上让Playwright启动时直接加载。需要注意两点。第一Cookie有有效期闲鱼大概一周左右会失效所以我加了一道“登录态检测”每次抓取前先访问一次搜索页看返回内容里有没有“登录”字样或验证码一旦触发就发告警通知提醒手动更新Cookie。第二导出的Cookie字段里有某些帮助判定风险的项比如sgsg这种尽量完整保留缺了容易被识别为异常环境。# cookie_loader.py 片段 import json from pathlib import Path def load_cookies(browser_context, cookie_path: str /data/cookies.json): raw json.loads(Path(cookie_path).read_text()) # 注意闲鱼导出的是带name/value/domain三要素的格式 cleaned [ {name: c[name], value: c[value], domain: c.get(domain, .goofish.com), path: /, secure: False, httpOnly: False} for c in raw ] browser_context.add_cookies(cleaned)飞牛NAS上跑Playwright之前记得先装系统依赖。镜像里缺的通常不是Python包而是Chromium的运行库。# 在容器内执行 apt-get update apt-get install -y \ libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \ libcups2 libdrm2 libxkbcommon0 libxcomposite1 \ libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2不装这些库Playwright启动Chromium时报错千奇百怪最常见的是OSError: [Errno 8] Exec format error这种误导性提示实际就是缺内核依赖。2.2 搜索结果的提取逻辑闲鱼页面的DOM结构变动很频繁上一周用的选择器下周可能就失效。我写提取逻辑的时候定了一条规矩能用文本匹配的绝不用复杂XPath能一次定位的绝不做二次遍历。商品链接的规律是/item/开头的可以稳定提取。价格字段的DOM虽然有改动但页面上有最终的渲染文本直接用正则抽就能解决。# parser.py 片段 import re from playwright.sync_api import sync_playwright items [] for card in page.query_selector_all(div[class*item]): if not card: continue title_el card.query_selector(span[class*title]) price_el card.query_selector(span[class*price]) link_el card.query_selector(a[href*/item/]) if not all([title_el, price_el, link_el]): continue href link_el.get_attribute(href) if not href: continue m re.search(r/item/(\d), href) if not m: continue raw_title title_el.inner_text().strip() price_txt price_el.inner_text().strip() price_match re.search(r(\d\.?\d*), price_txt) items.append({ id: m.group(1), title: raw_title, price: float(price_match.group(1)) if price_match else 0.0, url: fhttps://www.goofish.com{href}, })这里有个性能优化细节不要每次搜索都重新打开浏览器页面而是复用同一个Browser实例只开新Tab带Cookie去访问搜索页几轮后统一关闭。启动Chromium的耗时占比在真实运行里占大头能省就省。2.3 定时任务的时间粒度选择盯价任务的频率不能拍脑袋定。一开始我用的5分钟一刷跑了半天账号就被要求滑块验证了。后来逐步拉长到30分钟稳定运行两周没触发过风控。对于闲鱼这种二手交易平台好价商品并不会像秒杀券那样一分钟内没掉。30分钟的粒度配合大模型的快速筛选基本能在货源被拍走前推送到手。追求更快刷新频率的话收益开始急剧递减但风控压力指数上升非常不划算。定时我用的是容器内crontab配合CronRocket做了一次封装方便直接在环境变量里配时间表达式# docker-compose 片段 environment: CRON_RULE: */30 * * * * SEARCH_KEYWORDS: 索尼A7M3, 尼康Z6, 苹果M2 Mac mini2.4 新旧商品对比避免重复通知盯价最大的痛点不是漏而是重。同一个商品链接如果每次抓取都在每次筛选都通过那微信一天能推个十几条。我给每个商品ID建立了一张“已处理表”存Redis的set里通知过的ID直接跳过。但还要处理“降价再通知”的场景如果同一个商品价格比上次抓取时低超过10%也算新事件重新推送。# dedupe.py 片段 import redis r redis.Redis(hostredis, port6379, db0) def is_worth_notifying(item_id: str, current_price: float) - bool: key fitem:seen:{item_id} last_price r.hget(key, price) if last_price is None: r.hset(key, price, str(current_price)) return True last_price float(last_price) # 降价超过10%算新事件 if last_price and current_price last_price * 0.9: return False r.hset(key, price, str(current_price)) return True这套逻辑跑了一个月发现一个意外收益账号的安全性更高了因为重复请求数量骤减页面级抓取频次自然降下来了。3. 大模型筛选层Dify编排Agent的核心环节3.1 本地模型还是云端API怎么取舍热词里大家都在聊“大模型部署”“本地部署大模型”但真正的生产实践是混用不是二选一。我最初全用Ollama上的qwen2.5:7b做筛选发现推理速度大概是3到5秒一条应付30分钟一轮的数据量刚刚好。但问题在于7B模型的判断深度有限遇到复杂描述比如“同行勿扰不议价碰到鸽子拉黑”这种带行业黑话的它会给出模棱两可的结论甚至把“面交仅限自提”误判为高风险。经验值是粗筛走本地小模型精筛走云端大模型。粗筛负责过滤明显垃圾信息比如标题与搜索词无关、价格低于市场价五折、明确标注“批发代理”精筛工作交给GLM-4或DeepSeek这类云端API只处理粗筛通过的那批每次调用成本几分钱但准确率能拉到95%以上。3.2 Dify工作流的搭建思路我是先在飞牛的Docker里部署了Dify社区版然后创建了一个名为“闲鱼好价侦探”的Agent应用。工作流结构如下入口接收Playwright传来的商品标题、价格、描述、链接规则过滤先走一个“价格红线”判断低于市场价3折的直接拦截本地模型粗筛送Ollama输出一个0到1的疑似垃圾分云端模型精筛对得分在0.4到0.8之间的商品调用云端API重新评估输出返回actionnotify或actionskip附带一段推荐理由筛选提示词我迭代了好几版核心是四段结构角色定义、判断维度、输出格式、反例说明。你是一位熟悉闲鱼交易规则的资深买手。 请从以下维度评估商品信息是否值得买家关注 1. 标题与描述是否真实可信是否存在夸大 2. 价格与同类商品相比是否合理是否明显低于市场均价 3. 卖家描述中是否存在风险信号如引导站外交易、先款后货、拒绝平台担保 4. 商品是否带有明显行业内幕如“同行勿扰”“专业回收”“瑕疵处理” 输出严格遵循JSON格式 {should_notify: true/false, confidence: 0到1, reason: 简述判断依据} 只输出JSON不要添加任何多余文字。值得说明的是最后的“只输出JSON”这句话非常关键。不约束输出的话模型经常会送出一大段解释文本还得单独拆JSON解析失败率高达三成。3.3 飞牛NAS上的Dify与Ollama内存规划飞牛NAS跑大模型最大的瓶颈不是CPU是内存和显存。我的NAS是32GB内存没独显Ollama跑qwen2.5:7b量化版占用大概4.2GBDify全家桶API、Worker、Postgres、Redis、Weaviate加起来要吃掉3.5GBPlaywright容器留1GB总占用接近9GB长期跑下来还剩不少余量。如果你内存只有16GB建议Ollama换qwen2.5:3b或者gemma2:2b粗筛的阈值可以适当调宽。Dify里的向量数据库Weaviate是内存大户如果只做筛选不做RAG知识库可以去掉Weaviate容器用SQLite模式顶上能省出1GB。# docker-compose 容器资源限制片段 services: dify-worker: deploy: resources: limits: memory: 1024M ollama: deploy: resources: limits: memory: 6144M playwright-service: deploy: resources: limits: memory: 1024M内存限制一定要给够。我一开始没设限制Ollama默认会把70%的可用内存都吃进去飞牛自己反而卡了NAS后台都打不开。3.4 大模型筛选的“召回率”陷阱玩大模型筛选有个绕不开的词叫“幻觉”。“把这个商品当漏网之鱼放过去”的漏报往往比“把无关商品顶上来”的误报更可怕。试过几次之后我给工作流加了一条兜底逻辑凡是粗筛得分在0.5以下的商品不是直接丢弃而是进“低优先级队列”存着次日凌晨汇总一次推送。这样做的收益很明显有些商家改标题蹭热点导致关键词命中的凌晨人工扫一眼就能发现既不影响主流程的精准性又不至于漏掉真正的漏网之鱼。这条规则的触发率每周大概在5%左右实际运行下来没有一次是因为模型误判导致的好价商品被漏掉。4. 公网入口配置飞牛NAS用Cloudflare Tunnel实现HTTPS直连4.1 为什么不用DDNS端口转发热词里有人搜“飞牛nas怎么用ipv6访问”说明不少人已经走上DDNS的路线了。但DDNS在国内环境的痛点非常明显一是多数住宅宽带有公网IPv4需求运营商默认不给或给了也是动态的二是即便有公网IPv6跨运营商访问稳定性也是个谜。如果你有公网IPv4确实可以用DDNS端口转发但要做HTTPS证书和源站保护配置成本反而更高。我是直接上的Cloudflare Tunnel原因是无公网IP也能用、TLS证书自动续期、入口域名统一走HTTPS安全性和维护成本双优。4.2 飞牛NAS上快速部署CloudflaredCloudflared在飞牛NAS上直接用容器跑最简单不需要登录后台。docker run -d \ --name cloudflared \ --restartunless-stopped \ -v /path/to/your-config:/etc/cloudflared \ cloudflare/cloudflared:latest \ tunnel --config /etc/cloudflared/config.yml run配置文件里把Dify应用和管理后台公网化。要暴露的端口分别是Dify的Web端口、Ollama的可选API端口不建议直接暴露后面说原因。# config.yml tunnel: your-tunnel-id credentials-file: /etc/cloudflared/your-tunnel-id.json ingress: - hostname: dify-monitor.example.com service: http://127.0.0.1:3000 - hostname: nas-status.example.com service: http://127.0.0.1:8080 - service: http_status:404建议不要在公网直接暴露Ollama端口。没有鉴权的话未来任何访问到你域名的人都可以直接调用你的本地模型不仅白嫖算力还可能把模型跑死。更稳妥的方案是只暴露Dify的API让所有Ollama的访问都经由Dify工作流转一层。4.3 Cloudflare Tunnel的权限与Tunnel Token生成这里有个坑新版cloudflared推荐用Token启动但远程登录Tunnel Dashboard创建Token非常方便比自己折腾证书省事得多。你只需要在Cloudflare Zero Trust面板里创建一条隧道拿到Tunnel Token然后用docker run的时候把它注入docker run -d \ --name cloudflared \ --restartunless-stopped \ cloudflare/cloudflared:latest \ tunnel --no-autoupdate run --token YOUR_TUNNEL_TOKEN用Token模式下配置文件里的credentials-file就可以被跳过容器启动会自动拉取隧道配置。这样维护起来更省事配置变更直接在Cloudflare面板上改即可无需进NAS敲命令。4.4 公网入口的安全加固暴露Dify Web界面到公网等于给全世界开了个口子不加固就是给自己埋雷。第一层Cloudflare Access。配置一条Zero Trust策略只允许你的邮箱账号登录后才能访问Dify域名。这一步相当于在公网入口前面加一道“内部员工门禁”比Dify自己的密码认证靠谱得多。第二层限制管理类接口。Dify控制台的API密钥是独立的通知到微信的关键接口不要用公网域名只在容器内部网络访问。Playwright脚本在NAS本地调用Dify的内网IP不经过Cloudflare彻底断了外部触达管理逻辑的路。第三层账号锁定。飞牛NAS本身的管理端口不要映射到公网如果要远程SSH或看后台统一走Cloudflare Tunnel里的SSH route。这样所有入口都收口在Cloudflare源站IP完全不出现在公网。5. 常见问题与排查技巧实录5.1 闲鱼页面布局变化导致抓取空数据一个月里我遇到过三次抓取结果为空两次是页面改版一次是登录态过期。判断方法很简单给Playwright加一个页面快照和HTML级别日志每次抓完搜索页都把页面标题、商品数量、页面关键文本打个日志。如果日志显示有“验证码”字样直接告警人不要自动重试。# snapshot 逻辑 def after_fetch_snapshot(page, tag: str): logs { time: datetime.now().isoformat(), title: page.title(), body_snippet: page.inner_text(body)[:200], item_count: len(items), } logger.info(f[{tag}] {json.dumps(logs, ensure_asciiFalse)})自动重试反而会加重风控。一旦检测到异常通知人工处理比任何自动化修复都稳。5.2 Cloudflare Tunnel流量回源超时飞牛NAS的家庭上行带宽一般只有20M到50M如果公网访问Dify页面时发现图片加载不出来多半不是Cloudflare的问题而是源站在回传大文件时超时。Dify里配置了文件存储路径默认存在本地卷访问体积较大的图片时NAS的上行压力就上来了。我在Cloudflare Tunnel配置里加了严格的缓存规则只对静态资源生效API请求不做缓存避免拿到陈旧结果。还有一坑Cloudflare Tunnel的免费套餐处理大体积POST流量时会限速所以Playwright推送给Dify的数据只带文本字段不带图片base64需要看图时给链接让模型访问或人工用浏览器开。5.3 Ollama推理速度飘忽刚跑起来那两天每次抓取后通知延迟有时30秒有时3分钟。查日志发现是Ollama的模型被从内存换出了。原因是我没给Ollama设置OLLAMA_KEEP_ALIVE默认模型闲置5分钟就释放。Playwright抓取间隔是30分钟每次唤醒模型都要重新加载重新加载一次量化模型耗时20到60秒。解决办法environment: OLLAMA_KEEP_ALIVE: 30m设成30分钟后模型基本常驻内存每次筛选用不到5秒。代价是多占了几个G内存但从实际体验看完全值得。5.4 飞牛NAS上Dify安装失败排查飞牛的应用中心有Dify的社区版一键部署但我建议直接用Docker Compose手动拉因为应用中心的版本更新滞后而且模板里带了之前提到的Weaviate等一堆组件对于“只用工作流”的场景纯属浪费。手动部署时最容易栽在.env文件配置上Dify官方仓库给的是示例文件需要把SECRET_KEY、DB_PASSWORD这些值改成自己的。否则Postgres初始化失败容器启动两三秒就退出日志里一堆FATALS。# 拉取源码仓库的docker目录 git clone https://github.com/langgenius/dify.git cd dify/docker cp .env.example .env # 编辑.env强烈建议生成随机密码 docker compose up -d飞牛NAS上如果内存不够可以先把docker compose里的api和worker都只启动一个副本然后把nginx端口改成8080避免和飞牛自带的Web端口冲突。5.5 微信通知触达不到我用的是Pushplus配置极其简单只需要一个token。Dify工作流里加一个“发送通知”的HTTP节点就能处理。踩过一坑HTTP节点的请求体格式默认是JSONPushplus的接口接收的是form-data或者纯文本参数。不用改接口只需要在Dify里把请求体的Content-Type改成application/x-www-form-urlencoded即可。这个坑让我排查了半个晚上日志里显示200但微信一直不响。6. 这套系统的后续扩展空间一个有意思的方向是做“价格趋势记录”既然Playwright每次抓取都产出结构化价格数据把这些数据再喂给另一个大模型可以输出商品的价格波动曲线和“建议入手价”。我现在把每一轮的抓取结果都存了一份JSON到飞牛的文件系统里月底用Python脚本出一份Excel表能看到哪些型号在什么时间段价格最低。这个数据资产比监控本身值钱。另外一个方向是接入“闲鱼擦边词”的探测很多真实低价商品为了规避平台限制会把标题写成“戴尔精品”“拆机好物”这类模糊词纯关键词搜索是搜不到的。用大模型做意图扩展后把扩展词回传给采集层形成一个自我迭代的搜索词库。我已经在实验了初步效果是每周能多捕获3到4条手工搜索看不到的商品。最后想说的还是那个老话题AI套件跑在NAS上不叫“部署大模型”而是“让家里的设备有点脑子”。飞牛NAS的角色不再只是存储而是一个真正的私有Agent底座。这套监控系统跑了一周后我最好的战绩是1600块收到一台功能完好的微单挂9600的贩子货在模型面前露馅后被直接拦截。机器盯盘的性价比远大于人工蹲坑。这是我对这套项目最真实的评价。
返回列表