1

为买车,我要爬懂车帝了

 1 year ago
source link: http://www.justdopython.com/2022/09/26/python-scrapydcd/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

为买车,我要爬懂车帝了

2022-09-26

| python

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

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

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

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

1.png

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

2.png

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

评分最高的车型

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

3.png

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.png

可以看到,所有品牌中,评分最高的车型是劳斯莱斯的“幻影”,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模拟页面向下滚动加载,把滚动条拉到最下面。

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

    5.gif

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

观察它的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代表汽车品牌,不信吗,修改数字的话,第二个红框会出现不同的品牌:

6.png

所以url的倒数第三位,数字3代表奔驰,2为奥迪,4是宝马。其它你可以试一下,能搜索到结果的数字大概在600以内。我猜汽车品牌数也在600内吧。当然不能这么一个个猜, 还是赶紧干正事,找出每个品牌对应的数字,这样才好向相关品牌的页面发送请求。

一共有6个属性为layout_label__1qfS8的span标签,我们要找的是第6个,即找到“已知条件”这个span标签。

7.png

这个时候下面脚本中的条件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车系符合条件”

8.png

上面的next_sibling属性用来查询兄弟节点,也就是“已选条件”那个span的下一个span;next_sibling.a.text,下一个span的a标签里的文字就是品牌的名字

9.png

找出品牌对应的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设置

整个目录结构如下:

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

11.png
  1. item.py ```py import scrapy

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

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

![](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 获取品牌

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,按回车键,页面上会突出显示对应的元素。

13.png

5.2 获取所有的li标签,代表一辆辆汽车信息

然后循坏这些li标签,获取到车型、评分、左上角的蓝色说明文字,写入csv文件

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

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

    文件位置:

15.png

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

16.png

排序分组配色

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

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

17.png
#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')

输出结果如下:

18.png

从数据可以看出,国内一线品牌产品线丰富,吉利在小型SUV、紧凑型轿车、紧凑型SUV都有热销产品,奇瑞仅瑞虎3x和瑞虎5x位于销量榜前10(怎么没有出口汽车?),看来理工男从产品受欢迎程度上来说离一线品牌还有差距

可以根据自己的喜好,给想看的品牌配上它们的logo色,看看它们的数据是否有惊喜

代码下载地址

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

19.png

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK