目录
1 实验目的... 2
1.1 使用selenium防爬虫方法爬取懂车帝数据.. 2
1.2 数据预处理... 2
1.3 爬虫数据保存到数据库与本地.. 3
1.4 可视化分析... 3
2.操作平台... 3
3.实验步骤... 3
3.1 数据爬虫... 3
3.1.1 浏览器驱动器下载与配置.. 5
3.1.2 懂车帝车辆销量排行爬取.. 7
3.1.3 数据预处理... 9
3.1.4 MySql数据库存储.. 11
3.1.5 文件系统存储... 14
3.1.6 数据可视化分析.. 15
4. 出现的问题与解决... 22
1 实验目的
爬取懂车帝上近一年销售量前109的所有车型车辆,再爬取近一年销售量前109的“新能源”车辆(用于判断所有车辆中哪些是新能源),爬取到数据后需要处理数据,将数据分解与形式转换与处理缺失值。随后将数据存入本地文件与数据库。最后将数据一多种形式可视化,并分析。
1.1 使用selenium防爬虫方法爬取懂车帝数据
由于懂车帝页面数据是通过下滑页面动态生成的,通过JavaScript动态加载数据,这是常见的放爬虫技术,因此使用传统的“requests”方法只能爬取当前页面展示的一点数据,不能展示所有的数据,因此直接爬取页面html无法得到动态加载的数据。
需要模拟浏览器运行的库来应对,这里使用“Selenium”库,该库能够模拟真人访问页面,设置“下滑”操作加载数据,并且能够直接获取当前页面内容的代码。
1.2 数据预处理
爬取了数据之后需要进行一定的预处理操作,例如:爬取的既有品牌又有车型的数据,例如“特斯拉/紧凑型SUV”,需要转化为两个数据;价格区间数据需要分成最大价格和最低价格,例如“10.58-20.68万”需要转换为两个数据并且转换为“float”类型;车辆销量数据需要去掉中间的逗号且转换为int形式,例如“425,726”需要转换为int型的4257226。另外还需要将数据中的缺失值进行处理,例如有些车没有车型仅有品牌数据“上汽通用别克”,则需要进行处理。且爬取到的数据并没有排名,因此需要按照爬取顺序添加排名。
1.3 爬虫数据保存到数据库与本地
将爬取的数据上传到数据库与本地。
1.4 可视化分析
将数据可视化,并分析。例如:绘制不同品牌车的销量,分析出进一年什么品牌销售量最高,从而得出什么品牌在我国最受欢迎;绘制不同车型的销量数据,分析出什么车型销量最高,从而得出目前什么车型受大众欢迎;绘制出各个车辆的销量,并且用不同颜色表明是否是新能源车,并在图中标上最高价格和最低价格,分析销量、是否是新能源车、价格区间之间的关系,从而得出目前新能源的市场份额以及什么新能源车辆目前最受欢迎,以及销量与价格之间的关系。
2.操作平台
(1)操作系统:windows11;
(2)MySQL版本:8.0;
(3)python版本:3.8。
3.实验步骤
3.1 数据爬虫
下图是懂车帝上近一年所有车型销量排行榜,可以看到这个界面是滑块控制向下滑动的,滑动之后会动态加载数据,因此直接爬取这个网址只能获得当前展示的数据。
我们查看“开发者工具”的源码如下,发现网页源码只展示到了第10位的“五菱宏光MINIEV”车辆,后续的数据并没有展示。如果先手动滑动,让数据加载后再打开开发者工具查看源码,还是不会加载出数据来。
此时就得出动态更新的数据是通过XHR请求,以JavaScript的形式发送的。打开开发者工具中的“元素”查看,发现了动态加载的数据,因此以传统的“requests”方法爬虫只能爬到网页源码中展示的10辆车数据,动态加载的数据并不能爬取到,动态加载数据如下图:
3.1.1 浏览器驱动器下载与配置
针对这个防爬虫技术,就需要使用到“selenium”这个工具包模拟真人浏览界面并且获取当前展示网页的元素。实现模拟真人浏览则需要下载浏览器对应版本的驱动,本人使用的是Edge浏览器,下载地址为“Microsoft Edge WebDriver |Microsoft Edge 开发人员”,需要找到对应的版本驱动,下载解压后需要将可执行文件放在python路径的Scripts文件夹下:
配置好驱动之后先用百度网页测试一下,编辑以下代码:
from selenium import webdriver browser = webdriver.Edge() data = browser.get('http://www.baidu.com') #请求页面,会打开一个窗口 text = browser.page_source #获取当前呈现页面的源码 browser.quit() #关闭浏览器 print(text) #输出 |
运行以上代码会自动弹出以下界面:
最终输出的网页代码如下,测试无误:
3.1.2 懂车帝车辆销量排行爬取
基本方法确定了,开始对懂车帝近一年所有车型销量排行榜数据进行爬取。但是有一个问题,上面对百度爬取是通过“page_source”方法获取网页代码,但是我们需要的不是整个代码,而是代码中的车辆排名信息,因此不能用这个方法,而需要用“find_elements”方法查找元素,在这之前先获取源码在word中打开,查看源码结构:
上图是排名第一的特斯拉“model Y”车辆,划红线的是我们需要爬取的数据,分别为“车辆名称”、“品牌/车型”、“价格区间”、“近一年销量”,这写元素分布在不同的类中,我们则需要通过这些类来获取元素,因此需要用到的方法为“find_elements(By.CSS_SELECTOR, value)”,即通过CCS选择器查找,“values”是查找的依据,上图可以看出分别为:
'车辆名称': 'a.tw-font-semibold'、
'品牌/车型': 'span.tw-text-color-gray-700.tw-ml-6',、
'近一年销量': 'p.tw-text-18.tw-font-semibold.tw-leading-28',、
'价格区间': 'p.tw-leading-22.tw-text-color-red-500.tw-font-semibold'
因此编写一个爬虫类如下:
from selenium import webdriver from selenium.webdriver.common.by import By import time """ 由于懂车帝页面是JavaScript动态加载的,因此直接爬取页面html无法得到动态加载的数据 因此需要模拟浏览器运行的库来应对,这里使用Selenium库 """ class Parsing: def __init__(self, url): # 设置WebDriver路径(以Edge为例) self.browser = webdriver.Edge() self.url = url def parsing(self, **kwargs): self.browser.get(self.url) for i in range(10): # 页面滑动次数 self.browser.execute_script("window.scrollTo(0, document.body.scrollHeight);") time.sleep(2) # 滑动等待时间 for key, value in kwargs.items(): elements = self.browser.find_elements(By.CSS_SELECTOR, value) #find elements by CCS selector kwargs[key] = [element.text for element in elements] #must translate to text return kwargs self.browser.quit() if __name__ == '__main__': parsing1 = Parsing('https://www.dongchedi.com/sales/sale-x-1000-x-x-x-x') my_dict = {'name': 'a.tw-font-semibold', 'brand_type': 'span.tw-text-color-gray-700.tw-ml-6', 'sold_count': 'p.tw-text-18.tw-font-semibold.tw-leading-28', 'prize': 'p.tw-leading-22.tw-text-color-red-500.tw-font-semibold'} res = parsing1.parsing(**my_dict) print(res) |
运行结果为:
为了判断这些车辆中是否是新能源车,则需要再爬取近一年新能源车辆销售前100,由于所有车辆包含新能源车辆,所以一定能把包含的全部新能源车辆爬取出来。
3.1.3 数据预处理
1爬取了数据之后需要进行一定的预处理操作,例如:爬取的既有品牌又有车型的数据,例如“特斯拉/中型SUV”,需要转化为两个数据,如下图所示:
2价格区间数据需要分成最大价格和最低价格,例如“10.58-20.68万”需要转换为两个数据并且转换为“float”类型,如下图所示:
3车辆销量数据需要去掉中间的逗号且转换为int形式,例如“425,726”需要转换为int型的4257226,如下图所示:
4另外还需要将数据中的缺失值进行处理,例如有些车没有车型仅有品牌数据“上汽通用别克”,则需要进行处理,如下图所示:
5且爬取到的数据并没有排名,因此需要按照爬取顺序添加排名。
6需要将第一次爬取的所有车辆排名与第二次爬取的新能源车辆排名进行比对,确定哪些车是新能源,并添加一个属性“new_energy”,该值为1则是新能源,为0则不是新能源;
针对上面6个问题,编写一个函数解决:
def pre_process(car_dict, energy_list): #split the 'brand/type' into 'brand' and 'type' car_dict['brand'] = [] car_dict['type'] = [] for element in car_dict['brand_type']: b_t = element.split('/') if len(b_t)>=2: car_dict['brand'].append(b_t[0]) car_dict['type'].append(b_t[1]) else: car_dict['brand'].append(b_t[0]) car_dict['type'].append('未知') del car_dict['brand_type'] #del the ',' in sold_count car_dict['sold_count'] = [int(element.replace(',','')) for element in car_dict['sold_count']] #translate 'min_prize-max_prize万' intp 'min_prize' and 'max_prize' car_dict['min_prize'] = [] car_dict['max_prize'] = [] for element in car_dict['prize']: prz = element.replace('万', '') prz = prz.split('-') car_dict['min_prize'].append(float(prz[0])) car_dict['max_prize'].append(float(prz[1])) del car_dict['prize'] #add the ranking rows = len(car_dict['name']) car_dict['rank'] = [i+1 for i in range(rows)] #new energy or not car_dict['new_energy'] = [] for element in car_dict['name']: if element in energy_list: car_dict['new_energy'].append(1) else: car_dict['new_energy'].append(0) |
3.1.4 MySql数据库存储
预处理了数据之后,将上面数据存入数据库,编写了一个类如下:
import pymysql.cursors import pymysql class Connect_DB: def __init__(self, **kwargs): self.host = kwargs.get('host', 'localhost') self.port = kwargs.get('port', 3306) self.user = kwargs.get('user', 'root') self.passwd = kwargs.get('passwd', '123456') self.db = kwargs.get('db', 'demodb') self.charset = kwargs.get('charset', 'utf8') self.connection = None self.cursor = None def connect(self): self.connection = pymysql.Connect( host=self.host, port=self.port, user=self.user, passwd=self.passwd, db=self.db, charset=self.charset ) self.cursor = self.connection.cursor() print('数据库连接成功') def disconnect(self): if self.cursor: self.cursor.close() if self.connection: self.connection.close() print('数据库关闭成功') def insert(self, data, table): # 确保数据库连接已经建立 if not self.connection or not self.cursor: self.connect() # 接下来的操作 try: self.cursor.execute(f"""create table if not exists {table} ( `rank` int primary key, `name` varchar(255), `brand` varchar(255), `type` varchar(255), sold_count int, min_prize float, max_prize float, new_energy int)""") self.connection.commit() for i in range(len(data['name'])): self.cursor.execute(f"""insert into {table} values ({data['rank'][i]}, "{data['name'][i]}", "{data['brand'][i]}", "{data['type'][i]}", {data['sold_count'][i]}, {data['min_prize'][i]}, {data['max_prize'][i]},{data['new_energy'][i]})""") self.connection.commit() print('插入数据库成功') except Exception as e: print(f"发生错误: {e}") finally: self.disconnect() if __name__ == "__main__": pass |
存入数据库后表格如下,属性分别为“近一年销量排名”,“车名”,“品牌名”,“车型”,“近一年销量”,“最低价格”,“最高价格”,“是否是新能源”:
3.1.5 文件系统存储
还需要将爬取的数据存在文件中,本文采用存储为CSV文件的方式,编写的代码如下:
def save_csv(car_dict, filename): #如果文件夹不存在,则创建 if not os.path.exists('./parsingFile'): os.mkdir('./parsingFile') rows = len(car_dict['name']) with open(f'./parsingFile/{filename}.csv', 'w', newline='', encoding='GBK') as csvfile: writer = csv.DictWriter(csvfile, fieldnames = ['rank','name','brand','type','sold_count','min_prize','max_prize','new_energy']) writer.writeheader() for i in range(rows-1): row ={key: car_dict[key][i] for key in car_dict} writer.writerow(row) print('文件已保存') |
运行后保存的文件如下:
3.1.6 数据可视化分析
本文将爬取到的数据进行可视化绘图并分析,绘制的图有:
1近一年不同品牌销量图;
用于分析近一年中什么汽车品牌销量最高,什么品牌销量最低,可用于购车品牌参考。
2近一年不同车型销量图;
用于分析近一年中什么汽车车型销量最高,什么车型销量最低,可用于购车车型参考。
3近一年不同车量销量图与是否新能源与价格区间图。
用于直观查看车辆销量于是否新能源及价格区间的关系,能提供多方面参考价值。
对于以上三个可视化方法,编写一个类实现:
import pandas as pd import matplotlib.pyplot as plt # 设置字体为黑体,正确显示负号 plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False class DrawPic: def __init__(self): pass def bar_chart(self, info, labels, values): # 绘制条形图 plt.figure(figsize=(10, 11)) plt.bar(labels, values) # 设置标题和标签 plt.title(f'一年内不同{info}汽车销售总量') plt.xlabel(info) plt.xticks(rotation='vertical') plt.ylabel('一年内销量') plt.show() import pandas as pd import matplotlib.pyplot as plt # 设置字体为黑体,正确显示负号 plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False def bar_and_line_chart(self, car_data): # 获取车型名称、销售数量、最高价格和最低价格 labels = car_data['name'] sales = car_data['sold_count'] max_price = car_data['max_prize'] min_price = car_data['min_prize'] new_energy_status = car_data['new_energy'] # 设置颜色(新能源为浅绿色,非新能源为浅红色),通过调整 alpha 值来控制颜色的浅深 colors = ['lightgreen' if new_energy else 'lightcoral' for new_energy in new_energy_status] # 创建图形和轴 fig, ax1 = plt.subplots(figsize=(17, 10)) # 绘制条形图,调整透明度 ax1.bar(labels, sales, color=colors, alpha=0.6) ax1.set_xlabel('车型') ax1.set_ylabel('销量', color='b') ax1.tick_params(axis='y', labelcolor='b') ax1.set_xticklabels(labels, rotation='vertical') # 创建第二个轴来绘制折线图 ax2 = ax1.twinx() ax2.plot(labels, max_price, 'o-', color='red', label='最高价格') ax2.plot(labels, min_price, 'o-', color='black', label='最低价格') ax2.set_ylabel('价格', color='g') ax2.tick_params(axis='y', labelcolor='g') # 添加图例 ax2.legend(loc='upper left') # 设置标题 plt.title('不同车型销售总量与价格范围(绿色是新能源车)') # 显示图形 plt.show() if __name__ == '__main__': # 读取 CSV 文件 car_data = pd.read_csv('./parsingFile/file2.csv', encoding='GBK') # 计算每个品牌的总销量,并按销量从高到低排序 brand_totals = car_data.groupby('brand')['sold_count'].sum().sort_values(ascending=False) # 获取标签和值 labels = brand_totals.index values = brand_totals.values # 创建 DrawPic 对象并绘图 drawer = DrawPic() drawer.bar_chart('品牌', labels, values) # 计算每个类型的总销量,并按销量从高到低排序 brand_totals = car_data.groupby('type')['sold_count'].sum().sort_values(ascending=False) # 获取标签和值 labels = brand_totals.index values = brand_totals.values # 创建 DrawPic 对象并绘图 drawer = DrawPic() drawer.bar_chart('车型',labels, values) drawer.bar_and_line_chart(car_data) |
运行结果如下:
1近一年不同品牌销量图;
从图中可以看出,近一年中“比亚迪”品牌销量最高,几乎达到了2.5百万台,随后是“上汽大众”、“长安汽车”、“广汽丰田”等品牌;销量最低的是“北京汽车”品牌,近一年销量不到10万台。因此说明比亚迪已成“国货之光”,备受大众欢迎!这也为还未购车的人提供了建议。
2近一年不同车型销量图;
从上图可以看出,近一年中所有车型中,“紧凑型SUV”和“紧凑型车”销量最高,都几乎达到了400万台,说明无论是紧凑型SUV还是紧凑型轿车都具有小巧、高性价比的特点,成为大众的钟爱车型;反观“大型SUV”近一年销量未达到10万台,说明大型SUV只是少数人特定场景使用的。
3近一年不同车量销量图与是否新能源与价格区间图。
可以看出,单独车量中近一年特斯拉的“model Y”销量最高,几乎达到了50万台,说明这个车型确实有很大的优势,平均价格再30万元左右,具有很高的性价比;
绿色柱状是新能源车,因此近一年中销量最高的车是新能源车,并且销量前20中有10种是新能源,说明新能源车已经占据市场巨大的份额,未来前景非常大。
今年销量前5的新能源车种,除了第一名是特斯拉,剩下4名分别是宋PLUS DM-i、元PLUS、海豚、秦PLUS DM-i,都是比亚迪的车型,说明比亚迪和特斯拉几乎不相上下,并且比亚迪的很多车型都很吃香!
对于车辆的价格区间,可以看到前100销量车的价格区间大多都在10万到50万之间,这些价格的车辆占据了几乎90%的市场份额;另外,价格区间最大的车辆是极氪的ZEEKR 001,价格区间在26.9-76.9万元,相差了50万元,查看得知原来该车几个型号价格区间在26.9-35万之间,仅仅是是2023款 FR版 100kWh 四驱超级跑车就价值70万;还有深蓝汽车的深蓝SL03,价格区间在13.99-67.79万元,相差了大约54万元,查阅得知该车几个型号价格都未超过21万,唯独2022款 730氢电版价值67万元,采用了氢燃料,怪不得这么贵!
4. 出现的问题与解决
按照解决问题花费时间排序:
1“懂车帝”有反爬虫功能,数据通过JavaScript动态更新,采用传统的“requests”方法爬取网页的html代码只能爬取到10台车的数据,爬取不到动态加载的数据。
解决方法:
使用“selenium”模块模仿真人动态浏览网页,并获取网页元素,使用“selenium”的下滑操作如下:
for i in range(10): # 页面滑动次数 self.browser.execute_script("window.scrollTo(0, document.body.scrollHeight);") time.sleep(2) # 滑动等待时间 |
向下滑动10次页面获取数据,两次滑动之间等待2秒加载页面。
2浏览器驱动器安装与配置出错,python代码不能找到下载的驱动器。
解决方法:
将下载的驱动器放在python路径的Scripts文件夹中。
3数据可视化时按照品牌汇总不同车辆的销量比较复杂。
解决方法:
采用“groupby”和“sum”的方法进行统计。
brand_totals = car_data.groupby('brand')['sold_count'].sum().sort_values(ascending=False) # 获取标签和值 labels = brand_totals.index values = brand_totals.values # 创建 DrawPic 对象并绘图 drawer = DrawPic() drawer.bar_chart('品牌', labels, values) |
4爬取的数据到CSV文件中乱码。
解决方法:
Excel不能正确显示“utf-8”编码的文件,需要在存入的时候设置编码为“GBK”。
with open(f'./parsingFile/{filename}.csv', 'w', newline='', encoding='GBK') as csvfile: writer = csv.DictWriter(csvfile, fieldnames = ['rank','name','brand','type','sold_count','min_prize','max_prize','new_energy']) writer.writeheader() |
5在一个统计图中同时绘制条形图和折线图。
解决方法:
使用“ax2 = ax1.twinx()”方法。
# 创建第二个轴来绘制折线图 ax2 = ax1.twinx() ax2.plot(labels, max_price, 'o-', color='red', label='最高价格') ax2.plot(labels, min_price, 'o-', color='black', label='最低价格') ax2.set_ylabel('价格', color='g') ax2.tick_params(axis='y', labelcolor='g') # 添加图例 ax2.legend(loc='upper left') |
还没有评论,来说两句吧...