Python爬取京东评论的5个实战避坑指南

1. 请求头设置的艺术与科学

当你第一次尝试用Python爬取京东评论时,最可能遇到的第一个障碍就是请求被直接拒绝。这不是因为你的代码有问题,而是因为京东的反爬机制已经识别出这是一个自动化请求。

现代网站的反爬系统越来越智能,它们会检查请求头中的各种细节。User-Agent只是最基本的检查项,还包括Accept-Language、Referer甚至Connection等字段。一个真实的浏览器请求会包含完整的头部信息,而简单的requests.get()如果不做处理,头部信息会非常简陋。

这是我常用的完整请求头设置:

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': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
    'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
    'Accept-Encoding': 'gzip, deflate, br',
    'Connection': 'keep-alive',
    'Upgrade-Insecure-Requests': '1',
    'Referer': 'https://item.jd.com/',
    'Sec-Fetch-Dest': 'document',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-Site': 'same-origin',
    'Sec-Fetch-User': '?1',
    'Cache-Control': 'max-age=0'
}

提示:定期更新你的User-Agent字符串,使用较新的浏览器版本号,这能让你的请求看起来更像真实用户。

2. 解密京东评论接口的关键参数

京东的评论接口有几个关键参数如果理解错误,会导致获取的数据不完整或完全错误。其中最容易出问题的两个参数是foldisShadowSku

  • fold=1:表示获取折叠的评论(通常是默认值)
  • isShadowSku=0:表示获取主商品的评论,而不是变体商品的评论

但实际应用中,这些参数的含义可能会随着京东的接口更新而变化。我建议的做法是先通过浏览器开发者工具观察正常请求时的参数,然后对照着设置。

这是我常用的参数组合:

params = {
    'productId': '100142621600',  # 商品ID
    'score': 3,                   # 评分类型(1-5分)
    'sortType': 5,                # 排序类型(5表示推荐排序)
    'page': 0,                    # 页码(从0开始)
    'pageSize': 10,               # 每页评论数量
    'isShadowSku': 0,             # 是否影子商品
    'fold': 1                     # 是否折叠
}

注意:京东的page参数是从0开始计数的,这与很多其他网站从1开始不同,这个小细节可能导致你获取的数据总是差一页。

3. 处理JSON数据结构的健壮性技巧

京东评论接口返回的JSON数据结构可能会发生变化,或者某些字段在某些情况下不存在。直接通过键名访问如data['comments']可能会导致KeyError异常。

我强烈建议使用.get()方法并提供默认值,这样可以确保程序不会因为缺少某个字段而崩溃。下面是一个健壮的数据提取示例:

comment = {
    'nickname': item.get('nickname', '匿名用户'),
    'score': item.get('score', 5),
    'content': item.get('content', ''),
    'productColor': item.get('productColor', '默认颜色'),
    'creationTime': item.get('creationTime', '未知时间'),
    'imageCount': len(item.get('images', [])),
    'usefulVoteCount': item.get('usefulVoteCount', 0),
    'replyCount': item.get('replyCount', 0)
}

此外,京东的评论接口有时会返回嵌套较深的数据结构。例如,用户等级信息可能位于userClient']下的userLevel']中。处理这种情况时,可以采用链式.get()

user_level = item.get('userClient', {}).get('userLevel', 0)

4. 避免IP被封的智能请求策略

频繁请求京东接口是最容易被封IP的行为。单纯的time.sleep()虽然有效,但不够智能。我推荐以下几种策略组合使用:

  1. 随机延时:在固定延时基础上增加随机性

    import random
    time.sleep(1 + random.random() * 2)  # 1-3秒随机延时
    
  2. 请求速率限制:使用令牌桶算法控制请求速率

    from ratelimit import limits, sleep_and_retry
    
    @sleep_and_retry
    @limits(calls=30, period=60)  # 每分钟最多30次请求
    def call_api():
        return requests.get(url, headers=headers, params=params)
    
  3. 代理IP池:使用多个代理IP轮换

    proxies = {
        'http': 'http://user:pass@proxy_ip:port',
        'https': 'http://user:pass@proxy_ip:port'
    }
    response = requests.get(url, headers=headers, params=params, proxies=proxies)
    
  4. 自动重试机制:对失败的请求自动重试

    from tenacity import retry, stop_after_attempt, wait_exponential
    
    @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
    def get_comments():
        response = requests.get(url, headers=headers, params=params)
        if response.status_code != 200:
            raise Exception(f"请求失败,状态码:{response.status_code}")
        return response.json()
    

5. 解决CSV文件写入的编码与格式问题

将爬取的数据保存到CSV文件时,最常见的两个问题是中文乱码和格式不规范。以下是经过实战验证的解决方案:

完整文件写入示例:

import csv
from datetime import datetime

filename = f'jd_comments_{datetime.now().strftime("%Y%m%d_%H%M%S")}.csv'

with open(filename, 'w', encoding='utf-8-sig', newline='') as f:
    fieldnames = ['nickname', 'score', 'content', 'productColor', 
                 'creationTime', 'imageCount', 'usefulVoteCount']
    writer = csv.DictWriter(f, fieldnames=fieldnames)
    
    writer.writeheader()
    
    for comment in comments:
        # 处理内容中的换行符和逗号
        comment['content'] = comment['content'].replace('\n', ' ').replace(',', ',')
        writer.writerow(comment)

关键点说明:

  1. encoding='utf-8-sig':使用带BOM的UTF-8编码,确保Excel能正确识别中文
  2. newline='':防止在Windows系统下出现空行
  3. 内容清洗:替换换行符和英文逗号,避免破坏CSV格式
  4. 动态文件名:包含时间戳,避免重复覆盖

对于大规模数据,建议分批写入并定期保存:

BATCH_SIZE = 100

with open(filename, 'w', encoding='utf-8-sig', newline='') as f:
    writer = csv.DictWriter(f, fieldnames=fieldnames)
    writer.writeheader()
    
    batch = []
    for i, comment in enumerate(comments, 1):
        batch.append(process_comment(comment))
        
        if i % BATCH_SIZE == 0:
            writer.writerows(batch)
            batch = []
            print(f'已保存{i}条评论')
    
    if batch:  # 写入剩余数据
        writer.writerows(batch)

6. 实战中的进阶技巧与经验分享

在实际爬取京东评论的过程中,我还总结出一些非常有用的进阶技巧:

动态商品ID获取:有时你需要从商品详情页URL中提取商品ID。京东的URL格式有多种变体,需要正则表达式灵活处理:

import re

def extract_product_id(url):
    patterns = [
        r'https://item.jd.com/(\d+).html',
        r'jd.com/\d+[?&]sku=(\d+)',
        r'wareId=(\d+)'
    ]
    for pattern in patterns:
        match = re.search(pattern, url)
        if match:
            return match.group(1)
    return None

评论图片下载:京东评论中的图片URL需要特殊处理才能获取高清版本:

def get_large_image(url):
    if 's128x96_jfs' in url:
        return url.replace('s128x96_jfs', 'jfs')
    return url

分页爬取优化:京东的评论接口有时会返回不准确的总页数信息。更可靠的做法是持续爬取直到返回空数据:

page = 0
while True:
    params['page'] = page
    response = requests.get(url, headers=headers, params=params)
    data = response.json()
    
    if not data.get('comments'):
        break
        
    # 处理评论数据
    process_comments(data['comments'])
    
    page += 1
    time.sleep(1 + random.random())

数据去重:在长时间运行爬虫时,可能会遇到重复数据。可以使用评论ID作为唯一标识进行去重:

seen_ids = set()

for comment in comments:
    comment_id = comment.get('id')
    if comment_id in seen_ids:
        continue
    seen_ids.add(comment_id)
    # 处理新评论

异常处理增强:网络请求可能因各种原因失败,完善的异常处理能让爬虫更健壮:

try:
    response = requests.get(url, headers=headers, params=params, timeout=10)
    response.raise_for_status()
    data = response.json()
    
    if data.get('errorCode'):
        raise Exception(f"接口返回错误:{data.get('errorCode')}")
        
except requests.exceptions.RequestException as e:
    print(f"请求失败:{str(e)}")
    # 可以在这里添加重试逻辑或代理切换
except json.JSONDecodeError:
    print("响应不是有效的JSON格式")
except KeyError as e:
    print(f"缺少必要字段:{str(e)}")
Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐