简介这份资源是面向计算机相关专业学生与Python爬虫初学者的一套闲鱼平台商品数据抓取实战项目可作为课程设计、期末大作业或毕设参考也适合想通过完整案例巩固爬虫与前后端联调能力的开发者。压缩包共28个文件约9.33MB以11个Python源码为核心配合4个JSON配置、3个Markdown说明文档以及Vue与JavaScript前端文件、HTML页面和少量图标、截图等静态资源整体结构覆盖后端接口、前端展示与项目说明。项目基于FastAPI与Vue搭建围绕二手商品数据的采集与可视化展开目录划分清晰便于按模块阅读与二次修改。目前已有222人学习下载说明其具备一定参考价值。对于需要快速搭建爬虫项目框架、理解数据抓取到可视化完整链路的读者可从中获取可运行的代码结构、依赖配置与说明文档并借助作者答疑解决开发中的具体问题。1. 闲鱼商品爬虫到底抓什么从关键词监控到数据落库的真实链路做闲鱼关键词监控的人十有八九一开始都以为「爬虫」就是 requests 加个循环。真上手才发现闲鱼 PC 端和 App 端的数据根本不是静态 HTML搜索页、详情页、卖家主页各自走不同的接口返回结构还随版本变。这个标题里的「闲鱼商品爬虫-xianyu平台数据抓取」本质是一套围绕商品维度做采集、清洗、入库的工程方案核心产出是结构化的商品数据标题、价格、发布时间、卖家 ID、想要人数、浏览量、商品状态。它解决的是「人工刷闲鱼找货、盯价格、追竞品」的低效问题适合做二手电商选品、价格监控、竞品分析、关键词预警的从业者。新手能照着把最小链路跑通熟手更该关注的是频率控制、字段稳定性和长期可维护性——这三样决定你的爬虫是能跑一周还是能跑一年。2. 抓取链路拆解从搜索入口到商品详情的四段式设计2.1 为什么不能只抓搜索页必须补详情页搜索列表接口返回的字段通常只有商品 ID、标题、价格区间、封面图和「想要」数缺发布时间、卖家信用、商品描述、图片列表这些做选品判断的关键信息。只抓列表你拿到的是一堆「看起来便宜」的标题没法判断是不是钓鱼价、是不是已售、卖家靠不靠谱。所以标准链路是四段关键词搜索拿 ID 列表 → 详情接口补全字段 → 卖家维度做去重和信誉聚合 → 落库打时间戳做价格追踪。这个设计的好处是每段可独立重试搜索页挂了不影响已拿到的 ID 继续补详情。2.2 请求头与签名参数最容易被忽略的翻车点闲鱼接口对请求头敏感尤其是User-Agent、Referer、Cookie里的登录态字段。很多新手直接拿浏览器复制的 cURL 跑第一次成功跑几十次就返回空数据或跳验证。常见做法是把移动端 UA 固定下来Referer 指向对应商品或搜索页Cookie 定期更新。签名参数如sign、t、appKey随接口版本变化不要硬编码抽成独立函数方便替换。import requests, time, random HEADERS { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148, Referer: https://www.goofish.com/, Accept: application/json, } def fetch(url, params, cookie): headers dict(HEADERS) headers[Cookie] cookie # 每次请求间隔随机化降低被识别为机器的概率 time.sleep(random.uniform(1.5, 3.5)) resp requests.get(url, headersheaders, paramsparams, timeout10) if resp.status_code ! 200: raise RuntimeError(fHTTP {resp.status_code}) return resp.json()这段代码的关键点有三个UA 用移动端而非桌面端Referer 必须和请求的资源同域sleep 用随机区间而不是固定值。参数timeout10防止卡死cookie作为参数传入而不是写死方便后续接 Cookie 池。失败时先看状态码403 多半是头或签名问题200 但返回空 data 通常是频率触发或登录态失效。2.3 分页与去重用商品 ID 做主键搜索接口一般用page或offset翻页但闲鱼的结果排序会随时间和个性化变化同一关键词两次翻页可能拿到重复或遗漏。稳妥做法是每页都记录商品 ID用集合去重翻到连续两页无新 ID 就停。入库时以商品 ID 为唯一键用 upsert 更新价格和状态这样同一商品多次出现只保留最新快照历史价格另存一张表。seen set() def crawl_keyword(keyword, max_page20): for page in range(1, max_page 1): data fetch(SEARCH_URL, {q: keyword, page: page}, COOKIE) items data.get(data, {}).get(items, []) if not items: break new_count 0 for it in items: gid it[itemId] if gid in seen: continue seen.add(gid) new_count 1 yield gid # 连续无新增说明结果已翻到底或触发限制 if new_count 0: breakmax_page是保护性上限避免死循环new_count判断比单纯看items是否为空更可靠因为闲鱼有时会返回重复页。yield让抓取和入库解耦后面可以接队列做分布式。2.4 字段清洗价格和时间的坑最多价格字段可能是「¥100」「100元」「面议」时间可能是「刚刚」「3小时前」「2024-05-01」。清洗时统一转成数值和时间戳无法解析的标记为 None 而不是丢弃整条记录。卖家 ID 要做脱敏存储只保留哈希值用于聚合避免原始信息泄露。原始字段问题处理方式price含符号、单位、面议正则提取数字面议置 Nonepublish_time相对时间按抓取时间反推绝对时间want_count可能是「1万」解析为数值万乘 10000seller_id敏感SHA256 后存储3. 工程化落地存储、调度与反爬对抗的取舍3.1 存储选型SQLite 起步PostgreSQL 收尾个人做关键词监控SQLite 足够跑几个月单文件、零配置、方便迁移。数据量上到百万级或要做多关键词并发写入换 PostgreSQL用ON CONFLICT做 upsert配合时间分区表存价格历史。MySQL 也行但 JSON 字段处理不如 PG 顺手。表结构至少两张items存商品最新状态price_history存每次抓到的价格快照。CREATE TABLE items ( item_id TEXT PRIMARY KEY, title TEXT, price NUMERIC, seller_hash TEXT, status TEXT, first_seen TIMESTAMP, last_seen TIMESTAMP ); CREATE TABLE price_history ( item_id TEXT, price NUMERIC, seen_at TIMESTAMP, PRIMARY KEY (item_id, seen_at) );item_id做主键保证幂等seller_hash用于按卖家聚合status记录在售/已售first_seen和last_seen能算出商品存活周期这对判断「是不是长期挂着的钓鱼价」很有用。3.2 调度频率别把「稳定」理解成越快越好很多人问爬虫多久跑一次合适。我的血泪经验是单关键词 10 到 15 分钟一轮全天不超过 100 轮夜间降到 30 分钟一轮。频率越高触发验证的概率越大维护成本指数上升。用 APScheduler 或系统 cron 都行关键是加随机抖动别整点整分跑。from apscheduler.schedulers.blocking import BlockingScheduler import random sched BlockingScheduler() sched.scheduled_job(interval, minutes15) def job(): # 抖动 0-5 分钟打散请求峰值 time.sleep(random.uniform(0, 300)) for kw in KEYWORDS: for gid in crawl_keyword(kw): save_item(gid) sched.start()interval15是基准抖动让实际间隔在 15 到 20 分钟之间浮动。关键词多的时候串行跑别开线程池猛冲串行反而更稳。3.3 反爬对抗的边界哪些能做哪些别碰能做的换 UA、加 Referer、控制频率、Cookie 池轮换、失败重试退避。别碰的破解验证码、模拟登录批量养号、高频压测式抓取。前者是工程优化后者既不稳定也不可持续。遇到验证页就停记录时间点等一段时间再试比硬刚划算。分布式爬虫听起来高级但对闲鱼这种强登录态的场景多 IP 不如多 Cookie且 Cookie 质量比数量重要。4. 避坑与排查五个真实踩过的坑4.1 现象第一次跑通第二次全空原因Cookie 或签名参数有时效浏览器复制的 cURL 里的t和sign只对当次有效。解决把签名逻辑抽成函数每次请求重新生成Cookie 单独维护失效时手动更新或接刷新流程。4.2 现象价格抓回来全是「面议」原因搜索列表页的价格字段和详情页不一致列表页对部分商品只给占位符。解决以详情接口为准列表页价格仅作初筛入库前用详情数据覆盖。4.3 现象翻页到第 5 页后全是重复原因闲鱼搜索结果的个性化排序导致翻页不稳定page参数在深页失效。解决改用offset或时间游标或者只抓前 3 页高频结果深页用关键词变体覆盖。4.4 现象跑一晚上被封第二天全 403原因固定间隔 固定 UA 无重试退避被识别为机器。解决随机 sleep、UA 池、失败后指数退避1s、2s、4s、8s连续失败 5 次停 30 分钟。4.5 现象数据库里同一商品几十条记录原因没用唯一键每次插入新行。解决item_id做主键用 upsert 更新价格历史单独表按时间追加。5. 进阶技巧用价格历史做关键词预警跑通基础链路后真正有价值的是价格历史带来的预警能力。我的习惯是每天凌晨跑一次全量关键词把价格变动超过 15% 的商品挑出来推送到自己的通知渠道。实现上不复杂查price_history里同一item_id最近两条记录算变动率超阈值就触发。def detect_price_drop(conn, threshold0.15): sql SELECT item_id, price, LAG(price) OVER (PARTITION BY item_id ORDER BY seen_at) AS prev_price FROM price_history rows conn.execute(sql).fetchall() alerts [] for item_id, price, prev in rows: if prev and prev 0: change (price - prev) / prev if abs(change) threshold: alerts.append((item_id, prev, price, change)) return alertsLAG窗口函数拿上一条价格threshold0.15是经验值二手商品波动大设太低会天天报警。abs(change)同时抓涨价和降价涨价有时意味着同款稀缺也是信号。验证方法很简单手动找几个你关注的商品看预警是否和实际吻合跑一周调阈值。另一个技巧是给关键词分组比如「相机」「镜头」「配件」分开跑每组独立频率和阈值。相机类更新慢30 分钟一轮够配件类上新快10 分钟一轮。这样资源花在刀刃上也不容易触发限制。最后说个我自己的教训别追求一次抓全先让一条链路稳定跑两周再扩关键词和字段。我最早贪多一次上二十个关键词结果第三天全挂排查花了两天。后来改成一次加两个稳定了再加反而省时间。希望帮到你。本文还有配套的精品资源点击获取