实战解析:利用Python requests库高效抓取京东商品评论并构建结构化数据集
1. 项目缘起:为什么我们要自己动手抓评论?
大家好,我是老张,一个在数据分析和电商圈子里混了十来年的“老油条”。今天想和大家聊聊一个非常实际的问题:当你需要分析一款产品的用户口碑时,你该怎么办?是手动去商品页面一页页翻看评论,还是等着别人给你现成的数据报告?我的答案是,都不如自己动手,用Python把数据“拿”过来。
你可能觉得,抓取数据听起来是程序员干的事儿,离我们运营、市场、数据分析师有点远。但说实话,在这个数据驱动的时代,能自己获取一手数据,就像自己会做饭一样,是一种非常核心的生存技能。特别是电商平台的用户评论,这里面藏着金矿:用户为什么喜欢这个产品?最常吐槽的点是什么?竞品的评价怎么样?这些信息对于产品迭代、市场定位、甚至是广告投放策略都至关重要。
京东作为国内主要的电商平台,其商品评论的质量和数量都非常可观。但平台本身并没有提供一个非常方便的一键导出功能。这时候,Python的requests库就成了我们的“万能钥匙”。它不像Scrapy那样庞大复杂,对于新手来说,requests足够简单直接,能让我们快速上手,把精力集中在数据本身,而不是框架学习上。今天,我就带你走一遍完整的流程,从找到数据接口,到写出健壮的代码,最后把几千条评论整整齐齐地存进CSV表格里。放心,哪怕你之前只写过“Hello World”,跟着我的步骤,也一定能搞定。
2. 实战前的侦察:找到数据的“后门”
在开始写代码之前,有个至关重要的步骤,我称之为“侦察”。我们得先搞清楚,京东的评论数据藏在哪里,以及它以什么形式提供。直接去解析网页HTML代码(也就是常说的“爬网页”)是一种方法,但这种方法既笨重又脆弱,页面结构一变,代码就得重写。更聪明的方法是找到网站背后提供数据的API接口,也就是数据的“后门”。
怎么找到这个“后门”呢? 这里就要用到浏览器的“开发者工具”了。以Chrome浏览器为例,你打开任意一个京东商品页面,比如某个手机的详情页,然后按下键盘上的 F12 键。这时浏览器旁边或底部会弹出一个面板,这就是我们的“侦察兵指挥部”。
- 在开发者工具顶部,找到并点击 “Network” (网络)标签页。
- 然后,在商品页面上,找到“商品评价”区域,点击“只看当前商品评价”或者尝试切换不同的评分筛选(比如“好评”、“中评”、“差评”)。
- 此时,你的眼睛要紧盯“Network”面板。你会看到一大堆文件在刷屏,这些就是页面加载时请求的各种资源,比如图片、样式表、JavaScript文件,还有最重要的——数据接口。
我们的目标是找到那个名字看起来像comment或productPageComments的请求。通常,它的类型(Type)是xhr或fetch。当你点击它,在右侧的“Headers”(标头)选项卡里,你能看到完整的请求网址(Request URL)。我敢打赌,你十有八九会看到一个类似这样的链接:
https://club.jd.com/comment/productPageComments.action?...
这个链接,就是我们梦寐以求的API接口地址。在“Preview”(预览)或“Response”(响应)选项卡里,你能直接看到这个请求返回的数据,通常是清晰明了的JSON格式,就像下面这样的一小段:
{
"comments": [
{
"nickname": "j***d",
"score": 5,
"content": "手机很好用,运行流畅,拍照清晰",
"productColor": "星光色",
"creationTime": "2023-10-27 10:30:00",
"images": [...]
},
// ... 更多评论
],
"maxPage": 100,
// ... 其他信息
}
看到没?数据已经结构化地摆在这里了,我们要做的,就是写个程序,模拟浏览器去“调用”这个接口,然后把返回的JSON数据解析出来。这比从混乱的HTML标签里大海捞针要高效、稳定一万倍。这个侦察过程,是任何数据抓取项目成功的第一步,也是最能体现“实战”经验的地方。我见过太多新手一上来就对着HTML硬啃,结果事倍功半。
3. 搭建你的数据流水线:核心代码逐行拆解
侦察完毕,拿到了接口地址和参数规律,接下来就是搭建我们的自动化数据流水线了。我会把完整的代码拆解成几个功能模块,就像搭积木一样,一块块给你讲明白。
3.1 准备工具箱:导入必要的库
工欲善其事,必先利其器。我们不需要很复杂的工具,四个Python标准库就足够了。打开你的代码编辑器(比如VS Code、PyCharm或者哪怕只是记事本),新建一个Python文件,比如叫jd_comment_crawler.py,然后在文件开头写下:
import requests # 核心武器,用于发送HTTP请求
import csv # 数据搬运工,负责读写CSV文件
import json # 翻译官,把接口返回的JSON字符串变成Python字典
import time # 节奏控制器,防止请求太快被限制
这里重点说一下requests库,它不是Python自带的,需要额外安装。打开你的命令行(Windows上是CMD或PowerShell,Mac/Linux上是终端),输入下面这行命令,回车即可:
pip install requests
如果安装速度慢,可以试试国内的镜像源,比如清华的:pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple。
3.2 创建数据仓库:初始化CSV文件
我们要把抓下来的数据存起来,CSV格式是最通用、最方便用Excel打开查看的。这一步,我们创建文件并定义好表格的“表头”,也就是每一列叫什么名字。
# 打开(创建)一个名为‘jd_comments.csv’的文件,准备写入
# mode='w' 表示写入模式,如果文件存在会清空重写,不存在则创建
# encoding='utf-8-sig' 是关键!这样用Excel打开中文不会乱码
# newline='' 是为了避免在Windows系统下写入空行
with open('jd_comments.csv', mode='w', encoding='utf-8-sig', newline='') as f:
# 定义我们想要保存的字段(表头)
fieldnames = ['nickname', 'score', 'content', 'productColor', 'creationTime', 'imageCount']
# 创建一个DictWriter对象,它知道如何把字典写成CSV的一行
csv_writer = csv.DictWriter(f, fieldnames=fieldnames)
# 把表头先写入文件
csv_writer.writeheader()
注意,我用了with open(...) as f:的写法。这是Python的上下文管理器,它的好处是,无论程序正常结束还是中途出错,它都会自动帮我们关闭文件,防止数据丢失或文件损坏。这是一个非常好的编程习惯。
3.3 伪装成浏览器:设置请求头
直接用一个“裸”的Python程序去访问网站,对方服务器一眼就能看出来你不是正常用户,很可能会拒绝你的请求。所以我们需要进行简单的“伪装”,主要是设置User-Agent(用户代理),告诉服务器:“我是一个正经的Chrome浏览器”。
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
这个字符串可以从你之前打开的浏览器开发者工具的“Network”标签里,随便找一个请求,看它的Request Headers部分复制过来。记得偶尔更新一下版本号,让它看起来更“新鲜”。
3.4 组装请求参数:告诉接口你要什么
根据我们之前的侦察,京东评论接口需要一些参数。最核心的是productId(商品ID)和score(评分)。商品ID就在商品页面的网址里,比如 https://item.jd.com/100142621600.html, 那么100142621600就是它的ID。
base_url = 'https://club.jd.com/comment/productPageComments.action'
# 定义基础参数,这些是固定或循环的起点
params_template = {
'productId': '100142621600', # 替换成你想分析的商品ID
'sortType': 5, # 5表示按推荐排序,6按时间排序
'pageSize': 10, # 每页请求多少条,最大可设30,但太大可能不稳定
'isShadowSku': 0,
'fold': 1
}
这里我把它定义为一个模板,因为page(页码)和score(评分)是需要我们在循环里动态变化的。
3.5 启动抓取循环:按评分和页码遍历
京东的评论可以按1-5分筛选。为了获取全面的数据,我们最好把所有评分的评论都抓下来。同时,每个评分下又有许多页。这就需要用到两层循环。
# 外层循环:遍历1星到5星评分
for score in range(1, 6):
print(f'开始抓取 {score} 星评价...')
# 内层循环:遍历每一页。这里先假设抓前5页,你可以根据需要调整
for page in range(0, 5): # 注意:京东的页码通常从0开始
print(f' 正在抓取第 {page+1} 页...')
# 组合当前请求的参数
params = params_template.copy() # 重要!复制模板,避免修改原模板
params['score'] = score
params['page'] = page
# 发送GET请求,带上我们的伪装头和参数
try:
response = requests.get(base_url, params=params, headers=headers, timeout=10)
response.raise_for_status() # 如果状态码不是200,会抛出异常
except requests.exceptions.RequestException as e:
print(f' 请求失败: {e}')
continue # 跳过这一页,继续下一页
# 解析返回的JSON数据
try:
json_data = response.json()
except json.JSONDecodeError:
print(f' 解析JSON失败,响应内容: {response.text[:200]}')
continue
# 检查数据是否有效
if 'comments' not in json_data or not json_data['comments']:
print(f' 第{page+1}页无评论数据,可能已抓完。')
break # 跳出当前评分的内层循环
comments = json_data['comments']
这段代码有几个实战要点:
params_template.copy():在循环内修改字典前先复制一份。如果不复制,直接修改params_template,会导致下一次循环的参数是上一次修改后的结果,引发错误。- 异常处理(
try...except):网络请求充满了不确定性,服务器可能出错、网络可能中断。用try包裹起来,程序遇到错误不会崩溃,而是记录下错误并继续运行,这是编写健壮爬虫的基本素养。 - 超时设置(
timeout=10):告诉requests,如果10秒内没收到响应,就认为请求失败,避免程序无限期卡住。 - 数据有效性检查:解析完JSON后,先判断是否有
comments字段以及它是否为空。如果为空,通常意味着这个评分下的评论已经抓取完毕,可以用break跳出当前评分的循环,提高效率。
3.6 提取与存储:从JSON到CSV行
拿到comments列表后,我们就可以遍历每一条评论,提取出我们关心的字段,并写入CSV文件。
# 遍历当前页的每一条评论
for comment in comments:
# 构建要保存的数据字典
row_data = {
'nickname': comment.get('nickname', '匿名用户'),
'score': comment.get('score', 0),
'content': comment.get('content', '').replace('\n', ' ').strip(), # 清洗换行符
'productColor': comment.get('productColor', ''),
'creationTime': comment.get('creationTime', ''),
'imageCount': len(comment.get('images', [])) # 计算图片列表的长度
}
# 将这一行数据写入CSV文件
csv_writer.writerow(row_data)
# 在控制台打印一下进度,让人安心
print(f' 已保存: {row_data["nickname"][:5]}...的评价')
# 礼貌性延迟,模拟真人操作,避免给服务器带来压力或触发反爬
time.sleep(1.5) # 休眠1.5秒
这里有几个细节处理:
- 使用
.get()方法:comment.get('nickname', '匿名用户')的意思是,尝试从comment字典里获取nickname键的值,如果这个键不存在,就返回默认值‘匿名用户’。这能有效避免因数据字段缺失导致的程序崩溃。 - 数据清洗:评论内容
content里可能有换行符\n,直接存入CSV会导致行错乱。我们用.replace('\n', ' ')把它替换成空格,再用.strip()去掉首尾空白字符。 - 计算图片数量:接口返回的
images字段是一个列表,里面是图片信息。我们不需要具体图片链接(除非你要下载),只关心数量,所以用len()计算列表长度即可。 - 请求间隔(
time.sleep):这是网络爬虫的道德和生存准则。快速连续的请求会对目标服务器造成压力,也极易被识别为恶意攻击而封禁IP。加上一个1到3秒的随机延迟,是友好且必要的。你可以用time.sleep(random.uniform(1, 3))让间隔时间更随机。
当所有循环结束,with open语句会自动关闭文件。这时,打开你的项目文件夹,就能看到一个名为jd_comments.csv的文件,用Excel或文本编辑器打开,里面就是整整齐齐的结构化评论数据了。
4. 从能用变好用:代码健壮性与功能扩展
上面的代码已经是一个可用的爬虫了。但“能用”和“好用”、“耐用”之间还有距离。在实际项目中,我们还需要考虑更多。
4.1 增强健壮性:应对各种异常
网络世界充满意外,我们的程序必须足够“皮实”。
- 连接错误/超时:我们已经用
try...except requests.exceptions.RequestException捕获了。 - HTTP错误:我们用
response.raise_for_status()处理了非200状态码。 - JSON解析错误:用
try...except json.JSONDecodeError捕获。 - 关键字段缺失:用
.get()方法提供了默认值。
但还可以更进一步,比如加入重试机制。当请求失败时,不是直接跳过,而是重试几次。
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
# 创建一个带重试策略的Session对象
session = requests.Session()
retry_strategy = Retry(
total=3, # 总共重试3次
backoff_factor=1, # 重试等待时间间隔增长因子
status_forcelist=[429, 500, 502, 503, 504], # 遇到这些状态码才重试
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
session.mount("https://", adapter)
# 之后就用 session.get 代替 requests.get
response = session.get(base_url, params=params, headers=headers, timeout=10)
这个Retry配置会在请求失败(如遇到服务器内部错误500,或网关错误502)时,自动重试最多3次,并且每次重试的等待时间会逐渐增加(backoff_factor),避免“雪崩”式重试。
4.2 扩展数据字段:挖掘更多信息
基础的评论信息之外,接口往往还返回了更多有价值的数据,我们可以根据分析需求决定是否抓取。例如:
userLevelName: 用户等级(如“金牌会员”),可用于分析不同等级用户的评价倾向。userClientShow: 用户购买渠道(如“来自京东APP”)。afterDays/afterUserComment: 追评内容和时间,这对了解产品长期使用体验非常关键。usefulVoteCount: “有用”点赞数,可以筛选出高质量评论。
你只需要在fieldnames列表和构建row_data字典时,把这些字段加进去就行。记得先用print(json_data)打印一下完整的返回数据,看看里面到底有什么宝藏。
4.3 实现自动翻页:不再限制抓取页数
我们之前的代码固定抓取前5页。但一个热销商品可能有成千上万条评论。如何实现自动翻页,直到抓完所有数据呢?关键在于解析接口返回的maxPage字段。
# 在成功解析json_data后
max_page = json_data.get('maxPage', 1) # 获取最大页数,默认1
print(f'该评分下共有 {max_page} 页评论。')
# 然后将内层循环改为
for page in range(0, max_page): # 从0遍历到 max_page-1
# ... 原有的请求和解析代码 ...
这样,程序就会自动根据该评分下的总页数进行抓取,真正做到“一网打尽”。
4.4 数据清洗与预处理:让分析更高效
抓取下来的原始数据可能有些“脏”,在存入CSV前做初步清洗,能为后续分析省不少事。
- 去重:有些接口可能在不同条件下返回重复数据。可以根据
comment_id(如果接口提供)或结合nickname、content、creationTime生成唯一标识进行去重。 - 文本清洗:除了处理换行符,还可以移除无意义的广告、统一表情符号(如将[微笑]统一替换为
[表情_微笑])、过滤掉过短的无意义评论(如“好评”、“不错”)。 - 格式标准化:将
creationTime字符串转换为Python的datetime对象,方便后续按时间进行分析。
这些清洗步骤可以在构建row_data字典之后,写入CSV文件之前进行。
5. 让程序更“智能”:应对反爬与调度
如果你的抓取量很大,或者需要定期运行,就需要考虑更高级的策略。
5.1 应对反爬虫策略
网站为了防止数据被过度抓取,会设置反爬机制。除了我们已经做的设置User-Agent和添加延迟,还可能遇到:
- IP限制:短时间内同一IP请求过多会被封。解决方案是使用代理IP池。你可以购买付费的代理服务,或者寻找一些免费的代理IP(但稳定性较差)。在
requests.get中通过proxies参数设置。proxies = { 'http': 'http://your_proxy_ip:port', 'https': 'http://your_proxy_ip:port', } response = requests.get(url, headers=headers, proxies=proxies) - 请求签名验证:一些复杂的接口会对参数进行加密或签名。这需要你逆向分析前端JavaScript代码,找到加密算法并用Python实现。这是爬虫工程师的“深水区”,需要一定的JS逆向能力。
- Cookie/Session验证:有些数据需要登录后才能查看。这时你需要先用
requests模拟登录,获取有效的Cookie,并在后续请求中携带。可以使用requests.Session()对象来保持会话。
5.2 程序调度与日志
- 定时运行:如果你需要每天定时抓取某商品的评论,可以使用系统的定时任务。在Linux上可以用
cron,在Windows上可以用“任务计划程序”。更Pythonic的方式是使用schedule或APScheduler这样的库在程序中实现定时。 - 记录日志:把程序运行情况(开始时间、成功抓取多少页、遇到什么错误等)记录到文件里,而不是只打印在屏幕上,方便事后排查问题。Python自带的
logging库就非常好用。 - 增量抓取:如果你每天只关心新增的评论,可以记录上次抓取的最后一条评论的时间,下次只抓取这个时间之后的评论。这需要在代码中加入时间比对逻辑,并持久化存储这个“最后时间点”。
写一个能跑起来的爬虫脚本不难,但要让它在复杂的网络环境中稳定、可靠、长期地运行,并且友好地对待目标网站,这些“工程化”的思考和实践才是真正价值的体现。这也是我从无数个深夜调试和爬虫被封的教训中总结出来的经验。希望这些扩展思路能帮你把项目从“课堂作业”升级为“生产工具”。
更多推荐



所有评论(0)