使用Python爬取易车网的车型数据

今天跟大家分享下最近研究的python爬取易车网的车型数据. 本来app上有现成的api来获取,但是没有停售的车型数据,所以只好现学现用了.

先上代码传送门

思路

品牌:
关闭Chrome的JavaScript支持,打开车型页发现品牌数据并没有加载出来,由此可以断定品牌数据是通过JavaScript动态加载出来的,打开Chrome的开发工具分析js


image.png

可以看出品牌数据来源于接口 http://api.car.bitauto.com/CarInfo/getlefttreejson.ashx?tagtype=chexing&pagetype=masterbrand&objid=0,解析该接口返回的数据就可以拿到所有的品牌数据. 但是有点小坑的是返回的数据不是标准的json,需要截取里面的json部分并为所有key加上双引号才能正常解析.

车系:
在网站上随便点开一个品牌,比如http://car.bitauto.com/tree_chexing/mb_9/,通过修改浏览器的JavaScript设置然后重载页面,发现车系的数据没有受到影响,那么我们就可以正常使用response.xpath()来爬取页面. 查看网页的源代码找到元素位置

image.png

查看不同的品牌发现有的车系有供应商而有的车系则没有,那么处理这部分标签的时候需要区别对待.

车型:
网页上进入车型详情页可以点击左侧的车型进入,也可以点击车系详情中的车型进入详情页,所以我们在解析车系的时候就可以顺便爬取车型页了.


image.png

车型的数据分为在售车型和停售车型,在售车型可以通过解析页面的标签获取,但是停售车型需要通过接口http://car.bitauto.com/AjaxNew/GetNoSaleSerailListByYear.ashx?csID=2593&year=2017来取得,这个接口返回来的是标准的json可以直接使用.

Spider的全部代码

# coding=utf-8
import json
import re

import scrapy
from scrapy.spiders import Rule

from scrapy.linkextractors import LinkExtractor
from yiche.items import BrandItem, SerialItem, ModelItem


# json替换key
def replacea(matched):
    return '\"' + matched.group('value') + '\":'


# 解析车系Item
def parse_serial_item(serial, bid, vendor):
    item = SerialItem()
    item['id'] = serial.xpath('div/div/a/@id')[0].re(r'n(\d+)')[0]
    item['bid'] = bid
    item['name'] = serial.xpath('div/div/a/@title')[0].extract()
    item['vendor'] = vendor
    item['logo'] = serial.xpath('div/div/a/img/@src')[0].extract()
    sell = serial.xpath('div/ul/li[@class="price"]/a/text()')[0].re(ur'停售')
    item['sell'] = '0' if sell else '1'
    return item


class YiCheSpider(scrapy.Spider):
    name = "yiche"

    rules = (
        # 所有车系
        Rule(LinkExtractor(allow=(r'http://car\.bitauto\.com/tree_chexing/mb_\d+/$',)), callback='parse_serial', follow=True),
        # 在售车型
        Rule(LinkExtractor(allow=(r'http://car\.bitauto\.com/\w+/$',)), callback='parse_model', follow=True),
        # 停售车型
        Rule(LinkExtractor(allow=(r'http://car\.bitauto\.com/AjaxNew/GetNoSaleSerailListByYear\.ashx?csID=\d+&year=\d+$',)),
             callback='parse_model_selled', follow=True),
    )

    def start_requests(self):
        url = 'http://api.car.bitauto.com/CarInfo/getlefttreejson.ashx?tagtype=chexing&pagetype=masterbrand&objid=0'
        yield scrapy.Request(url, callback=self.parse, dont_filter=True)

    # 解析品牌
    def parse(self, response):
        print '==> %s' % response.url

        result = re.sub('(?P<value>\w+):', replacea, response.text[response.text.find('{'):response.text.rfind('}') + 1])
        data = json.loads(result)
        for char in data['char']:
            print '==> %s' % char
            try:
                for brand in data['brand']['%s' % char]:
                    item = BrandItem()
                    item['id'] = brand['id']
                    item['name'] = brand['name']
                    item['logo'] = 'http://image.bitautoimg.com/bt/car/default/images/logo/masterbrand/png/100/m_%s_100.png' % item['id']
                    item['initial'] = char
                    yield item

                    url = 'http://car.bitauto.com/tree_chexing/mb_%s/' % item['id']
                    request = scrapy.Request(url, callback=self.parse_serial, dont_filter=True)
                    request.meta['bid'] = item['id']
                    yield request
            except KeyError:
                pass

    # 解析车系
    def parse_serial(self, response):
        print '==> %s' % response.url

        bid = response.meta['bid']
        # 品牌下的全部车系及车型
        brands = response.xpath('//*[@id="divCsLevel_0"]/*')
        size = len(brands)
        if size % 2 == 0:
            for i in range(size / 2):
                vendor = brands[i * 2].xpath('a/text()')[0].re(r'(\w+)>>')[0]
                for serial in brands[i * 2 + 1].xpath('div'):
                    item = parse_serial_item(serial, bid, vendor)
                    yield item

                    url = 'http://car.bitauto.com%s' % serial.xpath('div/div/a/@href')[0].extract()
                    request = scrapy.Request(url, callback=self.parse_model, dont_filter=True)
                    request.meta['sid'] = item['id']
                    yield request
        else:
            for serial in brands.xpath('div'):
                item = parse_serial_item(serial, bid, '')
                yield item

                url = 'http://car.bitauto.com%s' % serial.xpath('div/div/a/@href')[0].extract()
                request = scrapy.Request(url, callback=self.parse_model, dont_filter=True)
                request.meta['sid'] = item['id']
                yield request

    # 解析车型
    def parse_model(self, response):
        print '==> %s' % response.url

        sid = response.meta['sid']
        # 在售车型
        classify = ''
        for tr in response.xpath('//*[@id="compare_sale"]/tbody/*'):
            tit = tr.xpath('@class')
            if len(tit) == 0:
                item = ModelItem()
                item['id'] = tr.xpath('td[1]/a[1]/@href')[0].re(r'/m(\d+)/')[0]
                item['sid'] = sid
                item['name'] = tr.xpath('td[1]/a[1]/text()')[0].extract()
                item['classify'] = classify
                item['sell'] = '1'
                yield item
            else:
                ths = tr.xpath('th[1]/text()')
                th = ths[1].extract().rstrip() if len(ths) > 1 else ths[0].extract().rstrip()
                strong = tr.xpath('th[1]/strong/text()')[0].extract()
                classify = strong + (('/' + th) if len(th) > 0 else '')

        # 停售车型
        years = response.xpath('//*[@id="carlist_nosaleyear"]/a/@id').extract()
        for year in years:
            url = 'http://car.bitauto.com/AjaxNew/GetNoSaleSerailListByYear.ashx?csID=%s&year=%s' % (sid, year)
            request = scrapy.Request(url=url, callback=self.parse_model_selled, dont_filter=True)
            request.meta['sid'] = sid
            yield request

    @staticmethod
    def parse_model_selled(response):
        print '==> %s' % response.url

        sid = response.meta['sid']
        try:
            datas = json.loads(response.body_as_unicode())
            for data in datas:
                classify = data['Engine_Exhaust'] + '/' + data['MaxPower'] + ' ' + data['InhaleType']
                for car in data['carList']:
                    item = ModelItem()
                    item['id'] = car['CarID']
                    item['sid'] = sid
                    item['name'] = car['YearType'] + ' ' + car['Name']
                    item['classify'] = classify
                    item['sell'] = '0'
                    yield item
        except ValueError:
            print 'model parse error,serial_id[%s].' % sid
            pass

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,460评论 25 707
  • 最近想在工作相关的项目上做技术改进,需要全而准的车型数据,寻寻觅觅而不得,所以就只能自己动手丰衣足食,到网上获(窃...
    littlelory阅读 3,871评论 7 19
  • 昨天大概睡了两个小时不到。 半夜12点多就醒来了,睡不着又很饿,结果一开始吃就停不下来了,把冰箱里仅剩下的存粮都吃...
    脸大的胖妞阅读 175评论 0 0
  • 两只眼皮在打架,我的月亮去哪啦? 月亮你真不该呀,谁把思念寄予他? 没能抓住佳节的尾巴,却无法忘记他! 明明放不下...
    瞿静阅读 191评论 0 0
  • 他把自己的创业经验总结成为「3+1理论」,「第一要做刚需;第二是项目能完成小型闭环;第三是要有现金流。再加一点『是...
    小飞机1948阅读 129评论 0 0