查看原文
其他

为了买车,我爬了懂车帝!

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设置

整个目录结构如下:


  1. 创建scrapy项目
#创建scrapy项目
scrapy startproject dcd
cd dcd
#生成一个爬虫
scrapy genspider car "https://www.dongchedi.com/"
  1. 修改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,
}
  1. 新建一个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


  1. item.py
import scrapy


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

这几个字段的意思用箭头标明了:


  1. 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]')

  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、美国开始「人才外流」!大量中国学者放弃美国教职归国


更多精彩等待你的发现点分享点点赞点在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存