ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于MongoDB聚合与PyECharts的闲鱼市场数据分析实践

基于MongoDB聚合与PyECharts的闲鱼市场数据分析实践 简介这是一套面向数据分析初学者与二手电商从业者的数据采集与分析工具集聚焦闲鱼平台商品市场洞察解决用户难以系统获取、结构化存储及多维可视化闲鱼搜索数据的痛点。资源共13个文件含8个Python脚本涵盖爬虫调度、JSON解析、MongoDB写入、统计分析与图表生成、2个文本配置说明、1份Word文档附赠资源指引、1个Markdown项目说明及1个Git忽略配置整体仅45KB轻量易部署。已有53人学习下载适合希望快速掌握“网页数据→数据库→分析报表”完整链路的实践者。读者可直接运行主程序完成从闲鱼搜索结果抓取、结构化解析、MongoDB持久化到价格分布热力图、地域占比环形图、类目词云及评价情感趋势图等多维度可视化输出代码模块清晰、配置分离、注释充分具备良好可读性与二次开发基础。1. 项目概述与核心价值最近在折腾一个挺有意思的小项目起因是我自己经常在闲鱼上淘点二手宝贝也偶尔出点闲置。时间久了就发现一个问题光靠手动刷、凭感觉判断很难把握整个市场的脉搏。比如想卖个旧手机到底定什么价位合适哪个城市的同类商品最多某个品类的整体评价怎么样这些问题单看一两个搜索结果根本没法回答。于是我就琢磨着能不能写个工具把闲鱼搜索结果的“底裤”给扒下来好好分析分析。这个工具的核心思路很直接模拟用户搜索行为抓取闲鱼返回的JSON格式数据把这些结构化的商品信息清洗、整理后存到MongoDB数据库里。存进去不是目的目的是为了能方便地做多维度分析比如看看价格分布、地理位置热力图、商品类别占比甚至分析一下评价数据。最后再用可视化的方式把这些分析结果直观地展示出来做成一个能辅助决策的“市场仪表盘”。它适合谁呢如果你是闲鱼的深度用户无论是想更聪明地买卖闲置还是想研究某个垂直品类比如二手相机、乐高、中古家具的市场情况这个工具都能给你提供数据层面的支持。对于有点Python和数据分析基础的朋友来说这也是一个非常不错的练手项目涵盖了网络爬虫需注意合规、数据解析、NoSQL数据库操作、数据分析和可视化这一整套数据流水线的实践。2. 整体架构设计与技术选型2.1 为什么是JSON和MongoDB闲鱼以及很多现代Web应用的前后端数据交互大量使用JSON格式。当你滚动搜索结果页面时浏览器会向服务器发送请求服务器返回的往往就是一个结构清晰的JSON对象里面包含了商品列表、分页信息等。直接解析这个JSON比去解析完整的HTML页面要高效、稳定得多因为HTML结构可能会经常变动而API接口的JSON结构相对稳定。那么数据抓下来存到哪里我选择了MongoDB主要基于以下几点考虑模式灵活闲鱼的商品信息字段可能不尽相同有的商品有“验货宝”标签有的没有有的卖家提供了视频有的只有图片。MongoDB的文档模型允许每个文档即每条商品记录拥有不同的结构插入数据时无需预先定义严格的表结构这种灵活性非常适合处理从网络抓取的、可能变化的数据。JSON原生支持MongoDB的文档采用BSONBinary JSON格式存储与我们抓取的JSON数据可以说是“天生一对”。Python里用pymongo库几乎可以无缝地将字典dict数据插入数据库省去了大量在关系型数据库中需要的“序列化/反序列化”或“ORM映射”的步骤。查询与聚合能力强我们后续要做多维度分析比如“计算每个城市手机类商品的平均价格”这涉及到分组group、筛选match、统计sum, avg等操作。MongoDB提供了强大的聚合管道Aggregation Pipeline功能可以非常直观地表达复杂的数据处理逻辑直接在数据库层面完成计算效率很高。开发效率高对于这样一个偏向探索和数据分析的项目快速迭代是关键。MongoDB的简单易用让我能更专注于业务逻辑而不是数据库的维护和复杂的SQL语句编写。当然这并不意味着关系型数据库如MySQL、PostgreSQL不好。如果你的分析维度非常固定且需要严格的关联查询和事务支持关系型数据库可能是更好的选择。但在这个项目里面对半结构化的网络数据和对灵活分析的需求MongoDB的优势更明显。2.2 工具链与依赖库整个项目我用Python来实现主要依赖以下库请求与解析requests用于发送HTTP请求获取搜索接口返回的JSON数据。json是Python标准库用于解析JSON字符串。数据库操作pymongo是官方推荐的Python驱动用于连接和操作MongoDB数据库。数据处理与分析pandas是数据分析的核心库虽然MongoDB聚合管道很强但有时将数据拉取到内存中用pandas进行更灵活的分析或清洗也很方便。numpy作为数值计算的基础支撑。数据可视化matplotlib和seaborn用于生成静态的统计图表如分布直方图、箱线图。pyecharts或plotly用于生成交互性更强的网页图表比如可缩放的地图、可筛选的仪表盘。本项目示例会侧重pyecharts因为它与Python集成好生成HTML文件即可分享。环境与配置管理使用.env文件管理数据库连接字符串等敏感信息用python-dotenv读取。项目结构用pipenv或poetry管理虚拟环境和依赖保证复现性。注意合规与伦理边界在开始之前必须强调一点任何数据抓取行为都必须遵守目标网站的robots.txt协议尊重服务器的负载避免高频请求。本项目的目的是技术学习和市场趋势分析严禁用于大规模爬取、侵犯隐私、进行价格垄断或任何干扰闲鱼平台正常运营的行为。在实际操作中务必添加合理的请求间隔如time.sleep(random.uniform(1, 3))模拟人类操作并只抓取公开可访问的搜索结果信息。3. 核心环节一抓取与解析闲鱼搜索JSON3.1 定位与模拟搜索接口这是第一步也是最关键的一步。你不能直接去爬闲鱼的HTML页面那样效率低且容易被反爬。正确的方法是找到其内部API接口。打开开发者工具在浏览器中打开闲鱼网站按F12进入“网络”Network选项卡。执行一次搜索在闲鱼搜索框输入关键词例如“iPhone 13”点击搜索。筛选XHR/Fetch请求在网络请求列表中筛选出XHR或Fetch类型的请求。你会看到一系列请求其中通常包含类似“search”、“list”、“item”等字样的请求。分析关键请求逐个点击这些请求查看其“预览”Preview或“响应”Response标签页。当你看到一个响应内容是结构化的JSON并且里面包含了商品列表data或items字段下是商品信息数组时就找到了目标接口。记录请求详情记下这个请求的URL、请求方法通常是GET、以及最重要的请求头Headers特别是Cookie和User-Agent。有时还需要关注URL中的查询参数Query Parameters比如q关键词、sort排序、page页码等。一个典型的请求URL可能看起来像https://s.2.taobao.com/list/list.htm?qiPhone13search_typeitempage1但更可能是类似https://acs.m.taobao.com/gw/mtop.taobao.idle.search/1.0/这样的API网关地址。具体地址需要你通过上述方法实时分析获取因为接口可能会变动。3.2 构建Python爬虫脚本拿到接口信息后就可以用Python来模拟请求了。import requests import json import time import random from typing import Dict, Any, List def fetch_xianyu_search(keyword: str, page: int 1) - Dict[str, Any]: 模拟请求闲鱼搜索接口 # 这里填入你分析得到的真实API URL (示例占位非真实地址) url https://acs.m.taobao.com/gw/mtop.taobao.idle.search/1.0/ # 关键构造请求头模拟浏览器。Cookie需要从浏览器中复制并定期更新。 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36, Accept: application/json, Accept-Language: zh-CN,zh;q0.9, Cookie: 你的Cookie字符串很长..., # 重要需要替换 Referer: https://s.2.taobao.com/ } # 构造查询参数参数名需根据实际接口确定 params { q: keyword, sort: default, # 可以是‘default’‘new’‘priceAsc’‘priceDesc’等 page: page, appName: ..., # 可能需要的其他参数 data: json.dumps({searchReq:{...}}) # 有时参数在data字段中以JSON字符串传递 } try: # 发送GET请求 response requests.get(url, headersheaders, paramsparams, timeout10) response.raise_for_status() # 检查HTTP错误 # 解析JSON响应 result_json response.json() # 实际数据结构可能嵌套在如 result[data][items] 下 return result_json except requests.exceptions.RequestException as e: print(f请求失败: {e}) return {} except json.JSONDecodeError as e: print(fJSON解析失败: {e}) return {} # 使用示例 if __name__ __main__: keyword 机械键盘 for page in range(1, 6): # 抓取前5页 print(f正在抓取关键词 {keyword} 第 {page} 页...) data fetch_xianyu_search(keyword, page) if data: # 这里调用后续的数据处理函数 process_and_save(data) # 非常重要设置随机延迟避免请求过快 time.sleep(random.uniform(2, 5))实操心得Cookie管理Cookie会过期。对于需要长期运行的任务可以考虑使用session对象并模拟登录流程更复杂或者准备一个Cookie池定期更换。本项目作为学习演示手动从浏览器复制短期可用的Cookie是最快的方式。参数动态化除了q和page排序方式sort、筛选条件如价格区间、地理位置都可能作为参数。你需要根据想分析的具体维度调整这些参数来抓取不同切片的数据。错误处理与重试网络请求不稳定一定要有完善的try-except和重试机制。对于请求失败或返回空数据的页面可以记录日志并跳过。3.3 解析JSON数据结构拿到response.json()返回的字典后你需要像剥洋葱一样一层层找到真正的商品列表。def extract_items(raw_data: Dict[str, Any]) - List[Dict[str, Any]]: 从原始响应数据中提取商品信息列表 实际路径需要根据接口响应结构调整 items [] # 假设商品列表在 data - result - items 路径下 # 你需要根据实际的JSON结构来调整这个路径 try: # 这是一个示例路径务必根据你抓取的实际数据调整 item_list raw_data[data][result][items] for item in item_list: # 提取我们需要的关键字段 parsed_item { item_id: item.get(itemId, ), title: item.get(title, ).strip(), price: float(item.get(price, 0)) / 100.0, # 注意单位可能是分 original_price: float(item.get(originalPrice, 0)) / 100.0, location: item.get(location, ), seller_nickname: item.get(seller, {}).get(nickname, ), category: item.get(categoryName, ), view_count: item.get(viewCount, 0), like_count: item.get(likeCount, 0), description: item.get(description, ), images: item.get(images, []), url: fhttps://2.taobao.com/item.htm?id{item.get(itemId, )}, crawl_time: time.strftime(%Y-%m-%d %H:%M:%S) # 记录抓取时间 } # 可以在这里添加更多字段清洗逻辑如去除价格异常值 if parsed_item[price] 0: # 过滤掉价格为0或异常的数据 items.append(parsed_item) except KeyError as e: print(f解析JSON结构时出错关键字段缺失: {e}) print(f原始数据片段: {json.dumps(raw_data, indent2)[:500]}...) return items提示闲鱼的接口返回字段非常丰富除了上述基础信息还可能包含卖家信用、商品标签如“包邮”、“验货宝”、发布时间、运费、是否可小刀等。在extract_items函数中你应该尽可能多地提取这些字段为后续的多维度分析储备“弹药”。同时记得做好异常值处理比如价格异常高或为0的商品。4. 核心环节二数据存储与MongoDB操作4.1 MongoDB连接与集合设计首先确保你已安装并运行了MongoDB服务。然后通过pymongo连接。from pymongo import MongoClient, ASCENDING, DESCENDING from pymongo.errors import DuplicateKeyError, ConnectionFailure import os from dotenv import load_dotenv load_dotenv() # 从.env文件加载环境变量 def get_mongo_client(): 创建并返回MongoDB客户端连接 # 从环境变量读取连接字符串避免硬编码 # MONGO_URI mongodb://localhost:27017/ 本地示例 MONGO_URI os.getenv(MONGO_URI, mongodb://localhost:27017/) try: client MongoClient(MONGO_URI, serverSelectionTimeoutMS5000) # 触发一个简单命令来测试连接 client.admin.command(ping) print(成功连接到MongoDB.) return client except ConnectionFailure as e: print(f连接MongoDB失败: {e}) return None # 使用 client get_mongo_client() if client: db client[xianyu_market] # 数据库名 collection db[search_items] # 集合名类似于表集合设计思考我将所有搜索到的商品都放在一个search_items集合里。每条文档就是一个商品。为了高效查询和去重需要创建索引。# 在集合上创建索引通常只需执行一次 collection.create_index([(item_id, ASCENDING)], uniqueTrue) # 商品ID唯一索引防止重复插入 collection.create_index([(crawl_time, DESCENDING)]) # 按抓取时间倒序方便查最新数据 collection.create_index([(category, ASCENDING), (price, ASCENDING)]) # 复合索引用于按品类和价格查询 print(索引创建完成。)uniqueTrue确保了同一商品不会被重复抓取入库。如果尝试插入重复item_id的数据MongoDB会抛出DuplicateKeyError我们需要捕获它。4.2 数据插入与更新策略有了连接和集合就可以将解析好的商品数据插入数据库了。def save_to_mongodb(items: List[Dict[str, Any]], collection): 将商品列表保存到MongoDB使用更新插入模式 if not items: print(没有数据需要保存。) return operations [] for item in items: # 构造更新操作如果item_id存在则更新如更新浏览量否则插入 # 使用 $setOnInsert 确保只在插入时设置某些字段更新时保留原值或更新特定字段 operation { updateOne: { filter: {item_id: item[item_id]}, # 根据item_id查找 update: { $setOnInsert: { # 只在插入时设置的字段 title: item[title], seller_nickname: item[seller_nickname], category: item[category], description: item[description], images: item[images], url: item[url] }, $set: { # 每次都会更新的字段如果变化 price: item[price], location: item[location], view_count: item[view_count], like_count: item[like_count], last_updated: item[crawl_time] }, $inc: {crawl_version: 1} # 每次更新使版本号1可用于追踪更新次数 }, upsert: True # 如果不存在则插入 } } operations.append(operation) if operations: try: result collection.bulk_write(operations, orderedFalse) # orderedFalse 加快速度某条失败不影响其他 print(f数据写入完成。插入: {result.upserted_count}, 修改: {result.modified_count}。) except Exception as e: print(f批量写入数据库时发生错误: {e})为什么用bulk_write和upsert性能bulk_write将多个写操作打包发送比循环执行单条insert_one或update_one快得多。去重与更新使用upsertupdate insert模式可以智能地处理商品信息的更新。比如同一个商品今天和明天的价格、浏览量可能不同这个操作能更新这些变化字段同时保持商品的其他固有信息不变。$setOnInsert确保了只在第一次插入时写入那些不变的字段如标题、卖家避免了不必要的覆盖。5. 核心环节三多维度数据分析与聚合数据存进MongoDB后就可以大展身手进行分析了。这里主要利用MongoDB的聚合管道。5.1 价格分布分析分析某个品类如“iPhone 13”的价格分布看看大部分商品集中在什么价位段。def analyze_price_distribution(collection, category_filterNone, price_range(0, 10000)): 分析价格分布返回按价格区间分组的数据 pipeline [] # 1. 匹配阶段筛选数据 match_stage {$match: {price: {$gt: price_range[0], $lt: price_range[1]}}} if category_filter: match_stage[$match][category] category_filter pipeline.append(match_stage) # 2. 分组阶段按价格区间分组 # 假设我们按每500元一个区间分组 bucket_size 500 bucket_stage { $bucket: { groupBy: $price, boundaries: list(range(price_range[0], price_range[1] bucket_size, bucket_size)), default: Other, # 超出范围的价格 output: { count: {$sum: 1}, avgPrice: {$avg: $price}, items: {$push: {title: $title, price: $price, location: $location}} } } } pipeline.append(bucket_stage) # 3. 排序阶段按价格区间排序 sort_stage {$sort: {_id: 1}} # _id 就是分桶的区间下限 pipeline.append(sort_stage) try: results list(collection.aggregate(pipeline)) return results except Exception as e: print(f聚合查询失败: {e}) return [] # 使用示例分析“机械键盘”的价格分布0-2000元 price_stats analyze_price_distribution(collection, category_filter机械键盘, price_range(0, 2000)) for bucket in price_stats: print(f价格区间 {bucket[_id]}: 商品数 {bucket[count]}, 平均价 {bucket[avgPrice]:.2f})这个聚合管道做了三件事先筛选出指定品类和价格区间的商品然后按500元的步长将价格分桶最后按桶排序。结果可以直接用来画直方图。5.2 地理位置特征分析分析商品发布地的分布找出某个品类的热点城市。def analyze_location_distribution(collection, category_filterNone, limit20): 分析商品发布地理位置分布返回发布量最多的城市 pipeline [] match_stage {$match: {location: {$ne: , $exists: True}}} if category_filter: match_stage[$match][category] category_filter pipeline.append(match_stage) # 按地理位置分组计数 group_stage { $group: { _id: $location, # 以location字段分组 count: {$sum: 1}, avgPrice: {$avg: $price} } } pipeline.append(group_stage) # 按数量降序排序 sort_stage {$sort: {count: -1}} pipeline.append(sort_stage) # 限制返回数量 limit_stage {$limit: limit} pipeline.append(limit_stage) try: results list(collection.aggregate(pipeline)) # 结果可能包含“上海”、“北京朝阳”等可能需要进一步清洗如提取市级地名 return results except Exception as e: print(f地理位置聚合失败: {e}) return [] # 使用示例 location_stats analyze_location_distribution(collection, category_filter笔记本电脑, limit15) for loc in location_stats: print(f城市: {loc[_id]}, 商品数量: {loc[count]}, 平均价格: {loc[avgPrice]:.2f})实操心得闲鱼的location字段可能包含省、市、区信息甚至有些是“未知”或“线上”。为了更精确的地理分析你可能需要接入一个地理编码API如高德、百度地图的逆地理编码服务将文本地址解析出标准的城市名称或者使用简单的字符串匹配规则来提取市级单位。5.3 类别占比与交叉分析分析所有抓取商品中不同类别的占比情况。或者进行交叉分析比如“不同城市下手机类商品的平均价格”。def analyze_category_ratio(collection): 分析商品类别占比 pipeline [ {$match: {category: {$ne: , $exists: True}}}, {$group: { _id: $category, count: {$sum: 1}, avgPrice: {$avg: $price} }}, {$sort: {count: -1}}, {$limit: 10} ] results list(collection.aggregate(pipeline)) total sum([r[count] for r in results]) for cat in results: ratio (cat[count] / total) * 100 if total 0 else 0 print(f类别: {cat[_id]:15s} 数量: {cat[count]:4d} 占比: {ratio:5.2f}% 均价: {cat[avgPrice]:.2f}) return results def cross_analysis_price_by_location_and_category(collection, top_n_categories5, top_n_locations10): 交叉分析热门品类在热门城市的价格情况 # 先找出热门品类 top_categories [cat[_id] for cat in analyze_category_ratio(collection)[:top_n_categories]] all_results {} for category in top_categories: pipeline [ {$match: {category: category, location: {$ne: }}}, {$group: { _id: $location, avgPrice: {$avg: $price}, itemCount: {$sum: 1} }}, {$match: {itemCount: {$gt: 5}}}, # 只考虑有足够样本的城市 {$sort: {itemCount: -1}}, {$limit: top_n_locations} ] city_stats list(collection.aggregate(pipeline)) all_results[category] city_stats return all_results交叉分析的结果是一个嵌套字典非常适合用热力图或分组柱状图来可视化可以直观看出“数码产品在上海的平均价格”与“在广州的平均价格”的差异。6. 核心环节四数据可视化展示分析出的数据是冰冷的数字可视化才能让它讲故事。这里我们用pyecharts来生成交互式图表。6.1 价格分布直方图from pyecharts import options as opts from pyecharts.charts import Bar, Pie, Map, Grid from pyecharts.globals import ThemeType def draw_price_histogram(price_bucket_data, title商品价格分布): 根据聚合出的价格分桶数据绘制直方图 price_bucket_data: analyze_price_distribution 函数返回的结果 x_data [f{bucket[_id]}-{bucket[_id]499} for bucket in price_bucket_data] # 区间标签 y_data [bucket[count] for bucket in price_bucket_data] bar ( Bar(init_optsopts.InitOpts(themeThemeType.LIGHT, width1000px, height600px)) .add_xaxis(x_data) .add_yaxis(商品数量, y_data, category_gap50%) # 设置柱间间隙 .set_global_opts( title_optsopts.TitleOpts(titletitle), xaxis_optsopts.AxisOpts(name价格区间元, axislabel_optsopts.LabelOpts(rotate-45)), # X轴标签旋转防重叠 yaxis_optsopts.AxisOpts(name商品数量), tooltip_optsopts.TooltipOpts(triggeraxis, axis_pointer_typeshadow), datazoom_opts[opts.DataZoomOpts()] # 添加数据区域缩放 ) .set_series_opts( label_optsopts.LabelOpts(is_showFalse), itemstyle_optsopts.ItemStyleOpts(color#5470c6) ) ) # 渲染到HTML文件 bar.render(fprice_distribution_{title}.html) print(f价格分布图已生成: price_distribution_{title}.html) return bar6.2 地理位置热力图需地理坐标要画地图需要将城市名转换为经纬度。我们可以用一个简单的映射字典只包含主要城市或者使用pyecharts内置的Geo组件配合一个包含坐标的JSON文件。def draw_location_heatmap(location_stats, title商品发布地理位置分布): 绘制地理位置分布图点状图或热力图 location_stats: analyze_location_distribution 返回的结果列表 # 准备数据格式为 [(城市名, 数值), ...] # 注意pyecharts的Map/Geo组件需要标准的省份或城市名如‘上海’‘北京’ # 你需要先清洗location字段确保是标准名称 data_pairs [(item[_id], item[count]) for item in location_stats if is_standard_city(item[_id])] if not data_pairs: print(无有效的地理位置数据用于绘制地图。) return None # 使用Map组件中国地图 map_chart ( Map(init_optsopts.InitOpts(themeThemeType.LIGHT, width1000px, height700px)) .add( series_name商品数量, data_pairdata_pairs, maptypechina, is_map_symbol_showFalse, # 不显示标记点 ) .set_global_opts( title_optsopts.TitleOpts(titletitle), visualmap_optsopts.VisualMapOpts( max_max([d[1] for d in data_pairs]) if data_pairs else 100, is_piecewiseFalse, # 连续型视觉映射 range_color[#e0f3f8, #abd9e9, #74add1, #4575b4, #313695] # 蓝绿色系 ), tooltip_optsopts.TooltipOpts(triggeritem, formatter{b}: {c}), ) ) map_chart.render(flocation_heatmap_{title}.html) print(f地理位置分布图已生成: location_heatmap_{title}.html) return map_chart # 一个简单的城市名判断函数示例实际需要更全的列表 def is_standard_city(location_str): standard_cities [北京, 上海, 广州, 深圳, 杭州, 成都, 武汉, 南京, 西安, 重庆] for city in standard_cities: if city in location_str: return True return False6.3 品类占比饼图与仪表盘整合def draw_category_pie(category_stats, title商品类别占比): 绘制品类占比饼图 data_pairs [(cat[_id], cat[count]) for cat in category_stats] pie ( Pie(init_optsopts.InitOpts(themeThemeType.LIGHT, width800px, height600px)) .add( series_name类别, data_pairdata_pairs, radius[30%, 70%], # 环形图 center[50%, 50%], label_optsopts.LabelOpts(formatter{b}: {c} ({d}%)) ) .set_global_opts( title_optsopts.TitleOpts(titletitle, pos_leftcenter), legend_optsopts.LegendOpts(orientvertical, pos_top15%, pos_left2%), tooltip_optsopts.TooltipOpts(triggeritem, formatter{a} br/{b}: {c} ({d}%)), ) .set_series_opts() ) pie.render(fcategory_pie_{title}.html) return pie你可以将多个图表组合到一个HTML页面中形成一个简单的仪表盘。pyecharts的Grid、Tab或Page组件可以帮助实现这一点。7. 常见问题与排查技巧实录在实际开发和运行这个工具的过程中我踩过不少坑。这里把一些典型问题和解决方法记录下来希望能帮你省点时间。7.1 抓取相关问题1请求被拒绝返回403或验证错误。可能原因请求头不完整或Cookie失效。闲鱼的反爬机制会校验User-Agent、Referer、Cookie等。排查用浏览器开发者工具对比你的Python请求和浏览器正常请求的所有请求头确保关键字段一致。检查Cookie是否过期。直接从浏览器复制一个新的。尝试添加其他常见请求头如Accept-Encoding,Connection。如果还不行可能需要处理更复杂的反爬如签名参数_m_h5_tk,_m_h5_tk_enc等。这需要仔细分析网络请求找到参数生成逻辑通常在前端JS代码中并用Python模拟。这难度较大对于学习项目可以暂时降低请求频率或寻找其他更简单的数据源。问题2返回的数据是空的或者结构不对。可能原因搜索接口URL或参数发生了变化。排查重新执行3.1节的步骤确认当前的接口地址和参数格式。打印出你实际请求的完整URL和响应内容的前几百个字符与浏览器中看到的进行对比。检查请求参数是否需要用json.dumps处理后再放入data字段而不是直接以字典形式传递。问题3抓取速度慢且很快被限制。解决必须添加延迟在每次请求之间使用time.sleep(random.uniform(2, 5))模拟真人操作。使用代理IP池如果抓取量非常大单一IP容易被封。可以考虑使用付费或免费的代理IP服务在请求时轮换IP。降低并发避免使用多线程/异步同时发起大量请求。7.2 数据与存储相关问题4插入MongoDB时出现重复键错误DuplicateKeyError。原因你尝试插入的文档item_id与集合中已存在的文档重复且你在item_id字段上创建了唯一索引。解决这正是我们期望的行为说明数据去重生效了。确保你的写入逻辑如save_to_mongodb函数使用的是upsert操作这样重复的数据会被更新而不是报错后中断。bulk_write的orderedFalse参数确保了即使中间某条文档重复也不会影响其他文档的写入。问题5聚合查询速度慢。原因数据量增大后没有索引的字段进行$match或$group操作会非常慢。解决为经常用于查询和分组的字段创建索引。例如如果你经常按category和price做分析那么创建复合索引collection.create_index([(category, ASCENDING), (price, ASCENDING)])会极大提升查询速度。使用collection.index_information()可以查看现有索引。问题6地理位置分析不准确城市名杂乱。原因location字段是用户填写的自由文本格式不一。解决数据清洗编写规则用正则表达式或字符串匹配提取市级名称。例如匹配“市”字前的两个字如“上海市”、“广州市”。使用外部API对于更精确的解析可以将地址字符串发送给高德/百度地图的“地理编码”API返回标准省市区信息。但这会产生API调用成本。模糊处理在可视化时对于无法识别的地址归为“其他”类别。7.3 可视化相关问题7Pyecharts地图不显示或显示不全。原因pyecharts的地图数据需要额外安装。解决安装中国地图包pip install echarts-china-provinces-pypkg echarts-china-cities-pypkg。确保你传递给Map组件的城市名是标准的如“北京”而不是“北京市朝阳区”。问题8生成的HTML图表在浏览器中打开是空白。原因可能是浏览器安全策略阻止了本地JavaScript文件的加载或者pyecharts版本问题。解决使用render_notebook()在Jupyter Notebook中直接查看。生成HTML时使用Page().add(bar, map_chart).render(dashboard.html)将多个图表整合到一个页面有时能避免单个文件的问题。检查浏览器控制台F12是否有JavaScript错误。确保网络通畅能加载pyecharts所需的在线JS库默认使用CDN。你也可以在InitOpts中设置opts.InitOpts(js_hostlocal/)并下载本地JS资源。这个项目从构思到实现最大的体会是“数据驱动”的视角真的能打开新世界。以前在闲鱼上买东西全凭感觉和运气现在至少能知道大概的市场水位在哪里。技术上它串起了从数据获取、清洗、存储到分析、可视化的完整链条每一个环节都有值得深挖的细节。比如如何更稳定地获取数据对抗反爬、如何更高效地清洗非结构化文本商品描述的情感分析、如何设计更合理的数据库模式以支持历史趋势分析等等。如果你有兴趣完全可以把这个工具扩展成一个定时运行的“闲鱼市场监控系统”针对你关注的几个品类每天抓取数据生成价格波动曲线和库存变化报告那会更有意思。最后再次提醒玩数据要遵守规则保持克制把技术用在正道上。本文还有配套的精品资源点击获取
返回列表