为买车,我要爬懂车帝了

上班摸鱼看了2个星期车评,还是一头雾水,选合资还是国产?发动机cvt好还是双离合好?艾瑞泽5 GT动力足,但腰线和前脸让人吐槽,真的可靠吗。国产选长安逸动还是吉利帝豪?标志408出来了,还有艾瑞泽8很漂亮。看会视频吧,同质化严重,讲来将去就是车内车外介绍一遍。

热门视频下通常有几百条评论,我一般会翻看一遍,七个八个视频就是几千条评论,信息芜杂,没个定准,所以干脆不看了!我决定从评分入手,爬下懂车帝看到底有多少个汽车品牌。长安、吉利、奇瑞它们有多少车型,高分段的车型多不,每家热销的车子分数排在哪个段位。所以用scrapy爬了3655条数据。

正好前段时间在python技术分享过一篇《不止高效,原来pandas表格可以更美的!》,https://mp.weixin.qq.com/s/KpKF6tvdcfG7d6rf8Xbv2w ,结合里面介绍过的排序分组配色,对我们的数据进行分析。通过这些数据,可以看出厂商的产品布局和销售优势。

比如马路上很多别克牌子,我又对它没啥印象,除了知道威朗在紧凑型轿车中排名靠前,还有其它热销车型吗?所以特意看了下别克的数据:

好家伙,居然有排名第一的! 再比如韩国车企在中国没落了,落寞成什么样子呢,

可以看到,现代和起亚在小型轿车和中型MPV是有销量的,悦纳曾经也是月销过万的主流合资轿车,这里它虽然位于小型桥车销量NO.10,但8月仅销售303辆。 确实有点惨淡啊,除了伊兰特,8月销量超过1千辆的只有一个库斯途,其它最多的的月销也只有1到3百辆,它们曾经非常受欢迎,不过随着汽车行业的更新换代,逐渐淡出了消费者们的视野。

评分最高的车型

再举个🌰,我想知道486个品牌中,每个品牌评分最高的车型

486个品牌的车型评分,已上传,http://ssw.fit/file/ 。由于部分品牌没有车型,如“众泰”没一款车型,所以爬的时候不会把这种写入csv文件中。一共3655条数据,也就是3655个车型。

对car.csv进行处理:

1
2
3
4
5
6
7
8
9
10
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多线程加快速度(这是个好工具,咱们的另一篇文章《3个python小工具,Linux服务器性能直线飞起!!!》,https://mp.weixin.qq.com/s/x4jZN4TisfTzdzVGSwZlWg 也用到过它)

  • 使用的爬虫框架是scrapy,它结构清晰,使用起来方便,比所有内容写到一个文件里好些。

  • 有些页面是动态加载的,需要使用selenium模拟页面向下滚动加载,把滚动条拉到最下面。

  • 大概运行过程是这样的:

好了,先从观察页面开始。

猜了个猜

观察它的url。首先来个十八连猜,猜下它尾部的18个“x”分别代表什么意思?

1
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才找得到:

1
2
3
4
5
6
7
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以内的数字基本能覆盖到所有品牌

1
2
pool = ThreadPool(10)
pool.map(get_brand_id,[i for i in range(1,1000)])

综上,获取品牌对应id的完整脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# -*- 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个汽车品牌:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
前面略...
 '雪佛兰': 6,
 '雪铁龙': 21,
 '零跑汽车': 207,
 '雷丁': 282,
 '雷克萨斯': 22,
 '雷诺': 46,
 '雷诺三星': 301,
 '雷达汽车': 514,
 '霍顿': 278,
 '领克': 174,
 '领志': 309,
 '领途汽车': 247,
 '飞凡汽车': 401,
 '飞碟汽车': 404,
 '首望': 340,
 '马自达': 15,
 '驭胜': 167,
 '骐铃汽车': 104,
 '高合': 249,
 '魏牌': 66,
 '黄海': 132,
 '龙程汽车': 415
 }

品牌对应的url如下,我们可以挑选自己感兴趣的品牌url发送请求:

1
2
3
4
5
6
7
[
 前面略...
 '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设置

整个目录结构如下:

  1. 创建scrapy项目
    1
    2
    3
    4
    5
    #创建scrapy项目
    scrapy startproject dcd
    cd dcd
    #生成一个爬虫
    scrapy genspider car "https://www.dongchedi.com/"
    
  2. 修改settings.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 是否遵守协议,设置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,
    }
    
  3. 新建一个chrom_middlewares.py文件

第2步DOWNLOADER_MIDDLEWARES设置的下载中间件,我们自己编写: chrom_middlewares.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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模拟页面向下滚动加载全部车型,否则取到的车型是不全的。

1
2
3
4
5
6
7
8
9
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

  1. item.py ```py import scrapy

class DcdItem(scrapy.Item): #品牌 brand = scrapy.Field() #车型 name = scrapy.Field() #评分 score = scrapy.Field() #特点 title = scrapy.Field()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
这几个字段的意思用箭头标明了:

![](http://www.justdopython.com/assets/images/2022/09/scrapydcd/12.png)

5. `car.py`
```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 获取品牌

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文件

1
lis = html.xpath('//*[@id="__next"]/div[1]/div[2]/div/ul/li[position()>= 1]')

  1. 新增一个启动爬虫的文件start.py
    1
    2
    from scrapy.cmdline import execute
    execute('scrapy crawl car'.split(' '))
    

    文件位置:

在pycharm中右击即可运行爬虫:

排序分组配色

参考上文提到的《不止高效,原来pandas表格可以更美的!》

假如你想了解长安吉利奇瑞这3个品牌,那么在car.py中填写对应的url

1
2
#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个品牌的数据了,方便我们配色和对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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色,看看它们的数据是否有惊喜

代码下载地址

已上传到 linux服务器上,http://ssw.fit/file/

总结

爬完了

Python Geek Tech wechat
欢迎订阅 Python 技术,这里分享关于 Python 的一切。