ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

闲鱼监控系统源码拆解:任务调度、安全管控与机器人实现

闲鱼监控系统源码拆解:任务调度、安全管控与机器人实现 简介这是一套面向二手交易平台的高并发AI监控机器人完整源码适合有Python基础的开发者学习自动化采集、任务调度与安全管控系统设计。资源包共30个文件约1.13MB涵盖Python核心脚本spider_v2.py、web_server.py、login.py、Web前端模板、Docker编排文件、配置文档与说明教程文件类型包括py、html、yml、dockerfile等便于按模块阅读和二次开发。系统特色在于通过自然语言即可一键创建监控任务支持多关键词并发与实时流式处理并集成GPT-4o等多模态大模型分析商品图文和卖家画像同时内置Cron定时调度、ntfy/企业微信/Bark多端通知、反爬随机延迟和Docker标准化部署等工程化能力。附带prompt_generator.py、config.json等AI提示词生成与多任务配置示例可快速改造为其他品类监控场景。目前已有126人学习下载适合作为个人自动化项目或AI Agent实战参考。1. 一个 zip 三套系统闲鱼监控为什么需要「监控 管控」同时盯几十个关键词、看竞品几点上新、自己商品被压价了要第一时间知道——闲鱼这种活干法靠手点刷新是撑不过三天的。你拿到的这份 zip 源码包拆开是三套东西任务监控系统负责在正确的时间把活跑起来安全智能管控系统管住登录权限和操作日志闲鱼智能监控机器人做具体的采集、匹配与通知。它是一份需要自己部署的 Python 源码不是装完就能用的成品适合准备长期盯守的闲鱼卖家也适合想研究定时任务、请求采集、限流鉴权这套组合的开发者。下面从解压开始拆把每一步翻给你看。2. 拿到源码第一步解压、目录、依赖与配置盘点2.1 解压先避两个坑伪加密与 EOCD 报错这份资源是 zip 压缩包最常见的翻车点就在解压。Windows 右键解压如果弹出要输入密码而你从头到尾没收到过密码先别急着找密码——很多分享出去的 zip 文件会被压缩工具顺手打上「加密」标记位但实际数据根本没加密这叫伪加密。zip 的通用标志位第 0 位表示文件有加密某些非标准工具会把这一位置 1Windows 资源管理器就会无条件要求输密码。处理方式很简单用 7-Zip 打开这个 zip通常可以直接把文件拖出来全程不需要密码。注意这只适用于「确实没有密码、只是标志位异常」的文件真正加密过的压缩包该输密码还是得输。另一个高频报错是解压到一半提示invalid zip archive: could not find EOCD。EOCD 是 zip 格式尾部的一段中央目录记录这个报错说明文件尾部字节损坏或丢失常见原因是文件在网盘、微信里转存被截断。表面看大小差不多实际尾部少了关键字节。解决方式是重新下载并且下完先校验完整性# 先校验压缩包完整性再解压 md5sum 任务监控系统_安全智能管控系统_闲鱼智能监控机器人.zip unzip -O gbk 任务监控系统_安全智能管控系统_闲鱼智能监控机器人.zip -d ./task_monitor-O gbk处理的是压缩包里中文文件名的乱码问题Linux 的 unzip 默认按 UTF-8 解中文名遇到 GBK 编码会解出一堆乱码目录。Windows 上对应的校验命令是certutil -hashfile 文件名 MD5。经验是凡是网盘下载的 zip先校验再解压能省掉一半的「明明解压了却找不到文件」的玄学问题。2.2 目录结构先分清三块代码在哪解压完成后先看目录不要急着跑。这套源码按标题里的三块功能做了分层拿到手先对照一下结构task_monitor/ ├── manage.py # 入口启动调度器与 Web 管控台 ├── requirements.txt ├── config/ │ └── config.py # 数据库、Redis、监控参数 └── app/ ├── scheduler/ # 任务监控系统 │ ├── jobs.py # 任务注册与调度 │ └── retry.py # 失败重试与状态记录 ├── security/ # 安全智能管控系统 │ ├── auth.py # 登录鉴权 │ ├── audit.py # 操作日志 │ └── ratelimit.py # 接口限流 ├── robot/ # 闲鱼智能监控机器人 │ ├── session.py # 登录态维护 │ ├── crawler.py # 采集与解析 │ └── notifier.py # 触发与通知 └── models/ ├── task.py └── item.py三个目录对应三个职能scheduler管任务什么时候跑、跑失败怎么办security管谁能看管控台、谁在什么时候干了什么、接口有没有被刷robot是业务核心管登录态、抓商品、发通知。manage.py是唯一入口先跑它就够了。如果目录结构和这里不一致以你手上的实际文件为准源码包在传播过程中难免有改动。2.3 依赖与中间件MySQL、Redis 一个都不能少这份资源依赖 MySQL 和 RedisMySQL 存商品数据和任务状态Redis 存登录态 Cookie、限流计数和分布式锁。依赖清单在requirements.txt里核心组件如下依赖作用常见坑Flask管控台与接口服务调试模式别开 debugTrue会重复加载调度器APScheduler定时任务调度不设 timezone 会差 8 小时requests闲鱼接口请求要复用 Session不能用 requests.get 裸调beautifulsoup4商品列表解析页面结构变化时要改选择器PyMySQLMySQL 连接版本低于 1.0 连不上 MySQL 8redis限流与会话存储Redis 未启动时任务会静默失败Windows 上装依赖直接用 pip 即可。MySQL 如果还没装去官网下 mysql-8.x-winx64.zip 这种 zip 包解压后管理员权限运行mysqld --initialize-insecure初始化再执行mysqld --install把它注册成本地服务这样就能用net start mysql托管了——这一步就是把 zip 包里的程序变成 Windows 服务和平时用nssm注册任意 exe 是同一个思路。pip install -r requirements.txt装完先别急着启动因为requirements.txt只解决 Python 依赖MySQL 和 Redis 得先保证在线。判断中间件是否就绪用下面两条命令看一眼连不上后面所有任务都会报「连接被拒」mysql -h 127.0.0.1 -u root -p -e SELECT VERSION(); redis-cli pingredis-cli ping返回PONG才说明 Redis 活着。这套资源里限流和会话维护都挂在 Redis 上Redis 挂了不会直接崩但登录态校验和限流会全失效表现成一种很隐蔽的「部分功能失灵」。2.4 配置文件这几个参数不改跑不起来config/config.py是唯一需要手动改的文件。注意这份代码设计上是把配置集中在一个类里改完重启进程生效class Config: # MySQL MYSQL_HOST 127.0.0.1 MYSQL_PORT 3306 MYSQL_USER root MYSQL_PASSWORD your_password # 改成你自己的 MYSQL_DB task_monitor # Redis REDIS_URL redis://127.0.0.1:6379/0 # Web 管控台密钥生产环境务必换掉 SECRET_KEY please-change-me # 闲鱼监控参数 MONITOR_INTERVAL 60 # 每 60 秒跑一轮监控 PRICE_DROP_RATIO 0.95 # 价格跌幅超过 5% 触发告警 WATCH_KEYWORDS [iPhone 13, Switch 二手, 相机 微单] DINGTALK_WEBHOOK https://oapi.dingtalk.com/robot/send?access_tokenxxxMONITOR_INTERVAL是机器人轮询间隔单位秒。个人用建议不小于 30别设成 5 秒一轮这种频率打出去基本活不过一天就会被风控。PRICE_DROP_RATIO是相对首次入库价格的跌幅阈值0.95 表示降到原价 95% 以下就通知。WATCH_KEYWORDS是你要盯的关键词列表改成你自己的品类。DINGTALK_WEBHOOK是钉钉群机器人的 Webhook程序靠它把命中消息推到群里没有就填空字符串程序会走日志输出。改完配置先手动建库mysql -u root -p -e CREATE DATABASE IF NOT EXISTS task_monitor DEFAULT CHARSET utf8mb4; python manage.py init_db建库时显式指定utf8mb4而不是utf8因为商品标题里可能有生僻字和特殊符号utf8在 MySQL 里存不了四字节字符插入时会报Incorrect string value。init_db子命令会建任务表、商品表、操作日志表执行后没报错就完成了第一步。3. 任务监控与安全管控给闲鱼机器人打上双保险3.1 定时调度让闲鱼监控任务自己跑起来闲鱼监控这件事的本质是「到点干活」。这套资源用的是 APScheduler 来做调度调度入口在app/scheduler/jobs.py。核心逻辑是一个常驻的BlockingScheduler把业务函数注册成定时作业from apscheduler.schedulers.blocking import BlockingScheduler from apscheduler.triggers.cron import CronTrigger from app.robot.crawler import run_monitor_once from app.security.ratelimit import check_quota sched BlockingScheduler(timezoneAsia/Shanghai) sched.scheduled_job(CronTrigger(minute*/10)) def robot_round(): # 每 10 分钟跑一轮闲鱼监控 if check_quota(robot, limit20, window60): run_monitor_once()scheduled_job装饰器把robot_round函数注册成作业CronTrigger(minute*/10)表示每 10 分钟触发一次。这里用的是 CronTrigger 而不是 IntervalTrigger区别在于CronTrigger 对齐自然时间适合「每 10 分钟」「每天 9:30」这种业务节奏IntervalTrigger 是相对上次执行的时间间隔适合「每 90 秒」这种固定周期。闲鱼监控建议用 CronTrigger因为对齐分钟数以后日志时间比较好对账——你看到14:30的日志就知道这轮是准点跑的。timezoneAsia/Shanghai这个参数必须设。很多部署翻车都是因为忽略了它本地 Windows 跑得好好的一上 Linux 服务器任务全错位本质是服务器系统时区是 UTC调度器按 UTC 触发。这里显式指定是让调度器不依赖系统时区自己管自己的时间。check_quota是安全管控系统的限流入口放在调度任务里而不是采集函数里用意是「先过闸再干活」——如果这一分钟配额已经用完这一轮就直接跳过而不是等请求发出去了才被拦。3.2 失败重试与任务状态别让一次抖动中断监控定时任务最怕的不是失败而是一失败就什么都不干。这套资源在app/scheduler/retry.py里封装了统一的重试逻辑import time from app.models.task import Task def run_task(task_id, execute, max_retries3, base_delay2): task Task.get(task_id) task.state running task.save() for attempt in range(1, max_retries 1): try: execute() task.state success task.last_error task.save() return except Exception as exc: if attempt max_retries: task.state failed task.last_error str(exc) task.save() notify_failure(task) # 钉钉告警不静默失败 return # 指数退避第 1 次等 2 秒第 2 次等 4 秒第 3 次等 8 秒 time.sleep(base_delay * (2 ** (attempt - 1))) task.state retrying task.save()每次任务执行前先置为running成功后置为success重试时置为retrying耗尽次数置为failed并记录last_error。这套状态机看着简单实际工作里特别有用排查问题时直接查任务表看最后一条状态的attempt字段就知道它卡在哪一步。重试间隔用指数退避而不是固定间隔原因是监控类任务大概率连着失败——闲鱼接口抖动、网络瞬断、登录态过期都是持续性的。如果固定 1 秒重试 5 次等于在接口已经出问题的情况下又打了 5 个无效请求反而加重风控。指数退避 2s、4s、8s 给系统留了恢复窗口。一个容易忽略的参数是max_retries。参数错误、数据格式变化这类确定性失败重试多少次都没用反而会把失败状态掩盖成「重试中」。我一般会约定网络异常连接超时、代理断连走重试业务异常返回码不是预期、字段缺失直接失败并告警。判断方式是看异常类型从哪个模块抛出在这套代码里就是requests.exceptions.ConnectionError重试KeyError不重试。3.3 安全管控登录鉴权、操作日志与接口限流管控台不是谁都能看的。这套资源的安全智能管控系统做了三件事登录鉴权、操作审计、接口限流三个模块各守一道口。鉴权用 JWT登录成功后发 token后续请求带在 Header 里。app/security/auth.py核心是一个装饰器from functools import wraps from flask import request, jsonify import jwt def login_required(f): wraps(f) def wrapper(*args, **kwargs): token request.headers.get(X-Token) try: payload jwt.decode(token, app.config[SECRET_KEY], algorithms[HS256]) request.user_id payload[uid] except Exception: return jsonify({code: 401, msg: 登录态失效}), 401 return f(*args, **kwargs) return wrapperjwt.decode用项目SECRET_KEY验签token 里只存uid不存密码。这里的坑是SECRET_KEY一定不能在配置文件里保持默认值JWT 的特点是签名密钥泄露等于所有人可以自己伪造 token等于鉴权整个失效。操作日志用装饰器实现def audit_log(action_name): def decorator(f): wraps(f) def wrapper(*args, **kwargs): result f(*args, **kwargs) log_operation( user_idrequest.user_id, actionaction_name, pathrequest.path, remote_iprequest.remote_addr, ) return result return wrapper return decoratoraudit_log记录的是「谁在什么时候干了什么」。log_operation写入独立的operation_log表而不是混在业务日志里。这样排查问题时可以单独查表某天机器人行为异常先看操作日志排除是不是有人手动调过配置或触发过任务。限流用 Redis 计数实现一个固定窗口import time import redis r redis.Redis.from_url(REDIS_URL) def rate_limit(key, limit30, window60): def decorator(f): wraps(f) def wrapper(*args, **kwargs): now int(time.time()) # 以窗口起点作为 key 的一部分窗口内自增 bucket frate:{key}:{now // window} count r.incr(bucket) if count 1: r.expire(bucket, window 1) if count limit: return jsonify({code: 429, msg: 请求过于频繁}), 429 return f(*args, **kwargs) return wrapper return decoratornow // window把时间切成一段段 60 秒的窗口incr计数超过limit直接返回 429。这里有个已知的边界问题固定窗口在窗口切换的一瞬间可以产生两倍流量——比如第 59 秒用了 30 次第 61 秒又可以再用 30 次。真正的滑动窗口能封住这个口子但这套资源是给闲鱼监控用的外部接口本身还有自己的风控固定窗口已经足够追求极致限流的同学可以自己替换成 Redis ZSet 滑动窗口版本。4. 闲鱼机器人链路实战登录态、采集解析与触发通知4.1 登录态维护Cookie 持久化与失效自恢复闲鱼监控机器人的核心难题不是抓数据而是登录态能撑多久。这套资源把登录态设计成了「Redis 存 Cookie 失效检测 人工介入恢复」的闭环。app/robot/session.py负责这一层import requests import redis r redis.Redis.from_url(redis://127.0.0.1:6379/0) def build_session(): s requests.Session() s.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Accept: application/json, text/plain, */*, Referer: https://www.goofish.com/, }) cookies r.get(session:cookies) if cookies: s.cookies.update(parse_cookie_string(cookies)) return s def is_logged_in(s): resp s.get(https://www.goofish.com /api/user/check, timeout5) return resp.json().get(login) is True def ensure_valid(s): if not is_logged_in(s): # 登录态失效需要人工扫码/验证介入一次完成后写回 Redis manual_login_and_save(s) return s把 Cookie 存 Redis 而不是进程内存里是有讲究的调度器一旦重启进程内存里的 Session 对象就没了但 Redis 里的 Cookie 还在进程起来后会重新加载登录态不丢。manual_login_and_save是人工介入的入口微信扫码或者滑块验证后程序会把新的 Cookie 序列化写回 Redis覆盖过期数据。不要尝试自动绕过验证码这既不稳定也不合规人工介入一次能稳定跑几天这已经是性价比最高的方案。这里有个实际经验Cookie 失效检测要轻量不要用「拉一页搜索页再判断」这种重方式。用/api/user/check这类轻量接口一次请求就知道是否登录。重检测会在登录态正常时浪费大量请求推高风控概率。注意监控要克制。登录态失效时程序会通知你人工介入而不是自动换 IP、自动过验证码——个人辅助场景不需要也不应该做这些。4.2 商品采集与解析从搜索页到结构化数据采集模块在app/robot/crawler.py流程是携带登录态请求搜索页、解析 HTML 提取商品卡片、按item_id去重入库。核心函数from bs4 import BeautifulSoup from app.models.item import Item def fetch_item_list(session, keyword, page1): params {keyword: keyword, page: page} resp session.get( https://www.goofish.com /api/search, paramsparams, timeout10, ) soup BeautifulSoup(resp.text, html.parser) items [] for card in soup.select(.item-card): items.append({ item_id: card.get(data-id), title: card.select_one(.title).text.strip(), price: float(card.select_one(.price).text.replace(¥, )), url: card.select_one(a).get(href), }) return items def save_if_new(items): new_count 0 for it in items: if Item.find_by_item_id(it[item_id]): continue # 已存在跳过 Item.insert(it) new_count 1 return new_count解析选择器.item-card是对应有商品卡片的 CSS class实际运行中以你抓包看到的页面结构为准页面改版后这里是最需要改的地方。find_by_item_id按商品 ID 去重这是必须的——同款商品标题会被卖家改来改去但item_id不会变按标题去重会重复入库同一件商品。首次部署有个必须处理的细节第一次跑监控会抓回一大批历史商品如果直接进通知逻辑你的钉钉会被刷屏。源码里的做法是支持「预热模式」——首次运行只入库不通知等库里有基线数据后后续新出现的商品才算「上新」。实现上就是给Item.insert加一个notify开关首轮传False之后传True。4.3 触发通知关键词、价格阈值与推送渠道商品入库之后判断要不要通知的逻辑在app/robot/notifier.py。这套资源支持两种触发条件关键词命中、价格跌破阈值。规则以列表形式配置在Config.WATCH_KEYWORDS附近def check_rules(item, rules): hits [] for rule in rules: # 配置结构: {keyword: iPhone 13, max_price: 4000} if rule[keyword] in item[title]: if rule.get(max_price) is None or item[price] rule[max_price]: hits.append(rule) return hits def notify(webhook, content): requests.post( webhook, json{msgtype: text, text: {content: content}}, timeout5, )check_rules返回命中的规则列表供上游拼接通知内容。max_price是可选的填了就做价格判断不填只做关键词判断。这里的匹配逻辑用的是子串匹配rule[keyword] in item[title]适合「iPhone 13」这种固定词。如果你想盯的是「价格低于 3000 的微单」就配{keyword: 微单, max_price: 3000}。通知走钉钉群机器人 Webhook除了价格和标题实际推送内容里我建议至少带上商品链接——你看到通知后点进去就能确认不用再打开监控后台查。requests.post加timeout5很关键Webhook 响应慢时不会让监控线程阻塞 5 秒以上。如果钉钉在你们公司不好用可以把这个函数改成 Server酱、企业微信机器人接口格式都是 POST JSON改动很小。运行时的取词节奏也有讲究每轮监控之间建议加一个 0.5 到 1.5 秒的随机休眠打散请求时间特征。连续请求如果间隔完全一致机器特征会非常明显这是被风控的头号原因。随机休眠的代价是每轮监控耗时变长但换来的是登录态寿命按天计算值。5. 常见问题排查五个能让人半夜惊醒的坑5.1 运行与风控三个能把人送走的坑现象一任务到点不跑日志一行都没有。原因调度器进程根本没存活。常见于用终端前台跑python manage.pySSH 一断开进程就被杀了另一种可能是BlockingScheduler是单线程的前一个任务卡在一个网络请求上不返回后续任务全部排队堵死。解决部署时用第 6 章的 systemd 方式托管进程让它是常驻服务而不是终端子进程同时给所有requests.get都加timeout参数这个资源里统一设了 10 秒防止单次请求把整个调度器卡死。另外把任务表加一个「心跳」字段每次任务执行时更新时间排查时一眼看出调度器最后一次干活是什么时候。现象二闲鱼登录态两天就失效监控悄悄变成「未登录」状态。原因要么是请求频率过高触发了平台风控要么是 Cookie 虽然写进了 Redis 但被其他设备登录挤掉了。解决先看监控频率MONITOR_INTERVAL小于 30 的先调回 60再看是不是只有一台设备在用这套登录态闲鱼登录态和设备和 IP 是绑定的不要让程序和手机同时在线互踢。失效时程序会调manual_login_and_save等人工介入不要跳过这一步更不要并发拉多个 Session 去测。现象三请求频率不高压根没调却开始返回 406 或滑块验证页面。原因请求特征太明显——同一个 User-Agent、固定 1 秒间隔、无浏览器行为特征。平台风控看的是指纹而不是单纯频率。解决第一把 User-Agent 抽成配置做一个轮换列表每轮请求随机选一个第二随机休眠不要用固定值用random.uniform(0.5, 1.5)让请求间隔有自然抖动第三不要多线程并发采集这资源里所有调度都是单线程串行就是这个原因——并发一时爽封号火葬场。5.2 基础设施与时区两个容易被忽略的坑现象四pymysql报cryptography相关错误或Authentication plugin caching_sha2_password cannot be loaded但 Navicat 连同一个 MySQL 是正常的。原因MySQL 8.0 默认认证插件是caching_sha2_password旧版 PyMySQL 只支持mysql_native_password。Navicat 客户端自带新插件所以没事PyMySQL 版本太老就直接报错。解决先升级 PyMySQL 到 1.0 以上pip install PyMySQL1.0.0这是首选方案如果数据库权限受限、只能改用户可以用ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY password;把认证方式改回去。注意 MySQL 8.4 开始已经弃用mysql_native_password不建议在全新环境里反向操作正确路径是升级客户端。现象五定时任务每天差 8 小时本地 9:30 该跑的任务服务器下午 5:30 才跑。原因APScheduler 默认用系统本地时区Linux 服务器系统时区往往是 UTC比北京时间慢 8 小时。你以为配了hour9就行实际它按 UTC 的 9 点触发换算成北京就是下午 5 点。解决在创建调度器时显式声明时区BlockingScheduler(timezoneAsia/Shanghai)并在所有CronTrigger.from_crontab调用里也统一传时区。这个坑的隐蔽性在于本地 Windows 开发机时区就是 UTC8完全复现不了一上服务器才现原形属于典型的部署环境差异。从那以后我每到一个新环境第一件事就是date看一眼系统时区再决定调度器要不要显式指定。6. 进阶把机器人变成常驻服务并验证整条链路监控脚本变成常驻服务才算真正落地。Linux 上用 systemd 托管写一个 service 文件能让崩溃自动拉起、开机自动启动[Unit] DescriptionTask Monitor Robot Afternetwork.target redis.service mysql.service [Service] Userdeploy WorkingDirectory/opt/task_monitor ExecStart/usr/bin/python3 manage.py Restartalways RestartSec5 [Install] WantedBymulti-user.targetRestartalways保证进程退出了 5 秒后自动重启WorkingDirectory必须指向项目根目录否则代码里相对路径全部失效。保存到/etc/systemd/system/task-monitor.service后执行sudo systemctl daemon-reload sudo systemctl enable --now task-monitor systemctl status task-monitorWindows 环境可以用nssm把python manage.py注册成服务原理一致指定可执行文件和启动目录勾选「失败后自动重启」。服务化之后不再担心 SSH 断开杀进程后台任务才能真正 7×24 跑。服务化之后的验证技巧很关键。部署完成不要直接等手动往商品表里插一条符合监控规则的商品记录强制走一轮完整链路mysql -u root -p task_monitor \ -e INSERT INTO item (item_id, title, price, url, created_at) VALUES (TEST001, iPhone 13, 3800, https://www.goofish.com/item/TEST001, NOW()) ON DUPLICATE KEY UPDATE price3800;设置完等一个调度周期然后去钉钉群确认有没有收到这条商品的推送。收到说明从采集入库、规则匹配到 Webhook 推送的整条链路是通的没收到用journalctl -u task-monitor -f看日志一眼就能定位是卡在入库还是卡在通知。从那以后我每次部署完都会先插一条测试记录把整条链路从前到后走一遍确认通知能收到才敢放手。希望帮到你。本文还有配套的精品资源点击获取
返回列表