为了买车,我爬了懂车帝!
The following article is from Python技术 Author 派森酱
(给程序员零距离加星标,了解项目开发.)
文 | ssw
来源:Python 技术「ID: pythonall」
上班摸鱼看了2个星期车评,还是一头雾水,选合资还是国产?发动机cvt好还是双离合好?艾瑞泽5 GT动力足,但腰线和前脸让人吐槽,真的可靠吗。国产选长安逸动还是吉利帝豪?标志408出来了,还有艾瑞泽8很漂亮。看会视频吧,同质化严重,讲来将去就是车内车外介绍一遍。
热门视频下通常有几百条评论,我一般会翻看一遍,七个八个视频就是几千条评论,信息芜杂,没个定准,所以干脆不看了!我决定从评分入手,爬下懂车帝看到底有多少个汽车品牌。长安、吉利、奇瑞它们有多少车型,高分段的车型多不,每家热销的车子分数排在哪个段位。所以用scrapy爬了3655条数据。
正好前段时间在python技术
分享过一篇《不止高效,原来pandas表格可以更美的!》 ,结合里面介绍过的排序分组配色
,对我们的数据进行分析。通过这些数据,可以看出厂商的产品布局和销售优势。
比如马路上很多别克牌子,我又对它没啥印象,除了知道威朗在紧凑型轿车中排名靠前,还有其它热销车型吗?所以特意看了下别克的数据:
好家伙,居然有排名第一的!再比如韩国车企在中国没落了,落寞成什么样子呢,
可以看到,现代和起亚在小型轿车和中型MPV是有销量的,悦纳曾经也是月销过万的主流合资轿车,这里它虽然位于小型桥车销量NO.10,但8月仅销售303辆。确实有点惨淡啊,除了伊兰特,8月销量超过1千辆的只有一个库斯途,其它最多的的月销也只有1到3百辆,它们曾经非常受欢迎,不过随着汽车行业的更新换代,逐渐淡出了消费者们的视野。
评分最高的车型
再举个🌰,我想知道486个品牌中,每个品牌评分最高的车型
486个品牌的车型评分,已上传,http://ssw.fit/file/ 。由于部分品牌没有车型,如“众泰”没一款车型,所以爬的时候不会把这种写入csv文件中。一共3655条数据,也就是3655个车型。
对car.csv进行处理:
import pandas as pd
df2 = pd.read_csv("D:/桌面/car.csv",encoding='gb18030')
#取出评分大于0的(也就是去掉懂车帝上显示“暂无评分”的)
x = df2.groupby('评分').filter(lambda x:x['评分'].mean() > 0)
#取各组品牌中评分最高的
t = x.sort_values('评分',ascending=False).groupby('品牌', as_index=False).first()
t.sort_values('评分',ascending=False,inplace=True)
t.to_csv('评分榜.csv', index = True)
输出如下:
可以看到,所有品牌中,评分最高的车型是劳斯莱斯的“幻影”,4.74分,这个分数很高了。要知道国产长安最高3.88(长安UNI-K),吉利最高3.9(星越L),日产最高也没超过4分。
说明几个地方
下面开始整活。先说明几个地方:
因为品牌较多,对应不同的url,可以利用multiprocessing.dummy多线程加快速度。
使用的爬虫框架是scrapy,它结构清晰,使用起来方便,比所有内容写到一个文件里好些。
有些页面是动态加载的,需要使用selenium模拟页面向下滚动加载,把滚动条拉到最下面。
大概运行过程是这样的:
好了,先从观察页面开始。
猜了个猜
观察它的url。首先来个十八连猜,猜下它尾部的18个“x”分别代表什么意思?
https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x
我先猜一个,倒数第3个x代表汽车品牌,不信吗,修改数字的话,第二个红框会出现不同的品牌:
所以url的倒数第三位,数字3代表奔驰,2为奥迪,4是宝马。其它你可以试一下,能搜索到结果的数字大概在600以内。我猜汽车品牌数也在600内吧。当然不能这么一个个猜, 还是赶紧干正事,找出每个品牌对应的数字,这样才好向相关品牌的页面发送请求。
找了个找
一共有6个属性为layout_label__1qfS8
的span标签,我们要找的是第6个,即找到“已知条件”这个span标签。
这个时候下面脚本中的条件condition[5]才成立,BeautifulSoup才找得到:
soup = BeautifulSoup(rep.text,'html.parser')
condition = soup.find_all('span', class_='layout_label__1qfS8')
# 当num大于500时,有可能没这个品牌,condition[5]会报错
try:
condition[5].next_sibling.a.text
except Exception as e:
pass
因为数字大于500时,很可能没这个品牌,页面上不会出现“已知条件”,而是提示“0车系符合条件”
上面的next_sibling属性用来查询兄弟节点,也就是“已选条件”那个span的下一个span;next_sibling.a.text,下一个span的a标签里的文字就是品牌的名字
找出品牌对应的id
BeautifulSoup找到数据后,因为品牌对应的url较多,使用multiprocessing.dummy多线程加快速度。另外,数字大于500时,很可能找不到品牌,所以循环1000以内的数字基本能覆盖到所有品牌
pool = ThreadPool(10)
pool.map(get_brand_id,[i for i in range(1,1000)])
综上,获取品牌对应id的完整脚本:
# -*- coding: utf-8 -*-
import json,re,requests,ssl
from bs4 import BeautifulSoup
from multiprocessing.dummy import Pool as ThreadPool
num_list = []
brand_dict = {}
def get_brand_id(num):
x = 'https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-%s-x-x' % num
rep = requests.get(x,timeout=10)
soup = BeautifulSoup(rep.text,'html.parser')
condition = soup.find_all('span', class_='layout_label__1qfS8')
# 当num大于500时,有可能没这个品牌,condition[5]会报错
try:
s = condition[5].next_sibling.a.text
print('s111', s)
except Exception as e:
pass
for span in condition:
if span.string == '已选条件':
print('ok')
brand_dict[s] = num
num_list.append(num)
pool = ThreadPool(10)
pool.map(get_brand_id,[i for i in range(1,1000)])
print(num_list)
print(brand_dict)
输出结果如下,总共486个汽车品牌:
{
前面略...
'雪佛兰': 6,
'雪铁龙': 21,
'零跑汽车': 207,
'雷丁': 282,
'雷克萨斯': 22,
'雷诺': 46,
'雷诺三星': 301,
'雷达汽车': 514,
'霍顿': 278,
'领克': 174,
'领志': 309,
'领途汽车': 247,
'飞凡汽车': 401,
'飞碟汽车': 404,
'首望': 340,
'马自达': 15,
'驭胜': 167,
'骐铃汽车': 104,
'高合': 249,
'魏牌': 66,
'黄海': 132,
'龙程汽车': 415
}
品牌对应的url如下,我们可以挑选自己感兴趣的品牌url发送请求:
[
前面略...
'https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-101-x-x',
'https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-76-x-x',
'https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-1-x-x',
'https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-126-x-x',
]
下面会用scrapy爬虫框架对这些url发起请求,数据写入csv
scrapy设置
整个目录结构如下:
创建scrapy项目
#创建scrapy项目
scrapy startproject dcd
cd dcd
#生成一个爬虫
scrapy genspider car "https://www.dongchedi.com/"
修改 settings.py
# 是否遵守协议,设置false
ROBOTSTXT_OBEY = False
#设置请求头
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0'
}
#下载中间件
DOWNLOADER_MIDDLEWARES = {
'dcd.chrom_middlewares.DcdDownloaderMiddleware': 543,
}
ITEM_PIPELINES = {
'dcd.pipelines.DcdPipeline': 300,
}
新建一个chrom_middlewares.py文件
第2步DOWNLOADER_MIDDLEWARES设置的下载中间件,我们自己编写:chrom_middlewares.py
import time
from selenium import webdriver
from scrapy.http.response.html import HtmlResponse
class DcdDownloaderMiddleware(object):
def __init__(self):
# selenium加载浏览器
options = webdriver.ChromeOptions()
options.add_argument('--no-sandbox')
options.add_argument('--disable-gpu')
options.add_argument('--ignore-certificate-errors')
options.add_argument('--ignore-ssl-errors')
self.driver = webdriver.Chrome(executable_path=r"C:\drf2\drf2\chromedriver.exe",options=options)
self.driver.maximize_window()
#重写process_request方法
def process_request(self, request, spider):
print('request.url',request.url)
self.driver.get(request.url)
js = 'return document.body.scrollHeight;'
height = 0
#selenium模拟页面向下滚动加载全部页面
if request.url != 'https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x':
while True:
new_height = self.driver.execute_script(js)
if new_height > height:
self.driver.execute_script('window.scrollTo(0, document.body.scrollHeight)')
height = new_height
time.sleep(1)
else:
print("滚动条已经处于页面最下方!")
break
source = self.driver.page_source
# 创建一个response对象,把页面信息都封装在reponse对象中
response = HtmlResponse(url=self.driver.current_url,body=source,request = request,encoding="utf-8")
return response
对process_request说明一点:
如果车型多,需要滚动鼠标分一次或多次才能加载完毕,这个时候需要selenium模拟页面向下滚动加载全部车型,否则取到的车型是不全的。
while True:
new_height = self.driver.execute_script(js)
if new_height > height:
self.driver.execute_script('window.scrollTo(0, document.body.scrollHeight)')
height = new_height
time.sleep(1)
else:
print("滚动条已经处于页面最下方!")
break
item.py
import scrapy
class DcdItem(scrapy.Item):
#品牌
brand = scrapy.Field()
#车型
name = scrapy.Field()
#评分
score = scrapy.Field()
#特点
title = scrapy.Field()
这几个字段的意思用箭头标明了:
car.py
import scrapy
from lxml import etree
from dcd.items import DcdItem
import os,csv
if os.path.exists('D:/桌面/car.csv'):
print('delete?')
os.remove('D:/桌面/car.csv')
f = open('D:/桌面/car.csv', 'a+', newline='', encoding='gb18030')
f_csv = csv.writer(f)
f_csv.writerow(['品牌','车型', '评分', '特点'])
class RainSpider(scrapy.Spider):
name = 'car'
allowed_domains = ['https://www.dongchedi.com/']
start_urls = ['https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-11-x-x','https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-13-x-x']
def parse(self, response):
print('html111')
html =etree.HTML(response.text)
item = DcdItem()
brand = html.xpath('//*[@id="__next"]/div[1]/div[2]/div/div[4]/span[2]/div/div/a/text()')[0]
lis = html.xpath('//*[@id="__next"]/div[1]/div[2]/div/ul/li[position()>= 1]')
print('111 lis',lis)
for li in lis:
name = li.xpath('./div/a[1]/text()')[0]
try:
#有评分
score = li.xpath('./div/a[2]/text()')[0].split('分')[0]
except Exception as e:
#无评分
score = 0
try:
#有标题
title = li.xpath('./div/span/text()')[0]
# print('title111',title)
except Exception as e:
#无标题
title = '无'
print(name,score,title)
f_csv.writerow([brand,name,score,title])
item['name'] = name
item['score'] = score
item['title'] = title
yield item
下面对car.py的2个地方进行说明
5.1 获取品牌
brand = html.xpath('//*[@id="__next"]/div[1]/div[2]/div/div[4]/span[2]/div/div/a/text()')[0]
xpath路径,在edge浏览器中可以通过右键“检查”找到元素,再右键选择“复制”->"复制 Xpath"
按crtl+F键可以粘贴刚才复制的xpath,按回车键,页面上会突出显示对应的元素。
5.2 获取所有的li标签,代表一辆辆汽车信息
然后循坏这些li标签,获取到车型、评分、左上角的蓝色说明文字,写入csv文件
lis = html.xpath('//*[@id="__next"]/div[1]/div[2]/div/ul/li[position()>= 1]')
新增一个启动爬虫的文件 start.py
from scrapy.cmdline import execute
execute('scrapy crawl car'.split(' '))
文件位置:
在pycharm中右击即可运行爬虫:
排序分组配色
参考上文提到的《不止高效,原来pandas表格可以更美的!》
假如你想了解长安吉利奇瑞这3个品牌,那么在car.py中填写对应的url
#url中的73代表吉利,18是奇瑞,35是长安,对这3个品牌发起请求
start_urls = ['https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-73-x-x','https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-18-x-x','https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-35-x-x']
这样car.csv中就只有这3个品牌的数据了,方便我们配色和对比
import pandas as pd
from datetime import datetime,timedelta
df2 = pd.read_csv("D:/桌面/car.csv",encoding='gb18030')
#取出评分大于0的(也就是去掉懂车帝上显示“暂无评分”的)
x = df2.groupby('评分').filter(lambda x:x['评分'].mean() > 0)
x.sort_values('评分',ascending=True,inplace=True)
new = x.groupby(['品牌','评分','车型','特点'],as_index=False)
new3 = new.all()
#给每种品牌加上颜色
#评分大于3.8的,用黄色标注
new3.style.highlight_between(left='吉利汽车',right='吉利汽车',subset=['品牌'],props='background:#ffa5a5')\
.highlight_between(left='奇瑞',right='奇瑞',subset=['品牌'],props='background:#a1eafb')\
.highlight_between(left='长安',right='长安',subset=['品牌'],props='background:#71c9ce')\
.highlight_between(left=3.8,right=5,subset=['评分'],props='background:#f9ed69')
输出结果如下:
从数据可以看出,国内一线品牌产品线丰富,吉利在小型SUV、紧凑型轿车、紧凑型SUV都有热销产品,奇瑞仅瑞虎3x和瑞虎5x位于销量榜前10(怎么没有出口汽车?),看来理工男从产品受欢迎程度上来说离一线品牌还有差距。可以根据自己的喜好,给想看的品牌配上它们的logo色,看看它们的数据是否有惊喜。
本文是由 Python技术 公众号粉丝 ssw 投稿,欢迎大家继续踊跃投稿!
- END -
文章精选1、坏起来了,车机也开始弹广告!2、能ping通,TCP就一定能连通吗?3、三大运营商集体发出警告:这些iPhone买了也用不了!4、看片时的烦人操作,终于被干掉!捡到宝了~5、打破常规!微信新版本来了,这下苹果用户舒坦了~6、抓 https 加密数据,偷偷摸摸爽得很!7、聊聊索引失效的10种场景,太坑了
8、美国开始「人才外流」!大量中国学者放弃美国教职归国
8、美国开始「人才外流」!大量中国学者放弃美国教职归国