摘要
本文将详细介绍如何使用Python的Selenium库和Pandas库来抓取懂车帝网站上的新能源汽车销售数据,并将其保存至MySQL数据库中。通过这种方式,我们可以获取到最新的市场动态,为后续的数据分析提供坚实的基础。
目录
- 引言
- 环境搭建
- 代码实现
- 数据处理
- 结果展示
- 总结
1. 引言
随着全球对环境保护意识的增强以及政府政策的支持,新能源汽车行业迎来了快速发展的黄金时期。为了更好地理解这一领域的现状和发展趋势,我们选择从知名汽车资讯平台——懂车帝上抓取相关销售数据进行分析。这些数据不仅涵盖了不同品牌、车型的基本信息,还包括了销量、价格等重要指标,对于研究者来说具有极高的参考价值。
2. 环境搭建
在开始编写爬虫之前,首先需要确保本地开发环境已经准备好:
- Python版本:建议使用Python 3.x以上版本。
- 安装必要的库:
selenium
:用于自动化浏览器操作,可以从PyPI安装。pandas
:强大的数据分析工具包,同样支持通过PyPI安装。sqlalchemy
:ORM框架,方便连接和操作关系型数据库,如MySQL。
可以通过以下命令一次性安装所有依赖项:
bash
深色版本
pip install selenium pandas sqlalchemy pymysql
此外,还需要下载对应浏览器(例如Chrome)的WebDriver驱动程序,并将其路径添加到系统环境变量中,以便Python脚本能自动识别。
3. 代码实现
3.1 初始化 WebDriver
python
深色版本
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import pandas as pd
from sqlalchemy import create_engine
# 初始化 WebDriver
driver = webdriver.Chrome()
这段代码初始化了一个Chrome浏览器实例,它将作为我们的网络爬虫工具。webdriver.Chrome()
函数会启动一个无头模式(Headless Mode)下的Chrome浏览器窗口,默认情况下不会显示出来,除非你特别指定了可视化选项。
3.2 定义月份范围与遍历
python
深色版本
# 定义月份范围
months = [f"{year}{month:02d}" for year in [2024] for month in range(1, 11)]
# 存储所有数据
all_data = []
# 遍历每个月份
for month in months:
url = f"https://www.dongchedi.com/sales/sale-x-{month}-x-x-x-x"
print(f"Processing URL: {url}")
# 访问页面
driver.get(url)
# 显式等待,确保页面加载完成
wait = WebDriverWait(driver, 10)
这里定义了一个包含目标月份URL列表的变量months
,然后通过循环依次访问每个链接。每次迭代时都会输出当前正在处理的URL地址,帮助开发者追踪进度。driver.get(url)
负责打开指定网页;WebDriverWait(driver, 10)
设置了一个最大等待时间为10秒的显式等待器,保证页面完全加载后再继续执行下一步。
3.3 模拟滚动页面
python
深色版本
# 模拟滚动页面
last_height = driver.execute_script("return document.body.scrollHeight")
scroll_count = 0
max_scroll_attempts = 50 # 设置最大滚动次数
while scroll_count < max_scroll_attempts:
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(2) # 等待页面加载
new_height = driver.execute_script("return document.body.scrollHeight")
if new_height == last_height:
break
last_height = new_height
scroll_count += 1
由于部分网页内容是动态加载的,因此我们需要模拟用户行为向下滚动页面,直到所有数据都呈现出来为止。上述代码实现了这一功能:通过JavaScript命令获取当前页面的高度,并不断尝试滚动到底部。当连续两次测量的高度相同时,说明已到达底部,可以停止滚动。为了避免无限循环,还设定了一个最大滚动次数限制。
3.4 抓取并处理数据
# 等待元素加载
names = wait.until(EC.presence_of_all_elements_located((By.XPATH, '//*[@id="__next"]/div[1]/div[2]'
'/div/div[4]/div/div/ol/li/div[3]/div[1]/a')))
prices = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR,'#__next > div.tw-flex > div.new-main.new > div > div.jsx-'
'495096444.md\:tw-grid.tw-grid-cols-7.tw-gap-12 > div > div > ol > li > div.tw-p'
'y-16.tw-pr-12 > p')))
quantities = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR,'#__next > div.tw-flex > div.new-main.new > div > div.js'
'x-495096444.md\:tw-grid.tw-grid-cols-7.tw-gap-12 > div > div > ol > li > div.'
'tw-py-16.tw-text-center > div > p')))
types = wait.until(EC.presence_of_all_elements_located((By.XPATH, '//*[@id="__next"]/div[1]/div[2]/div/div[4]/div/div/ol/li/div[3]/div[1]/span')))
ranks = wait.until(EC.presence_of_all_elements_located((By.XPATH, '//*[@id="__next"]/div/div[2]/div/div[4]/div/div/ol/li/div[1]/span')))
fluctuations = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR,'#__next > div > div.new-main.new > div > div.jsx-495096444.md\:tw-grid.t'
'w-grid-cols-7.tw-gap-12 > div > div > ol > li > div.list_index__3g4'
'js > div.tw-text-color-red-500.tw-font-bold')))
# 检查字段长度
print(f"排名: {len(ranks)}, 名字: {len(names)}, 价格: {len(prices)}, 数量: {len(quantities)}, 类型: {len(types)}, 波动: {len(fluctuations)}")
# 填充缺失的数据
fill_value = "" # 用空字符串填充缺失的数据
data_list = []
for rank, name, price, quantity, type_, fluctuation in zip(ranks, names, prices, quantities, types, fluctuations):
try:
data = {
'排名': rank.text.strip(),
'名字': name.text.strip(),
'价格': price.text.strip(),
'数量': quantity.text.strip(),
'类型': type_.text.strip(),
'波动': fluctuation.text.strip(),
'月份': month
}
data_list.append(data)
print(data)
except Exception as e:
print(f"Error processing data: {e}")
# 将当前月份的数据添加到总数据列表中
all_data.extend(data_list)
此段代码主要用于定位页面中的各个元素,并提取它们的文字内容。具体来说,我们使用了XPath和CSS选择器两种方式来精确定位目标节点,然后调用.text
属性读取其文本值。考虑到可能存在某些元素未能成功加载的情况,我们在最后加入了异常处理机制,确保即使遇到问题也不会导致整个程序崩溃。此外,为了便于后续分析,还将每条记录所属的月份信息一同存储起来。
3.5 关闭浏览器
python
深色版本
# 关闭浏览器
driver.quit()
完成所有页面的数据抓取后,记得关闭浏览器实例以释放资源。
3.6 数据保存
python
深色版本
# 将数据保存到 DataFrame
df = pd.DataFrame(all_data, columns=['排名', '名字', '价格', '数量', '类型', '波动', '月份'])
# 创建一个 SQLAlchemy 引擎
engine = create_engine('mysql+pymysql://root:123456@localhost:3306/sales1?charset=utf8')
# 将 DataFrame 写入数据库
df.to_sql('sales1', con=engine, index=False, if_exists='replace')
print("Data has been successfully saved to the database.")
最后,我们将收集到的所有数据构造成一个Pandas DataFrame对象,并利用SQLAlchemy提供的接口将其写入MySQL数据库中。这里需要注意的是,在实际应用中应该避免直接暴露数据库账号密码,推荐采用配置文件或者环境变量的方式管理敏感信息。
4. 数据处理
通过上述步骤,我们已经成功地将懂车帝网站上的新能源汽车销售数据抓取下来并保存到了MySQL数据库里。接下来,可以进一步对这些原始数据进行清洗、转换和探索性分析,挖掘出更多有价值的信息。例如:
- 去重:检查是否存在重复记录,并根据业务逻辑决定是否保留。
- 缺失值填补:对于那些含有空白或无效值的字段,可以考虑用均值、众数或者其他合理的方法进行填补。
- 数值化处理:将非数字类型的特征(如“价格”、“数量”)转换成浮点数或整数形式,便于后续建模计算。
- 时间序列分析:基于月份维度观察各品牌销量变化趋势,预测未来可能的发展方向。
5. 结果展示
为了更直观地展示分析成果,可以借助Matplotlib、Seaborn等可视化库绘制图表,如柱状图比较不同品牌的销售业绩,折线图追踪某款车型随时间变化的价格曲线等。每一张图表都应该配以详细的注释说明,帮助读者深刻理解背后的含义。
6. 总结
本文介绍了如何使用Python结合Selenium和Pandas两大利器,轻松实现从懂车帝网站抓取新能源汽车销售数据,并将其存入MySQL数据库的过程。这不仅为我们提供了宝贵的第一手资料,也为后续开展深入的数据分析打下了良好的基础。希望这篇博客能够给广大编程爱好者带来启发,鼓励大家勇敢尝试新技术,探索未知领域。
7. 全部代码
将最后的基本数据保存在数据库中
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import pandas as pd
from sqlalchemy import create_engine
# 初始化 WebDriver
driver = webdriver.Chrome()
# 定义月份范围
months = [f"{year}{month:02d}" for year in [2024] for month in range(1, 11)]
# 存储所有数据
all_data = []
# 遍历每个月份
for month in months:
url = f"https://www.dongchedi.com/sales/sale-x-{month}-x-x-x-x"
print(f"Processing URL: {url}")
# 访问页面
driver.get(url)
# 显式等待,确保页面加载完成
wait = WebDriverWait(driver, 10)
# 模拟滚动页面
last_height = driver.execute_script("return document.body.scrollHeight")
scroll_count = 0
max_scroll_attempts = 50 # 设置最大滚动次数
while scroll_count < max_scroll_attempts:
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(2) # 等待页面加载
new_height = driver.execute_script("return document.body.scrollHeight")
if new_height == last_height:
break
last_height = new_height
scroll_count += 1
# 等待元素加载
names = wait.until(EC.presence_of_all_elements_located((By.XPATH, '//*[@id="__next"]/div[1]/div[2]'
'/div/div[4]/div/div/ol/li/div[3]/div[1]/a')))
prices = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR,'#__next > div.tw-flex > div.new-main.new > div > div.jsx-'
'495096444.md\:tw-grid.tw-grid-cols-7.tw-gap-12 > div > div > ol > li > div.tw-p'
'y-16.tw-pr-12 > p')))
quantities = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR,'#__next > div.tw-flex > div.new-main.new > div > div.js'
'x-495096444.md\:tw-grid.tw-grid-cols-7.tw-gap-12 > div > div > ol > li > div.'
'tw-py-16.tw-text-center > div > p')))
types = wait.until(EC.presence_of_all_elements_located((By.XPATH, '//*[@id="__next"]/div[1]/div[2]/div/div[4]/div/div/ol/li/div[3]/div[1]/span')))
ranks = wait.until(EC.presence_of_all_elements_located((By.XPATH, '//*[@id="__next"]/div/div[2]/div/div[4]/div/div/ol/li/div[1]/span')))
fluctuations = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR,'#__next > div > div.new-main.new > div > div.jsx-495096444.md\:tw-grid.t'
'w-grid-cols-7.tw-gap-12 > div > div > ol > li > div.list_index__3g4'
'js > div.tw-text-color-red-500.tw-font-bold')))
# 检查字段长度
print(f"排名: {len(ranks)}, 名字: {len(names)}, 价格: {len(prices)}, 数量: {len(quantities)}, 类型: {len(types)}, 波动: {len(fluctuations)}")
# 填充缺失的数据
fill_value = "" # 用空字符串填充缺失的数据
data_list = []
for rank, name, price, quantity, type_, fluctuation in zip(ranks, names, prices, quantities, types, fluctuations):
try:
data = {
'排名': rank.text.strip(),
'名字': name.text.strip(),
'价格': price.text.strip(),
'数量': quantity.text.strip(),
'类型': type_.text.strip(),
'波动': fluctuation.text.strip(),
'月份': month
}
data_list.append(data)
print(data)
except Exception as e:
print(f"Error processing data: {e}")
# 将当前月份的数据添加到总数据列表中
all_data.extend(data_list)
# 关闭浏览器
driver.quit()
# 将数据保存到 DataFrame
df = pd.DataFrame(all_data, columns=['排名', '名字', '价格', '数量', '类型', '波动', '月份'])
# 创建一个 SQLAlchemy 引擎
engine = create_engine('mysql+pymysql://root:123456@localhost:3306/sales1?charset=utf8')
# 将 DataFrame 写入数据库
df.to_sql('sales1', con=engine, index=False, if_exists='replace')
print("Data has been successfully saved to the database.")