美文网首页
抓取唯品会商品数据

抓取唯品会商品数据

作者: 我来组成头部 | 来源:发表于2017-09-13 23:00 被阅读0次

    目标网站:http://www.vip.com/

    方法:scrapy Mysql 正则表达式

    思路

    第一步:分析爬虫入口,需要爬取唯品会在售的所有商品,打开首页可以看到有个分类按钮,点击进去分为商品分类和品牌分类。想要一并分析一下各品牌的受欢迎程度和在售商品数量,所以选择品牌分类。入口找到:http://category.vip.com/?act=brand
    第二步:找到各品牌页面的url
    第三步:爬取各品牌页面下的商品数据和品牌收藏数、商品数量。
    第四步:在mysql数据库中,分表存储品牌数据和商品数据,两表以品牌ID来关联。

    实战

    基本思路就是上面的,下面开干。
    首先,打开品牌分类页,可以看到各大类下都有小的分类,不过最前面有个全部,里面包涵了该大类下的全部品牌,点击品牌图片可进入对应的品牌页,我们只需要通过xpath定位到每个品牌小格子来提取url即可,不过我们查看品牌分类页的网页源码并没有找到这些url,所以可以怀疑这个是异步加载的。右键审查元素,打开log,刷新页面,页面往下拉,显示出第一个分类品质女装的品牌信息。可以看到加载了一个XHR格式的页面和一堆的图片,看一下这个页面返回的数据,看到data的值中是一个列表,展开第一个元素,可以发现’name :"乐为COMME LA VIE帽子专场",和第一个品牌对上了,link的值也和该品牌的url对上了,再看后面的元素也能对上。

    抓取品牌数据
    这时,可以确定这个页面就是品牌数据页面,反过来看看请求数据。是一个get请求,但是有很多请求参数,对比多个大类的品牌数据页可以发现只有三个值是变动的
    get请求参数
    callback 分析可以看到是每个大类的英文名称0
    department_idnew_cat_id 这都是一些数据,看似毫无规律,不过在查看品牌分类页面源码时发现中间有很大一段的javascript代码,完全看不懂,不过倒也是有一些数字,观察可以发现其实都出自这里。
    网页源码中包涵的数据 请求参数都找到了,通过正则表达式获取这段代码,再用eval函数来格式化,后面就可以以字典的形式来取值。之后直接构造get请求的url。测试发现,每个品牌的url只是ID的改变,这个id就是brand_id,那么只需要爬取到这个即可。返回的代码也不是js代码,同样使用正则表达式和eval来格式化数据方便取值。

    得到品牌页面的url后,可以看到有一个收藏品牌数,我们就以此作为品牌受欢迎的数值提现。那么我们先来找到这个数据。在页面源码中同样搜不到现在的收藏数。审查元素可以初步判断是来自javascript。
    抓包查看js,能找到这个收藏数。

    抓包收藏数 该get请求的参数只有brand_id,这个简单。不过在调试过程中无法正常获值,后来测试发现请求头中需要加上Referer,这样就正常了。
    再来抓包分析商品数据,发现也是来自XHR类型页面,打开品牌页面时加载了3个XHR页面,第一个返回的是该品牌下的商品分类,第二个返回的主要是一些数字列表,第三个就是我们想要的商品数据。
    抓包分析商品数据 不过这个get请求的参数有点多,变化的只有两个值productIdsr,其中r为品牌id,productIds这里面是数字列表,分析发现这些都来自第二个XHR页面返回的数据 商品信息请求参数抓包 其中products的数据即是前面的productIds,这也是商品ID,只是默认一次只加载50个商品。'total'为该品牌商品总数。
    接下来,我们分析该get请求,fromIndex为起始值,batchSize截止值。
    接下来就可以带入参数发起get请求获取到products后,再带入商品数据页面的请求,获取到商品信息,然后存储到mysql数据库。
    不多说,直接上代码:
    class VipSpider(scrapy.Spider):
        name = 'vip'
        allowed_domains = ['vip.com']
        start_urls = ['http://category.vip.com/?act=brand']
    
        def parse(self, response):
            #获取源码里的json数据,包涵分类名称,品牌信息页面的ID连接所需的数据
            data = eval(re.findall(r'<script>var brandCategoryData = (\[.*?\]);', response.text)[0])
            for x in data:
                callback = x['key']
                department_id = x['param']['department_id']
                new_cat_id = ",".join([z['cids'] for z in x['word']])
                url = 'http://category.vip.com/ajax/getSearchBrandBase.php?callback={}&page=190022&domain=www&show_in=0%2C1%2C2&ps=200&warehouse=VIP_HZ&fields=logo%2Clink%2Cimg%2Cname%2Cagio%2Cforeword%2Cbrand_id%2Csell_time_from&sort=operate-desc&department_id={}&new_cat_id={}'.format(callback,department_id,new_cat_id)
    
                yield Request(url,callback=self.getid,meta={'fl_name':x['chns']}) #传递分类名称fl_name
    
        def getid(self,response):
            #分析一个分类下品牌信息,找出品牌id,组成品牌店铺url和店铺下所有商品ID的url
            for x in eval(re.findall(r'"success","data":(\[.*?\]),"info":', response.text)[0]):
                mm = Sql.select_pp(x['brand_id'])#判断该品牌是否已经保存
                if mm == 1:
                    print u'该品牌数据已经保存'
                else:
                    name_url = 'http://list.vip.com/%s.html' %x['brand_id'] #获取品牌名称的
                    data_url ='http://list.vip.com/api-ajax.php?callback=getMerchandiseIds&getPart=getMerchandiseRankList&r=%s' %x['brand_id']
                    yield Request(name_url,callback=self.getname,meta={'brand_id':x['brand_id'],'fl_name':response.meta['fl_name']})
                    yield Request(data_url,callback=self.getdata_url,meta={'brand_id':x['brand_id']})
    
        def getname(self,response):
            #获取品牌的名称,构造品牌收藏数所在的页面url
            name = response.xpath('//meta[@name="keywords"]/@content').extract()[0]
            scs_url = 'http://fav.vip.com/api/fav/sales/isfavanducount?callback=inquiryFavStatusAmountCB&business=VIPSALES&brand_id='+str(response.meta['brand_id'])
            yield Request(scs_url,callback=self.getshoucang,meta={'name':name,'brand_id':response.meta['brand_id'],'fl_name':response.meta['fl_name']},headers={'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36',
    'Referer':response.url,
    })#注意,收藏数所在的页面,在请求的时候请求头需要加上Referer,否则为空
        def getshoucang(self,response):
            #获取品牌的收藏数,构造店铺商品数量统计的页面url
            shoucang = re.findall(r'"ucount":(.*?),"brandfav"', response.text)[0]
            lei_url = 'http://list.vip.com/api-ajax.php?callback=getCategoryListCB&getPart=getCategoryList&r='+str(response.meta['brand_id'])
            yield Request(lei_url,callback=self.getlei,meta={'name':response.meta['name'],'brand_id':response.meta['brand_id'],'shoucang':shoucang,'fl_name':response.meta['fl_name']})
    
        def getlei(self,response):
            #获取商品总数,并且返回数据品牌相关的数据,传给pipelines保存到数据库
            item = WeipinghuiItem()
            lei = 0
            for x in eval(re.findall(r'{"category":(\[.*?\]),"size":', response.text)[0]):
                lei =lei + int(x['total'])
            item['pp_name'] = response.meta['name']
            item['fl_name'] = response.meta['fl_name']
            item['pp_shoucang'] = response.meta['shoucang']
            item['pp_url'] = 'http://list.vip.com/%s.html' %response.meta['brand_id']
            item['pp_id'] = response.meta['brand_id']
            item['pp_shu'] = lei
            yield item
        def getdata_url(self,response):
            #获取店铺所有商品的id,由于每一次请求商品详情,最多只能50个,故以for循环进行分割,构造多个商品详情列表所在页面的url
            data =eval(re.findall(r'"products":(\[.*?\]),"keepTime"', response.text)[0])
            if data==[]:
                print  '已经抢购一空'
            else:
                b =[data[i:i+50] for i in range(0,len(data),50)]
                for u in [",".join(x) for x in b]:
                    url = 'http://list.vip.com/api-ajax.php?callback=getMerchandiseDroplets1&getPart=getMerchandiseInfoList&productIds=%s&r=%s' %(u,str(response.meta['brand_id']))
                    yield Request(url,callback=self.getdata,meta={'brand_id':response.meta['brand_id']})
    
        def getdata(self,response):
            #获取商品信息,传给pipelines保存到数据库
            item = DataItem()
            null = ''
            data =eval(re.findall(r'"merchandiseInfoList":(\[.*?\])', response.text)[0])
            for d in data:
                item['data_pp_id'] = response.meta['brand_id']
                item['data_id'] = d['mid']
                item['data_name'] = d['productName']
                item['data_url'] = d['detailUrl'].replace('\/','/')
                item['data_jiage'] = d['vipshopPrice']
                mm = Sql.select_data(d['mid'])
                if mm == 1:
                    print '该商品已经存在'
                else:
                    yield item
    

    数据展示

    品牌信息
    商品信息

    两张表可以通过pp__id来联接查询。

    代码缺陷

    上面的思路是在写完代码之后又反思得到的最佳线路,而以上代码是初步写成,还没有优化,所以还有一些缺陷,和以上思路有些区别。
    各品牌的商品总数是通过各品牌商品分类数据中得到的值累加得到,这样有个优点,就是可以提前得到商品总数,在获取商品ID时直接带上batchSize,一次完整获取所有的商品ID。而思路中是通过商品ID页返回的'total',在商品数未知的情况下,默认一次获取200各商品ID,需要发起多次请求来获取全部的商品ID(这个没有在代码中写出)

    总结

    唯品会良心,没有设置复杂的反爬机制,不限制单IP请求数和请求频率,虽然使用了javascript看不懂,不过请求参数全部在源码中,简单分析即可得到。不过应该需要补充javascript相关的知识才好。

    相关文章

      网友评论

          本文标题:抓取唯品会商品数据

          本文链接:https://www.haomeiwen.com/subject/uoblsxtx.html