
在电商数据分析场景中,京东作为头部电商平台,其手机品类的销量、评分数据是洞察市场趋势、分析用户偏好的核心依据。相较于静态网页爬取,京东采用动态渲染技术加载商品数据,传统的 Requests+BeautifulSoup 组合难以获取完整信息,而 Selenium 凭借模拟浏览器行为的特性,能完美解决动态数据爬取问题。本文将详细讲解如何基于 Selenium 实现京东手机销量与评分数据的爬取,并完成数据清洗与初步分析。
以京东 “手机” 关键词搜索结果为数据源,提取以下信息:
python
运行
import time
import pandas as pd
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup
# 代理配置信息(单独定义,便于维护)
proxyHost = "www.16yun.cn"
proxyPort = "5445"
proxyUser = "16QMSOML"
proxyPass = "280651"
class JDPhoneSpider:
def __init__(self):
# 配置Chrome选项,避免弹窗与自动化提示
chrome_options = Options()
# 无头模式(可选,注释后可见浏览器操作)
# chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-blink-features=AutomationControlled')
chrome_options.add_experimental_option('excludeSwitches', ['enable-automation'])
chrome_options.add_experimental_option('useAutomationExtension', False)
# ========== 核心修改:添加代理配置 ==========
# 1. 配置HTTP代理(包含主机和端口)
chrome_options.add_argument(f'--proxy-server=http://{proxyHost}:{proxyPort}')
# 2. 处理代理账号密码认证(需通过Chrome扩展或DesiredCapabilities,这里用更通用的方式)
# 注:Chrome 90+版本需通过selenium的ChromeDevTools协议注入认证信息
self.proxy_auth = (proxyUser, proxyPass)
# 初始化浏览器
self.driver = webdriver.Chrome(options=chrome_options)
# 注入代理认证(关键:避免代理需要账号密码时访问失败)
self._set_proxy_auth()
self.driver.execute_script('Object.defineProperty(navigator, "webdriver", {get: () => undefined})')
self.driver.implicitly_wait(10) # 隐式等待10秒
self.wait = WebDriverWait(self.driver, 15) # 显式等待对象
self.data_list = [] # 存储爬取的商品数据
def _set_proxy_auth(self):
"""注入代理账号密码认证,解决带密码的代理访问问题"""
try:
# 通过Chrome DevTools协议设置代理认证
self.driver.execute_cdp_cmd(
'Network.enable', {}
)
self.driver.execute_cdp_cmd(
'Network.setExtraHTTPHeaders',
{'headers': {'Proxy-Authorization': f'Basic {self._get_base64_auth()}'}}
)
except Exception as e:
print(f"代理认证配置警告:{e},部分代理可能无需此步骤")
def _get_base64_auth(self):
"""将代理账号密码转为Base64编码(HTTP Basic认证要求)"""
import base64
auth_str = f"{self.proxy_auth[0]}:{self.proxy_auth[1]}"
return base64.b64encode(auth_str.encode('utf-8')).decode('utf-8')
def get_page_data(self, page_url):
"""爬取单页商品数据"""
self.driver.get(page_url)
# 模拟滚动加载(京东动态加载商品,需滚动到底部)
for _ in range(3):
self.driver.execute_script('window.scrollTo(0, document.body.scrollHeight)')
time.sleep(2)
# 等待商品列表加载完成
self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'gl-item')))
# 解析页面源码
soup = BeautifulSoup(self.driver.page_source, 'lxml')
items = soup.find_all('li', class_='gl-item')
for item in items:
# 提取商品名称
name_tag = item.find('div', class_='p-name').find('em')
name = name_tag.get_text().strip() if name_tag else '未知'
# 提取商品价格
price_tag = item.find('div', class_='p-price').find('i')
price = price_tag.get_text().strip() if price_tag else '0'
# 提取销量(付款人数)
sales_tag = item.find('div', class_='p-commit').find('a', class_='J_comment')
sales = sales_tag.get_text().strip().replace('万+', '0000').replace('+', '') if sales_tag else '0'
# 提取评分(部分商品无评分,需做容错)
score_tag = item.find('div', class_='p-commit').find('span', class_='score')
score = score_tag.get_text().strip() if score_tag else '无评分'
# 提取评论数
comment_tag = item.find('div', class_='p-commit').find('strong').find('a')
comment_num = comment_tag.get_text().strip() if comment_tag else '0'
# 整理数据
self.data_list.append({
'商品名称': name,
'价格(元)': price,
'销量': sales,
'评分': score,
'评论数': comment_num
})
print(f'当前页爬取完成,累计获取{len(self.data_list)}条数据')
def crawl(self, keyword='手机', page_num=3):
"""核心爬取函数"""
base_url = f'https://search.jd.com/Search?keyword={keyword}&enc=utf8&page='
for page in range(1, page_num * 2, 2):
# 京东分页URL规则:第1页page=1,第2页page=3,第3页page=5...
page_url = base_url + str(page)
print(f'正在爬取第{(page+1)//2}页,URL:{page_url}')
try:
self.get_page_data(page_url)
# 翻页间隔,避免请求过快被风控
time.sleep(3)
except Exception as e:
print(f'第{(page+1)//2}页爬取失败:{str(e)}')
continue
def save_data(self, filename='jd_phone_data.csv'):
"""将数据保存为CSV文件"""
df = pd.DataFrame(self.data_list)
# 数据清洗:价格转为数值型,销量做简单处理
df['价格(元)'] = pd.to_numeric(df['价格(元)'], errors='coerce').fillna(0)
df['评论数'] = df['评论数'].str.replace('万', '0000').replace('+', '')
df['评论数'] = pd.to_numeric(df['评论数'], errors='coerce').fillna(0)
df.to_csv(filename, index=False, encoding='utf-8-sig')
print(f'数据已保存至{filename},共{len(df)}条有效数据')
def close(self):
"""关闭浏览器"""
self.driver.quit()
# 主程序执行
if __name__ == '__main__':
spider = JDPhoneSpider()
try:
# 爬取3页手机数据
spider.crawl(keyword='手机', page_num=3)
# 保存数据
spider.save_data()
finally:
# 确保浏览器关闭
spider.close()webdriver特征检测:通过execute_script修改navigator.webdriver属性,避免京东识别出自动化程序;京东商品列表采用滚动加载机制,通过window.scrollTo模拟鼠标滚动,配合time.sleep等待数据加载,确保能获取完整的商品信息。
if-else做容错处理,避免程序崩溃;time.sleep),避免短时间内大量请求;爬取完成后,可通过 Pandas 做简单分析:
python
运行
import pandas as pd
# 读取数据
df = pd.read_csv('jd_phone_data.csv')
# 1. 筛选评分≥4.8的商品
high_score_phones = df[df['评分'] != '无评分'][df[df['评分'] != '无评分']['评分'].astype(float) ≥ 4.8]
# 2. 统计价格区间分布
price_range = pd.cut(df['价格(元)'], bins=[0, 1000, 2000, 3000, 5000, 10000], labels=['千元内', '1-2千', '2-3千', '3-5千', '5千以上'])
price_dist = price_range.value_counts()
print(price_dist)原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。