
简介这是一套面向数据分析初学者与二手电商从业者的数据采集与分析工具集聚焦闲鱼平台商品市场洞察解决用户难以系统获取、结构化存储及可视化解读闲鱼搜索数据的痛点。资源包含13个文件以8个Python脚本为核心涵盖爬虫调度、JSON解析、MongoDB写入、多维统计与图表生成辅以2个说明文档txt与docx、1个配置文件settings.py及README.md等工程支撑文件整体压缩包仅45KB轻量易部署。已有53人学习下载适合希望快速搭建本地分析流水线的Python入门者或运营人员。使用者可直接运行main.py完成从搜索结果抓取、结构化解析、MongoDB持久化到价格分布热力图、地域占比环形图、类目词云及评价情感趋势图的全流程分析代码模块清晰、注释完整具备良好可读性与二次开发基础。1. 项目概述与核心价值最近在捣鼓一个挺有意思的小工具起因是我自己经常在闲鱼上淘点二手宝贝也偶尔出点闲置。时间长了就发现一个问题看商品全凭感觉和运气。比如想买个二手显卡搜出来价格从几百到几千都有哪个价位段是主流卖家都集中在哪些城市某个品类的商品比如“数码相机”下面又细分为微单、单反、卡片机各自占比如何好评率高的卖家有什么特征这些问题光靠手动翻几十页搜索结果看得眼花缭乱也得不出个准数。于是我就琢磨着能不能写个工具把闲鱼搜索结果的“底裤”给扒下来看看。这个工具的核心任务很明确自动化地抓取并解析闲鱼搜索结果的JSON数据然后存到MongoDB数据库里最后进行多维度分析和可视化展示。听起来好像挺复杂但其实拆解开来就是数据获取、数据存储、数据分析、数据展示四个标准步骤。它本质上是一个垂直领域的市场数据分析工具目标用户可以是像我这样的普通买家/卖家用于辅助决策也可以是做二手商品倒卖的“闲鱼玩家”用来发现价格洼地和热门商品甚至可以是研究消费行为的学生或分析师。这个项目的价值在于“降维打击”。当别人还在凭经验和感觉时你已经能通过数据看到市场的全貌价格分布曲线告诉你什么价位最稳妥地理位置热力图告诉你从哪里发货最快商品类别饼图告诉你当前什么最火爆评价数据统计帮你筛选出更靠谱的卖家。这一切都始于那一串串看似杂乱、实则信息量巨大的JSON数据。2. 技术栈选型与设计思路工欲善其事必先利其器。做一个数据分析工具技术选型直接决定了开发效率和最终效果。下面我详细说说为什么选这套组合拳。2.1 为什么是JSON解析而不是网页爬虫闲鱼和其他很多现代Web应用一样采用了前后端分离的架构。你在网页上看到的商品列表并不是服务器直接返回一个完整的HTML而是浏览器先加载一个基础页面然后通过JavaScript调用后端API接口接口返回结构化的JSON数据再由前端渲染成你看到的卡片。直接去爬网页HTML你需要面对复杂的标签结构、动态加载内容以及可能随时变动的CSS选择器难度大且不稳定。而直接获取API返回的JSON数据就相当于“抄了近道”。JSON本身就是一种轻量级的数据交换格式结构清晰包含了商品最核心的元信息如价格、标题、地点、卖家信息等且数据非常干净没有冗余的展示标签。我们只需要找到正确的API请求模拟它就能稳定高效地拿到高质量的结构化数据。这是本项目最核心的技术取巧点。2.2 为什么选择MongoDB数据分析项目数据库选型至关重要。我放弃了传统的关系型数据库如MySQL而选择了MongoDB主要基于以下几点考量模式灵活Schema-less闲鱼的商品数据字段并非一成不变。不同类别的商品可能有不同的属性比如手机有“内存”字段书本有“作者”字段甚至同一API在不同时间返回的字段也可能有细微调整。MongoDB的文档模型允许我直接存储JSON数据无需预先定义严格的表结构插入新字段就像在JSON对象里加个键值对一样简单这极大地提高了开发迭代速度和应对变化的灵活性。JSON原生支持MongoDB使用BSONBinary JSON格式存储数据与我们获取的JSON数据是天作之合。数据从网络请求到入库几乎不需要做复杂的格式转换写入和读取的效率非常高。强大的聚合框架Aggregation Framework这是MongoDB的“杀手锏”也是我选择它的最重要原因。后续的多维度分析比如“计算每个城市商品的平均价格”、“统计各类别商品的数量分布”这些操作如果放在关系型数据库里需要写复杂的SQL JOIN和GROUP BY语句。而在MongoDB中使用聚合管道$group,$match,$project,$sort等可以用一种更直观、更类似于数据处理流水线的方式来完成特别适合数据分析场景。扩展性与性能对于海量的商品数据MongoDB的横向扩展分片能力很强。虽然我们这个个人项目初期可能数据量不大但良好的架构设计要为未来留出空间。2.3 可视化方案的选择可视化是让数据说话的最后一环。我的选择是Python Matplotlib/Seaborn PyEcharts的组合。Matplotlib/Seaborn是Python生态的基石功能强大且稳定用于绘制一些标准的统计图表如价格分布的直方图与核密度估计图、类别占比的饼图或柱状图。Seaborn基于Matplotlib提供了更美观的默认样式和更高级的统计图表接口。PyEcharts这是一个将百度Echarts封装到Python中的库。我主要用它来生成交互性更强的图表比如地理位置热力图。它可以轻松地将城市名映射到地图上并用颜色深浅表示商品数量或平均价格直观地展示地域分布特征。生成HTML文件后可以直接在浏览器中交互查看体验非常好。这套技术栈Python爬虫/请求库 - JSON解析 - MongoDB存储 - 聚合分析 - Matplotlib/Seaborn/PyEcharts可视化形成了一个完整、高效且现代化的数据流水线。3. 核心实现步骤拆解接下来我们进入实战环节把整个工具的实现流程走一遍。我会尽量详细并附上关键代码和避坑指南。3.1 第一步捕获与解析闲鱼搜索JSON数据这是所有工作的源头。首先你需要使用Chrome或Edge浏览器的开发者工具。打开闲鱼网站搜索你感兴趣的关键词比如“iPhone 13”。按F12打开开发者工具切换到“Network”网络选项卡。清空当前列表然后刷新页面或滚动商品列表触发新的加载。在网络请求列表中仔细寻找类型为“XHR”或“Fetch”的请求。这些通常是API请求。请求的URL可能包含“mtop.taobao”、“h5api.m.taobao”、“s.taobao.com”或“acs.m.taobao.com”等域名路径中常带有“search”、“item”等字样。点击一个看起来像返回商品列表的请求查看其“Preview”预览或“Response”响应标签页。如果你看到的是结构清晰的JSON数据里面包含items、list这样的数组数组里的对象有title、price、location等字段那么恭喜你找到目标了。记录下这个请求的URL、请求方法通常是GET以及关键的请求头Headers特别是Cookie和User-Agent。Cookie包含了你的登录和会话信息是模拟请求的关键。注意闲鱼的接口可能有反爬机制频繁或大量请求可能导致IP被暂时限制。务必在请求中设置合理的延时如time.sleep(random.uniform(1, 3))并轮换User-Agent。这是道德和法律的红线请仅用于个人学习和小规模数据分析切勿用于商业爬取或给目标服务器造成压力。找到接口后我们用Python的requests库来模拟请求。这里假设我们找到了一个接口。import requests import json import time import random def fetch_xianyu_search(keyword, page1): 模拟请求闲鱼搜索接口 url https://s.taobao.com/api?_input_charsetutf-8 # 示例URL实际需替换 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36..., Cookie: 你的Cookie字符串很长..., Referer: https://s.taobao.com/ } params { q: keyword, s: (page - 1) * 44, # 闲鱼每页可能是44条 tab: all, _input_charset: utf-8 } try: response requests.get(url, headersheaders, paramsparams, timeout10) response.raise_for_status() # 检查请求是否成功 # 闲鱼的返回数据可能是一段JSONP需要先提取JSON部分 data_text response.text if data_text.startswith(jsonp): # 去除JSONP回调函数包裹例如 jsonp123(...) json_str data_text[data_text.find(()1: data_text.rfind())] data json.loads(json_str) else: data response.json() return data except requests.exceptions.RequestException as e: print(f请求失败: {e}) return None except json.JSONDecodeError as e: print(fJSON解析失败: {e}) print(响应文本:, response.text[:500]) # 打印前500字符帮助调试 return None # 使用示例 data fetch_xianyu_search(机械键盘, 1) if data and items in data.get(result, {}): items data[result][items] print(f获取到 {len(items)} 条商品数据)实操心得闲鱼的接口格式和参数可能会变上述代码中的URL和参数解析逻辑需要你根据实际抓包结果进行调整。最关键的一步是正确解析响应内容。很多时候响应不是纯JSON而是被一个函数包裹的JSONP需要用字符串处理的方法提取出真正的JSON字符串。3.2 第二步设计MongoDB数据存储结构拿到JSON数据后我们需要设计如何存入MongoDB。虽然MongoDB模式灵活但一个好的设计能极大提升查询和分析效率。我建议创建一个名为xianyu_items的集合Collection。每个文档Document对应一个商品。文档结构可以设计如下{ _id: ObjectId(自动生成), item_id: 674328927823, // 商品唯一ID title: 九成新iPhone 13 256G 国行, price: 3800.00, // 价格统一为浮点数 original_price: 4299.00, // 原价如果有 location: 广东 深圳, // 卖家所在地 seller_info: { user_id: user123456, nickname: 淘气的小明, credit_level: 极好 // 信用等级 }, category: 手机, // 主类别 sub_category: 苹果手机, // 子类别 tags: [包邮, 在保, 无拆修], // 商品标签 view_count: 150, // 浏览量 thumb_url: https://img.alicdn.com/...jpg, // 缩略图 detail_url: https://item.taobao.com/..., // 详情页链接 appraise_score: 4.8, // 卖家好评率如果接口提供 created_at: ISODate(2023-10-27T08:00:00Z), // 数据抓取时间 search_keyword: iPhone 13 // 搜索关键词便于区分不同批次数据 }字段设计解析item_id和_id不同_id是MongoDB的主键而item_id是业务上的商品ID用于去重。在插入数据前可以先查询是否已存在相同item_id的商品避免重复存储。将price等数字字段统一为数值类型如float或int方便后续进行数学计算和聚合。seller_info使用嵌套文档将卖家相关信息组织在一起结构更清晰。created_at记录抓取时间对于分析市场趋势如价格随时间变化至关重要。search_keyword是一个非常重要的维度它让你可以同时分析多个不同关键词下的数据并进行对比。3.3 第三步使用PyMongo将数据写入数据库安装MongoDB和Python驱动pymongo后连接和写入数据就非常简单了。from pymongo import MongoClient, UpdateOne from datetime import datetime def save_to_mongodb(items, keyword): 将商品列表存入MongoDB使用update_one实现upsert存在则更新不存在则插入 # 连接MongoDB默认连接本地27017端口 client MongoClient(mongodb://localhost:27017/) db client[xianyu_market] # 数据库名 collection db[xianyu_items] # 集合名 operations [] # 批量操作列表 for item in items: # 这里需要根据实际API返回的JSON结构提取并转换字段 # 以下是一个示例转换逻辑 doc { item_id: item.get(item_id), title: item.get(title), price: float(item.get(price, 0)) if item.get(price) else 0.0, location: item.get(location), seller_info: { user_id: item.get(seller_id), nickname: item.get(seller_nick), }, category: keyword, # 简单处理实际应从数据中解析 created_at: datetime.utcnow(), # 使用UTC时间 search_keyword: keyword } # 构建一个“更新”操作如果item_id存在则更新否则插入 # filter: 根据item_id查找 # update: 设置要更新的字段$set表示设置这些字段的值 # upsertTrue: 如果不存在则插入 operation UpdateOne( {item_id: doc[item_id]}, {$set: doc}, upsertTrue ) operations.append(operation) if operations: # 执行批量写入效率远高于单条插入 result collection.bulk_write(operations) print(f数据写入完成匹配{result.matched_count}条新增{result.upserted_count}条修改{result.modified_count}条。) else: print(没有有效数据需要写入。) client.close() # 假设items是从上一步fetch_xianyu_search获取的商品列表 # save_to_mongodb(items, 机械键盘)注意事项连接安全如果MongoDB设定了密码连接字符串应为mongodb://username:passwordlocalhost:27017/。生产环境务必使用密码并考虑网络加密。批量操作使用bulk_write配合UpdateOne进行批量 upsert 操作比在循环中单条执行insert_one或update_one效率高几个数量级。错误处理实际代码中应加入更完善的异常处理try...except比如网络中断、重复键冲突等。去重逻辑上述代码通过item_id进行 upsert 实现了去重。这是最有效的方式。如果接口不返回唯一ID可以考虑使用titlepriceseller_id的组合作为去重依据但会有一定误差。3.4 第四步利用MongoDB聚合框架进行多维度分析数据存好了重头戏就是分析。MongoDB的聚合管道非常强大。下面举几个典型分析场景的例子。场景一分析“机械键盘”的价格分布from pymongo import MongoClient client MongoClient(localhost, 27017) db client[xianyu_market] collection db[xianyu_items] pipeline_price [ { $match: { search_keyword: 机械键盘, price: {$gt: 0, $lt: 5000} # 过滤掉异常价格 } }, { $bucket: { groupBy: $price, boundaries: [0, 100, 200, 300, 500, 800, 1000, 1500, 2000, 5000], # 自定义价格区间 default: other, output: { count: {$sum: 1}, avgPrice: {$avg: $price} } } }, { $sort: {_id: 1} # 按价格区间排序 } ] price_stats list(collection.aggregate(pipeline_price)) for stat in price_stats: print(f价格区间 {stat[_id]}: {stat[count]} 件商品平均价 {stat[avgPrice]:.2f})这个聚合管道先匹配关键词然后使用$bucket操作符将商品按价格分段并计算每个区间的商品数量和平均价格。结果可以直接用于绘制直方图。场景二统计商品的地理位置分布pipeline_location [ { $match: {search_keyword: 机械键盘} }, { $group: { _id: $location, # 按地理位置分组 count: {$sum: 1}, avgPrice: {$avg: $price} } }, { $sort: {count: -1} # 按商品数量降序排列 }, { $limit: 20 # 只取前20个城市 } ] location_stats list(collection.aggregate(pipeline_location)) for loc in location_stats: print(f{loc[_id]}: {loc[count]} 件均价 {loc[avgPrice]:.2f})这个分析可以找出“机械键盘”卖家最集中的城市以及不同城市的均价差异。$limit用于控制输出数量避免数据太多。场景三分析类别占比如果数据中有分类字段假设我们通过其他方式如从标题提取为商品添加了category字段。pipeline_category [ { $match: {search_keyword: 数码产品} }, { $group: { _id: $category, count: {$sum: 1} } }, { $sort: {count: -1} } ] category_stats list(collection.aggregate(pipeline_category)) total sum([c[count] for c in category_stats]) for cat in category_stats: percentage (cat[count] / total) * 100 print(f{cat[_id]}: {cat[count]} 件占比 {percentage:.2f}%)场景四分析卖家信用与价格/销量的关系pipeline_seller [ { $match: {search_keyword: iPhone 13, seller_info.credit_level: {$exists: True}} }, { $group: { _id: $seller_info.credit_level, # 按信用等级分组 count: {$sum: 1}, avgPrice: {$avg: $price}, maxPrice: {$max: $price}, minPrice: {$min: $price} } }, { $sort: {_id: 1} # 信用等级可能有顺序如“差”“中等”“好”“极好” } ]这个分析可以帮助你判断信用等级高的卖家是否真的定价更合理均价适中极差小。3.5 第五步使用Matplotlib和PyEcharts进行可视化分析出的数据是冰冷的图表才能让它活起来。1. 价格分布直方图Matplotlib/Seabornimport matplotlib.pyplot as plt import seaborn as sns import pandas as pd # 假设 price_stats 是上面聚合查询的结果 df_price pd.DataFrame(price_stats) # 处理bucket结果_id是区间可能是一个字典这里简单处理 df_price[price_range] df_price[_id].astype(str) plt.figure(figsize(12, 6)) # 使用Seaborn绘制条形图更美观 bar_plot sns.barplot(datadf_price, xprice_range, ycount, paletteviridis) plt.title(闲鱼“机械键盘”商品价格分布, fontsize15) plt.xlabel(价格区间元) plt.ylabel(商品数量) plt.xticks(rotation45) # 旋转x轴标签避免重叠 # 在柱子上方显示数量 for p in bar_plot.patches: bar_plot.annotate(format(p.get_height(), .0f), (p.get_x() p.get_width() / 2., p.get_height()), ha center, va center, xytext (0, 9), textcoords offset points) plt.tight_layout() plt.savefig(price_distribution.png, dpi300) plt.show()2. 地理位置热力图PyEcharts这是本项目的亮点。你需要将城市名转换为经纬度坐标。可以预先准备一个城市-坐标的映射字典或者使用地理编码API需谨慎可能有调用限制。from pyecharts import options as opts from pyecharts.charts import Geo from pyecharts.globals import ChartType, SymbolType # 假设 location_stats_for_map 是一个列表元素如 {name: 广东 深圳, value: 150} # 其中‘value’可以是商品数量或平均价格 data_pairs [(item[name], item[value]) for item in location_stats_for_map] geo ( Geo(init_optsopts.InitOpts(width1200px, height600px)) .add_schema(maptypechina) .add( 商品数量, data_pairs, type_ChartType.HEATMAP, # 使用热力图类型 label_optsopts.LabelOpts(is_showFalse), ) .set_global_opts( visualmap_optsopts.VisualMapOpts( min_min([v for _, v in data_pairs]), max_max([v for _, v in data_pairs]), is_piecewiseFalse, range_color[#313695, #4575b4, #74add1, #abd9e9, #e0f3f8, #ffffbf, #fee090, #fdae61, #f46d43, #d73027, #a50026] ), title_optsopts.TitleOpts(title闲鱼商品地理位置分布热力图), ) ) geo.render(geo_heatmap.html) # 生成一个独立的HTML文件打开生成的geo_heatmap.html你将看到一个可交互的中国地图颜色越深的地方表示商品数量越多或平均价格越高鼠标悬停可以查看具体数值。3. 类别占比饼图PyEchartsfrom pyecharts.charts import Pie # 假设 category_stats 是上面聚合查询的结果 cate_data [(item[_id], item[count]) for item in category_stats] pie ( Pie() .add( , cate_data, radius[30%, 75%], # 环形图 center[50%, 50%], label_optsopts.LabelOpts(formatter{b}: {c} ({d}%)), ) .set_global_opts( title_optsopts.TitleOpts(title商品类别占比), legend_optsopts.LegendOpts(orientvertical, pos_top15%, pos_left2%), ) .set_series_opts(tooltip_optsopts.TooltipOpts(triggeritem, formatter{a} br/{b}: {c} ({d}%))) ) pie.render(category_pie.html)4. 常见问题与实战避坑指南在实际开发中我踩过不少坑这里总结一下希望能帮你节省时间。4.1 数据抓取阶段请求被拒绝或返回空数据原因最常见的原因是请求头不完整或Cookie失效。闲鱼会校验User-Agent,Referer,Cookie等。解决使用开发者工具仔细比对你的请求和浏览器正常请求的Headers确保关键字段一致。Cookie有有效期需要定期更新。可以考虑使用session对象来保持会话。数据解析失败JSONDecodeError原因响应内容不是纯JSON可能是JSONP、HTML或加密数据。解决打印出response.text的前几百个字符仔细查看。如果是JSONP按上文方法提取如果是加密数据可能需要逆向分析JavaScript这难度较大对于学习项目建议先寻找未加密或加密简单的接口。IP被限制访问原因请求频率过高。解决在请求间增加随机延时time.sleep(random.uniform(2, 5))。对于大规模抓取应考虑使用代理IP池。再次强调务必控制请求频率遵守 robots.txt尊重网站负载。4.2 数据存储与分析阶段MongoDB连接失败原因MongoDB服务未启动或防火墙阻止了27017端口。解决在终端运行sudo systemctl status mongod(Linux) 或检查服务状态 (Windows) 确认服务已运行。确保连接字符串正确。聚合查询速度慢原因数据量增大后没有索引的聚合操作会进行全集合扫描。解决为常用的查询字段建立索引。例如如果经常按search_keyword和price筛选可以创建复合索引collection.create_index([(search_keyword, 1), (price, 1)])。使用explain()方法分析查询执行计划。地理位置分析时城市名不规范原因闲鱼返回的location字段可能是“上海”、“上海浦东”、“上海市”等无法统一映射。解决在数据清洗阶段写一个规则函数来规范化地名。例如将包含“市”、“区”的后缀去掉或者建立一个常见城市别名的映射表。对于PyEcharts地图它识别标准行政区划名如“北京市”、“广东省”。4.3 可视化阶段PyEcharts地图不显示或城市位置错误原因PyEcharts内置的地图数据可能版本较旧或者你使用的城市名不在其标准地名库中。解决确保城市名是标准的省级或市级名称如“北京市”、“浙江省”。可以尝试注册并引入最新的地图文件geo.add_schema(maptypechina, ...)。对于更精细的区县级别需要自行获取并注册对应的GeoJSON数据这比较复杂。图表样式混乱或布局重叠原因Matplotlib在默认情况下图表元素可能比较紧凑。解决多使用plt.tight_layout()自动调整子图参数。使用figsize参数调整整个画布大小。Seaborn在样式上通常比原生Matplotlib更美观可以优先使用。这个项目从构思到实现是一个典型的“数据流水线”构建过程。它不仅仅是一个闲鱼工具更是一个通用的数据获取、存储、分析、展示的练手模板。你可以把数据源换成其他网站把分析维度换成其他指标这套方法论依然适用。最后数据处理一定要心怀敬畏合法合规地使用数据用它来提升效率、发现洞察而不是制造麻烦。本文还有配套的精品资源点击获取