
简介本资源是一套面向Python初学者与数据采集实践者的京东商品评论爬取实战项目聚焦电商用户反馈数据的自动化获取与结构化处理。项目基于requests库构建轻量级爬虫解决商品评价批量采集、情感维度分类正/中/负及本地CSV持久化存储等典型需求适用于市场调研、舆情分析或教学实训场景。压缩包共7个文件含核心爬虫脚本.py、三类情感标签的CSV结果文件电脑_positive.csv等、项目说明文档README.md及开源协议文件整体2.5MB结构简洁开箱即用。已有47人学习下载读者可直接运行脚本获取真实京东评论样本掌握HTTP请求构造、JSON响应解析、异常重试机制及按情感极性分类保存的完整链路同时获得应对基础反爬策略的工程化实践参考。1. 项目概述与核心价值最近在分析一些消费电子产品的市场反馈需要批量获取京东平台上的商品评论数据。手动复制粘贴显然不现实数据量一大效率低不说还容易出错。于是我决定用 Python 写一个爬虫工具核心思路就是利用requests库直接模拟浏览器请求从京东的商品评论接口把数据“拿”下来然后按照商品、评论类型比如好评、中评、差评或者日期进行结构化保存。这个项目听起来简单但实际动手你会发现从绕过反爬机制到处理海量数据每一步都有不少门道。今天我就把自己趟过的路、踩过的坑以及最终稳定运行的方案从头到尾拆解一遍。无论你是刚接触爬虫想练手还是需要类似的数据采集需求这篇内容都能给你提供一份可直接“抄作业”的详细指南。简单来说这个工具能帮你自动化完成三件事第一向京东的评论接口发送请求获取指定商品的评论数据第二解析返回的复杂 JSON 数据提取出我们关心的信息如用户昵称、评论内容、评分、时间、点赞数等第三将这些数据清洗后分类保存到本地文件比如 CSV 或 Excel中方便后续进行数据分析或情感分析。整个过程我们将完全依赖requests这个轻量级库不依赖浏览器自动化工具力求高效、稳定、易于部署。2. 核心思路与架构设计2.1 为什么选择 Requests 而非 Selenium/Scrapy面对网页数据抓取新手常会纠结工具选型。Scrapy 功能强大但略显笨重学习曲线陡峭适合构建大型、复杂的爬虫项目。Selenium 模拟浏览器能处理 JavaScript 动态渲染的页面但资源消耗大、速度慢。对于京东商品评论这种数据其核心内容是通过后端 API接口以 JSON 格式返回的页面本身只是提供了一个展示壳。这意味着我们只要能找到并正确调用这个 API就能直接拿到结构化的数据效率远高于解析整个 HTML 页面。requests库正是发起 HTTP 请求的利器。它轻量、简单、直接非常适合这种“找到接口、模拟请求、解析数据”的场景。我们的爬虫架构将非常清晰一个主循环负责控制爬取页数一个核心函数负责构造请求参数、发送请求、处理响应一个解析函数负责从 JSON 中提取字段最后一个保存函数负责将数据写入文件。整个流程是线性的易于理解和调试。2.2 京东评论接口分析与逆向工程这是整个项目的关键和难点。你不能直接去爬商品详情页的 HTML那里面评论数据是分段加载且混杂了大量无关标签。正确的方法是找到京东评论的数据接口。实操步骤打开浏览器开发者工具以 Chrome 为例打开任意一个京东商品页面例如某个手机的页面。切换到 Network网络标签页刷新页面或点击“商品评价”标签。筛选 XHR/Fetch 请求在纷繁的网络请求中寻找包含“productPageComments”、“club”或“comment”等关键词的请求。通常你会找到一个类似https://club.jd.com/comment/productPageComments.action?的请求。分析请求参数点击这个请求查看它的“Headers”和“Payload”或“Query String Parameters”。你会发现一堆参数其中几个是核心productId: 商品ID从商品页URL中获取。score: 评论类型。0全部1好评2中评3差评4追评5晒图。sortType: 排序类型。5推荐排序6时间排序。page: 页码从0开始。pageSize: 每页条数通常最大可设为10但有些接口可能支持更多。核心发现京东的评论是分页加载的通过改变page参数即可获取不同页的数据。接口返回的是标准的 JSON 格式结构清晰包含了评论列表、总条数、平均分等信息。我们的爬虫就是模拟这个请求。注意京东的接口参数和地址可能会随时间更新。如果上述接口失效你需要重复此步骤找到最新的有效接口。这是爬虫维护的常态。2.3 工具链与依赖库选择除了核心的requests我们还需要几个辅助库来让项目更完善pandas: 数据处理和保存的瑞士军刀。我们将用DataFrame来临时存储和整理数据并轻松导出为 CSV 或 Excel。time/random: 用于在请求间插入随机延时避免请求频率过高触发反爬返回 429 状态码。json: 用于解析接口返回的 JSON 数据。安装非常简单只需一行命令pip install requests pandas。如果要将数据保存为 Excel可能还需要openpyxl库 (pip install openpyxl)。3. 核心代码实现与分步解析下面我将把整个爬虫拆解成几个关键函数并附上详细的代码和注释。3.1 构建请求头与会话直接使用requests.get()可能会被服务器识别为简单的脚本请求。我们需要让请求看起来更像来自一个真实的浏览器。import requests import pandas as pd import time import random def create_session(): 创建一个带有常见浏览器请求头的 requests 会话。 使用会话可以自动管理 cookies在某些场景下更稳定。 session requests.Session() 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, Referer: https://item.jd.com/, # 引用页通常设置为商品页 Accept: application/json, text/plain, */*, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Connection: keep-alive, } session.headers.update(headers) return session关键点解释User-Agent这是最重要的字段之一告诉服务器我们是什么浏览器。这里使用了一个常见的 Chrome 浏览器标识。Referer表示请求是从哪个页面发起的。设置成商品页地址符合正常用户行为逻辑。Accept告诉服务器我们期望接收 JSON 格式的数据。使用Session()对象的好处是它可以跨请求保持某些参数如 cookies有时能简化登录态维护虽然本项目不涉及登录。3.2 核心爬取函数这个函数负责构造URL、发送请求、处理异常和解析初步数据。def fetch_comments_page(session, product_id, page0, score0, page_size10): 获取单页评论数据。 参数: session: requests.Session 对象 product_id: 商品ID (字符串或整数) page: 页码从0开始 score: 评论类型 (0全部1好评2中评3差评) page_size: 每页条数 返回: 解析后的JSON数据 (字典)如果请求失败返回None # 京东评论API基础URL (请根据实际情况确认最新地址) base_url https://club.jd.com/comment/productPageComments.action # 构造请求参数 params { productId: product_id, score: score, sortType: 5, # 推荐排序 page: page, pageSize: page_size, # 有时需要额外的参数如‘callback’或‘_’可通过分析网络请求获得 # _: int(time.time() * 1000), # 时间戳防缓存 } try: # 发送GET请求 response session.get(base_url, paramsparams, timeout10) # 检查HTTP状态码 response.raise_for_status() # 打印状态码和URL便于调试 print(f正在抓取 商品{product_id} 第{page1}页状态码: {response.status_code}) # 解析JSON响应 # 注意京东返回的数据可能被一个函数包裹如 fetchJSON_commentXXXX({...}) # 我们需要提取出括号内的JSON字符串 text response.text if text.startswith(fetchJSON_comment) and text.endswith();): # 去除函数包裹提取纯JSON json_str text[text.find({): text.rfind(})1] data json.loads(json_str) else: # 如果不是包裹格式直接解析 data response.json() return data except requests.exceptions.RequestException as e: print(f请求第{page1}页时发生错误: {e}) return None except json.JSONDecodeError as e: print(f解析第{page1}页JSON数据时发生错误: {e}) print(f原始响应文本: {response.text[:500]}) # 打印前500字符便于排查 return None避坑指南JSONP 问题京东的接口有时返回的是 JSONP 格式即 JSON 数据被一个 JavaScript 函数调用包裹而不是纯 JSON。代码中的if text.startswith(fetchJSON_comment)...就是为了处理这种情况剥离函数外壳提取核心 JSON 字符串。异常处理网络请求充满不确定性必须用try...except包裹。requests.exceptions.RequestException捕获所有请求相关错误超时、连接错误等json.JSONDecodeError捕获解析错误。超时设置timeout10确保了请求不会无限期挂起。调试信息打印状态码和页码信息非常有用能让你实时了解爬虫进度和是否遇到问题如频繁出现429。3.3 数据解析与清洗函数接口返回的 JSON 结构层次较多我们需要从中提取出有用的字段。def parse_comments_data(json_data): 从接口返回的JSON数据中解析出评论列表。 参数: json_data: fetch_comments_page 函数返回的字典 返回: 包含所有评论字典的列表如果输入无效或没有评论则返回空列表 comments_list [] if not json_data or comments not in json_data: print(未找到评论数据或数据结构异常。) return comments_list comments json_data[comments] for comment in comments: # 提取核心字段这里根据实际返回的JSON结构调整 comment_info { id: comment.get(id), # 评论ID content: comment.get(content, ).strip(), # 评论内容去除首尾空格 creationTime: comment.get(creationTime), # 创建时间 score: comment.get(score), # 评分1-5星 usefulVoteCount: comment.get(usefulVoteCount, 0), # 有用点赞数 replyCount: comment.get(replyCount, 0), # 回复数 nickname: comment.get(nickname), # 用户昵称 userLevelName: comment.get(userLevelName), # 用户等级 # 追评内容 afterUserComment: comment.get(afterUserComment, {}).get(content, ) if comment.get(afterUserComment) else , # 商品规格 productColor: comment.get(productColor, ), productSize: comment.get(productSize, ), } comments_list.append(comment_info) print(f本页解析到 {len(comments_list)} 条评论。) return comments_list字段选择逻辑我选取了最常用的一些字段。content评论正文和score星级是核心。creationTime用于时间序列分析。usefulVoteCount点赞数可以衡量评论的有用性。afterUserComment是追评往往包含更长期的使用反馈价值很高。productColor和productSize有助于分析不同规格产品的口碑差异。你可以根据你的分析目标增减字段。3.4 分类保存逻辑实现“分类保存”是这个项目的亮点。我们可以按评论类型好评/中评/差评或者按商品来保存。def save_comments_to_file(comments_list, product_id, score_typeall, file_formatcsv): 将评论列表保存到文件。 参数: comments_list: 由 parse_comments_data 返回的评论字典列表 product_id: 商品ID用于命名文件 score_type: 评论类型用于命名文件 (0:all, 1:good, 2:medium, 3:bad) file_format: 保存格式csv 或 excel if not comments_list: print(评论列表为空跳过保存。) return # 将列表转换为 pandas DataFrame df pd.DataFrame(comments_list) # 定义类型映射用于生成文件名 type_map {0: all, 1: good, 2: medium, 3: bad} score_name type_map.get(score_type, unknown) # 生成文件名包含商品ID、评论类型和时间戳避免覆盖 timestamp time.strftime(%Y%m%d_%H%M%S) filename fjd_comments_{product_id}_{score_name}_{timestamp} try: if file_format.lower() csv: filepath f./data/{filename}.csv df.to_csv(filepath, indexFalse, encodingutf-8-sig) # utf-8-sig 解决Excel打开中文乱码 print(f数据已保存至: {filepath}) elif file_format.lower() excel: filepath f./data/{filename}.xlsx df.to_excel(filepath, indexFalse) print(f数据已保存至: {filepath}) else: print(f不支持的格式: {file_format}) except Exception as e: print(f保存文件时出错: {e})实操心得文件名设计包含商品ID、评论类型和时间戳保证了文件唯一性且信息明确。时间戳能避免多次运行程序时覆盖旧文件。编码问题保存为 CSV 时指定encodingutf-8-sig是关键。这样用 Excel 打开时中文字符就不会显示为乱码。如果只用utf-8Excel 可能无法正确识别。目录管理代码中假设存在一个./data/目录。在实际运行前最好先检查并创建这个目录os.makedirs(./data, exist_okTrue)。3.5 主控流程与反爬策略现在我们把所有函数串联起来并加入最重要的反爬策略。import os import json def main(): 主函数控制整个爬取流程。 # 1. 创建会话 session create_session() # 2. 目标商品ID (从商品页URL获取) product_id 100012043978 # 示例商品ID请替换成你的目标商品 # 3. 确保保存目录存在 os.makedirs(./data, exist_okTrue) # 4. 定义要爬取的评论类型和页数 # score_types [0, 1, 2, 3] # 分别爬取全部、好评、中评、差评 score_types [0] # 本次只爬全部评论 max_pages 10 # 每种类型最多爬多少页请根据实际需求调整勿过度爬取 all_comments_for_product [] # 用于存储单个商品的所有评论 for score in score_types: print(f\n开始爬取商品 {product_id} 的评论类型: {score}) comments_for_this_score [] for page in range(max_pages): # 5. 爬取单页数据 json_data fetch_comments_page(session, product_id, page, score) if json_data is None: print(f第{page1}页获取失败可能已无更多数据或遇到限制。) break # 跳出当前类型的爬取 # 6. 解析数据 comments_list parse_comments_data(json_data) if not comments_list: print(f第{page1}页无评论数据可能已到底部。) break # 7. 累积数据 comments_for_this_score.extend(comments_list) # 8. 关键请求延时避免触发反爬429 Too Many Requests # 随机延时 2 到 5 秒模拟人类操作间隔 delay random.uniform(2, 5) print(f等待 {delay:.2f} 秒后继续...) time.sleep(delay) # 9. 可选检查是否还有更多页 (根据接口返回的maxPage或totalCount判断) # current_page json_data.get(maxPage, 0) # if page current_page - 1: # 页码从0开始 # print(f已达到最大页数 {current_page}停止爬取。) # break # 10. 保存当前评论类型的数据 if comments_for_this_score: save_comments_to_file(comments_for_this_score, product_id, score, csv) all_comments_for_product.extend(comments_for_this_score) # 11. 可选保存该商品所有评论的汇总文件 if all_comments_for_product: save_comments_to_file(all_comments_for_product, product_id, all_merged, csv) print(f\n商品 {product_id} 所有评论爬取完成共 {len(all_comments_for_product)} 条。) if __name__ __main__: main()反爬策略精讲 代码中time.sleep(random.uniform(2, 5))这一行是抵御“429 Too Many Requests”错误的生命线。京东服务器会对高频请求进行限制。固定的延时如time.sleep(1)容易被识别为机器行为。随机延时在 2-5 秒之间更贴近真人浏览的节奏能显著降低被封 IP 或请求被拒的风险。这是小型爬虫项目中最有效、最简单的反爬措施。流程控制主循环按评论类型和页码进行。内层循环爬取某一类型下的多页数据直到达到预设的最大页数max_pages或接口返回空数据。外层循环遍历不同的评论类型。这种结构清晰易于扩展。4. 高级技巧与深度优化4.1 处理“429 Too Many Requests”错误即使设置了延时在长时间、大批量爬取时仍可能遇到 429 错误。我们需要一个更健壮的错误处理机制。def robust_fetch(session, url, params, max_retries3): 增强的请求函数包含重试机制和更长的退避延时。 for attempt in range(max_retries): try: resp session.get(url, paramsparams, timeout15) if resp.status_code 200: return resp elif resp.status_code 429: wait_time (attempt 1) * 10 random.uniform(5, 15) # 退避策略10秒20秒30秒... print(f触发429限制第{attempt1}次重试等待{wait_time:.1f}秒...) time.sleep(wait_time) else: print(f请求失败状态码: {resp.status_code}) resp.raise_for_status() except requests.exceptions.RequestException as e: print(f第{attempt1}次请求异常: {e}) if attempt max_retries - 1: time.sleep((attempt 1) * 5) # 异常后也等待 else: raise e # 重试次数用尽抛出异常 return None # 所有重试均失败策略解析当收到 429 状态码时说明请求过快。此时不应立即重试而应采用“指数退避”策略等待时间随着重试次数增加而延长如10秒、20秒、30秒。这给了服务器足够的冷却时间也符合良好的爬虫礼仪。4.2 多进程/多线程加速爬取当需要爬取多个商品时单线程顺序执行会非常慢。我们可以使用concurrent.futures库进行并发爬取。from concurrent.futures import ThreadPoolExecutor, as_completed def crawl_multiple_products(product_ids, max_workers3): 并发爬取多个商品。 注意并发会大幅增加请求频率务必谨慎设置 max_workers 和每个爬虫内部的延时 session create_session() os.makedirs(./data, exist_okTrue) # 控制总并发数避免对目标服务器造成过大压力 with ThreadPoolExecutor(max_workersmax_workers) as executor: # 提交任务 future_to_product {executor.submit(crawl_single_product, session, pid): pid for pid in product_ids} # 处理完成的任务 for future in as_completed(future_to_product): product_id future_to_product[future] try: result future.result() print(f商品 {product_id} 爬取完成: {result}) except Exception as exc: print(f商品 {product_id} 爬取过程中产生异常: {exc}) def crawl_single_product(session, product_id, max_pages5): 爬取单个商品的函数可被线程池调用 # 这里可以复用之前 main 函数中针对单个商品的爬取逻辑 # 但需要确保每个线程有自己的数据存储逻辑避免写入冲突 # 例如保存文件名中加入线程ID或更精确的时间戳 all_comments [] for page in range(max_pages): # ... 爬取逻辑 ... time.sleep(random.uniform(3, 6)) # 并发时单个爬虫的延时可以适当延长 # 保存文件时文件名加入唯一标识如 fjd_comments_{product_id}_{int(time.time()*1000)}.csv return f爬取了{len(all_comments)}条评论重要警告并发爬取是一把双刃剑。它能极大提升效率但也会成倍增加服务器压力更容易触发反爬机制429错误甚至IP封禁。务必严格控制并发数(max_workers3已经比较激进)。大幅增加每个请求之间的延时例如 3-6 秒甚至更长。考虑使用代理IP池将请求分散到不同的IP地址上这是应对反爬的终极方案之一但实现和维护成本较高。4.3 数据持久化与增量爬取对于需要定期更新的数据我们不应该每次都从头爬取。可以实现一个简单的增量爬取逻辑。思路在本地保存一个记录文件如last_crawled.json记录每个商品最后爬取到的评论ID或时间。下次爬取时先获取最新几页数据然后与本地记录对比只保存新的评论。def load_last_comment_id(product_id): 加载上次爬取的最后一条评论ID try: with open(f./data/last_record_{product_id}.json, r, encodingutf-8) as f: record json.load(f) return record.get(last_comment_id) except FileNotFoundError: return None def save_last_comment_id(product_id, last_id): 保存本次爬取的最后一条评论ID with open(f./data/last_record_{product_id}.json, w, encodingutf-8) as f: json.dump({last_comment_id: last_id}, f) def incremental_crawl(session, product_id): 增量爬取示例简化版基于评论ID判断 last_id load_last_comment_id(product_id) new_comments [] for page in range(5): # 假设只检查最新5页 data fetch_comments_page(session, product_id, page) if not data: break comments parse_comments_data(data) if not comments: break for comment in comments: if last_id and comment[id] last_id: # 如果遇到已爬取过的ID停止假设评论按ID倒序排列 print(f遇到已爬取评论ID {comment[id]}停止增量爬取。) if new_comments: save_comments_to_file(new_comments, product_id, incremental, csv) save_last_comment_id(product_id, new_comments[0][id]) # 保存最新的ID return new_comments.append(comment) time.sleep(random.uniform(2, 4)) if new_comments: save_comments_to_file(new_comments, product_id, incremental, csv) save_last_comment_id(product_id, new_comments[0][id])5. 常见问题排查与解决方案实录在实际运行中你几乎一定会遇到下面这些问题。我把我的排查经验整理成了速查表。问题现象可能原因排查步骤与解决方案返回状态码 429请求频率过高触发服务器限流。1.立即增加延时将time.sleep的随机区间加大如random.uniform(5, 10)。2.实现退避重试如上文robust_fetch函数所示遇到429后等待更长时间再重试。3.降低并发如果使用了多线程/进程减少并发数量。4.终极方案考虑使用代理IP轮询。返回状态码 403请求被拒绝可能请求头不完整或被识别为爬虫。1.检查请求头确保User-Agent,Referer等关键头信息齐全且有效。可以复制浏览器真实请求的头信息。2.检查 Cookies某些接口可能需要简单的 Cookies。用 Session 对象可以自动管理。可以尝试先手动访问一下商品页让 Session 捕获 Cookies。3.接口已更新京东可能更改了评论接口的地址或参数格式。重新按2.2节步骤分析网络请求。JSON解析错误1. 接口返回的不是纯JSON如JSONP。2. 接口返回错误页面如HTML。1.打印响应文本在except json.JSONDecodeError块中打印response.text[:500]查看原始返回内容。2.处理JSONP如果文本以fetchJSON_comment开头用文中提到的字符串截取方法提取JSON。3.检查状态码如果状态码不是200先解决请求问题。爬取到的数据为空1. 商品ID错误。2. 请求参数不正确。3. 该商品或该类型评论确实为空。1.核对商品ID从商品页URL中确认productId。2.模拟浏览器请求用 Postman 或浏览器直接访问你构造的URL看是否能返回数据。这是最直接的验证方法。3.尝试其他参数将score改为0全部page改为0先爬第一页试试。保存文件中文乱码CSV文件编码问题Excel 默认可能不以 UTF-8 打开。保存时指定编码使用df.to_csv(file.csv, indexFalse, encodingutf-8-sig)。utf-8-sig会添加 BOM 头帮助 Excel 正确识别。爬取速度慢单线程延时导致。1.评估需求是否真的需要极快速度稳定比快更重要。2.谨慎使用并发如上文4.2节使用线程池但务必控制并发数和延时。3.优化循环如果只需最新评论可设置较小的max_pages。‘comments’键不存在接口返回的数据结构发生变化。打印出json_data的键查看新的数据结构。例如print(json_data.keys())。根据新的结构调整parse_comments_data函数中的字段提取逻辑。我的个人踩坑记录不要贪快我最开始没加延时连续请求了20页立刻被禁。后来加了1秒固定延时偶尔还会出429。直到改成2-5秒随机延时才真正稳定下来。慢就是快在爬虫里是真理。勤于验证每次运行前我都习惯先用浏览器开发者工具手动构造一个请求URL在地址栏里打开看看确认能拿到数据再写进代码。这能节省大量调试时间。保存中间状态在爬取大量页面时可以每爬完一定数量比如每5页就保存一次到文件。这样即使程序中途崩溃也不会丢失全部成果。尊重robots.txt虽然技术上可以爬取但从法律和道德角度务必查看目标网站的robots.txt文件例如https://www.jd.com/robots.txt了解哪些路径是允许爬取的。过度爬取会对对方服务器造成负担。这个基于requests的京东评论爬虫从思路到实现再到优化和避坑基本就这些了。代码块里的内容可以直接复制使用但最重要的是理解背后的原理和策略。爬虫是与网站反爬机制持续博弈的过程今天的有效方法明天可能就会失效。保持学习灵活调整在满足自己数据需求的同时尽量做一个有节制的爬取者。本文还有配套的精品资源点击获取