分析了 7 万款 App,全是没想到

时尚        2019-09-09   来源:小斌说三农

来源:第二大脑   链接:

https://mp.weixin.qq.com/s/mq2SLVuBCAl6lkEub4H2Mg

摘要:使用 Scrapy 爬取豌豆荚全网 70,000+ App,并进行探索性分析。

写在前面:若对数据抓取部分不感兴趣,可以直接下拉到数据分析部分。

1 分析背景

之前我们使用了 Scrapy 爬取并分析了酷安网 6000+ App,为什么这篇文章又在讲抓 App 呢?

因为我喜欢折腾 App,哈哈。当然,主要是因为下面这几点:

第一、之前抓取的网页很简单

在抓取酷安网时,我们使用 for 循环,遍历了几百页就完成了所有内容的抓取,非常简单,但现实往往不会这么 easy,有时我们要抓的内容会比较庞大,比如抓取整个网站的数据,为了增强爬虫技能,所以本文选择了「豌豆荚」这个网站。

目标是: 爬取该网站所有分类下的 App 信息并下载 App 图标,数量在 70,000 左右,比酷安升了一个数量级。

第二、再次练习使用强大的 Scrapy 框架

之前只是初步地使用了 Scrapy 进行抓取,还没有充分领会到 Scrapy 有多么牛逼,所以本文尝试深入使用 Scrapy,增加随机 UserAgent、代理 IP 和图片下载等设置。

第三、对比一下酷安和豌豆荚两个网站

相信很多人都在使用豌豆荚下载 App,我则使用酷安较多,所以也想比较一下这两个网站有什么异同点。

话不多说,下面开始抓取流程。

▌分析目标

首先,我们来了解一下要抓取的目标网页是什么样的。

可以看到该网站上的 App 分成了很多类,包括:「应用播放」、「系统工具」等,一共有 14 个大类别,每个大类下又细分了多个小类,例如,影音播放下包括:「视频」、「直播」等。

点击「视频」进入第二级子类页面,可以看到每款 App 的部分信息,包括:图标、名称、安装数量、体积、评论等。

在之前的一篇文章中(见下方链接),我们分析了这个页面:采用 AJAX 加载,GET 请求,参数很容易构造,但是具体页数不确定,最后分别使用了 For 和 While 循环抓取了所有页数的数据。

∞ Python For 和 While 循环爬取不确定页数的网页

接着,我们可以再进入第三级页面,也就是每款 App 的详情页,可以看到多了下载数、好评率、评论数这几样参数,抓取思路和第二级页面大同小异,同时为了减小网站压力,所以 App 详情页就不抓取了。

所以,这是一个分类多级页面的抓取问题,依次抓取每一个大类下的全部子类数据。

学会了这种抓取思路,很多网站我们都可以去抓,比如很多人爱爬的「豆瓣电影」也是这样的结构。

▌分析内容

数据抓取完成后,本文主要是对分类型数据的进行简单的探索性分析,包括这么几个方面:

  • 下载量最多 / 最少的 App 总排名

  • 下载量最多 / 最少的 App 分类 / 子分类排名

  • App 下载量区间分布

  • App 名称重名的有多少

  • 和酷安 App 进行对比

▌分析工具

  • Python

  • Scrapy

  • MongoDB

  • Pyecharts

  • Matplotlib

2 数据抓取

▌网站分析

我们刚才已经初步对网站进行了分析,大致思路可以分为两步,首先是提取所有子类的 URL 链接,然后分别抓取每个 URL 下的 App 信息就行了。

可以看到,子类的 URL 是由两个数字构成,前面的数字表示分类编号,后面的数字表示子分类编号,得到了这两个编号,就可以抓取该分类下的所有 App 信息,那么怎么获取这两个数值代码呢?

回到分类页面,定位查看信息,可以看到分类信息都包裹在每个 li 节点中,子分类 URL 则又在子节点 a  的 href 属性中,大分类一共有 14 个,子分类一共有 88 个

到这儿,思路就很清晰了,我们可以用 CSS 提取出全部子分类的 URL,然后分别抓取所需信息即可。

另外还需注意一点,该网站的 首页信息是静态加载的,从第 2 页开始是采用了 Ajax 动态加载,URL 不同,需要分别进行解析提取。

▌Scrapy抓取

我们要爬取两部分内容,一是 APP 的数据信息,包括前面所说的:名称、安装数量、体积、评论等,二是下载每款 App 的图标,分文件夹进行存放。

由于该网站有一定的反爬措施,所以我们需要添加随机 UA 和代理 IP,关于这两个知识点,我此前单独写了两篇文章进行铺垫,传送门:

∞ Scrapy 中设置随机 User-Agent 的方法汇总

∞ Python 爬虫的代理 IP 设置方法汇总

这里随机 UA 使用 scrapy-fake-useragent 库,一行代码就能搞定,代理 IP 直接上阿布云付费代理,几块钱搞定简单省事。

下面,就直接上代码了。

items.py

 1import scrapy
2
3class WandoujiaItem(scrapy.Item):
4    cate_name = scrapy.Field() #分类名
5    child_cate_name = scrapy.Field() #分类编号
6    app_name = scrapy.Field()   # 子分类名
7    install = scrapy.Field()    # 子分类编号
8    volume = scrapy.Field()     # 体积
9    comment = scrapy.Field()    # 评论
10    icon_url = scrapy.Field()   # 图标url

middles.py

中间件主要用于设置代理 IP。

 1import base64
2proxyServer = "http://http-dyn.abuyun.com:9020"
3proxyUser = "你的信息"
4proxyPass = "你的信息"
5
6proxyAuth = "Basic " + base64.urlsafe_b64encode(bytes((proxyUser + ":" + proxyPass), "ascii")).decode("utf8")
7class AbuyunProxyMiddleware(object):
8    def process_request(self, request, spider):
9        request.meta["proxy"] = proxyServer
10        request.headers["Proxy-Authorization"] = proxyAuth
11        logging.debug('Using Proxy:%s'%proxyServer)

pipelines.py

该文件用于存储数据到 MongoDB 和下载图标到分类文件夹中。

存储到 MongoDB:

 1MongoDB 存储
2class MongoPipeline(object):
3    def __init__(self,mongo_url,mongo_db):
4        self.mongo_url = mongo_url
5        self.mongo_db = mongo_db
6
7    @classmethod
8    def from_crawler(cls,crawler):
9        return cls(
10            mongo_url = crawler.settings.get('MONGO_URL'),
11            mongo_db = crawler.settings.get('MONGO_DB')
12        )
13
14    def open_spider(self,spider):
15        self.client = pymongo.MongoClient(self.mongo_url)
16        self.db = self.client[self.mongo_db]
17
18    def process_item(self,item,spider):
19        name = item.__class__.__name__
20        # self.db[name].insert(dict(item))
21        self.db[name].update_one(item, {'$set': item}, upsert=True)
22        return item
23
24    def close_spider(self,spider):
25        self.client.close()

按文件夹下载图标:

 1# 分文件夹下载
2class ImagedownloadPipeline(ImagesPipeline):
3    def get_media_requests(self,item,info):
4        if item['icon_url']:
5            yield scrapy.Request(item['icon_url'],meta={'item':item})
6
7    def file_path(self, request, response=None, info=None):
8        name = request.meta['item']['app_name']
9        cate_name = request.meta['item']['cate_name']
10        child_cate_name = request.meta['item']['child_cate_name']
11
12        path1 = r'/wandoujia/%s/%s' %(cate_name,child_cate_name)
13        path = r'{}\{}.{}'.format(path1, name, 'jpg')
14        return path
15
16    def item_completed(self,results,item,info):
17        image_path = [x['path'for ok,x in results if ok]
18        if not image_path:
19            raise DropItem('Item contains no images')
20        return item

settings.py

 1BOT_NAME = 'wandoujia'
2SPIDER_MODULES = ['wandoujia.spiders']
3NEWSPIDER_MODULE = 'wandoujia.spiders'
4
5MONGO_URL = 'localhost'
6MONGO_DB = 'wandoujia'
7
8# 是否遵循机器人规则
9ROBOTSTXT_OBEY = False
10# 下载设置延迟 由于买的阿布云一秒只能请求5次,所以每个请求设置了 0.2s延迟
11DOWNLOAD_DELAY = 0.2
12
13DOWNLOADER_MIDDLEWARES = {
14    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware'None,
15    'scrapy_fake_useragent.middleware.RandomUserAgentMiddleware'100# 随机UA
16    'wandoujia.middlewares.AbuyunProxyMiddleware'200 # 阿布云代理
17    )
18
19ITEM_PIPELINES = {
20   'wandoujia.pipelines.MongoPipeline'300,
21   'wandoujia.pipelines.ImagedownloadPipeline'400,
22}
23
24# URL不去重
25DUPEFILTER_CLASS = 'scrapy.dupefilters.BaseDupeFilter'

wandou.py

主程序这里列出关键的部分:

 1def __init__(self):
2        self.cate_url = 'https://www.wandoujia.com/category/app'
3        # 子分类首页url
4        self.url = 'https://www.wandoujia.com/category/'
5        # 子分类 ajax请求页url
6        self.ajax_url = 'https://www.wandoujia.com/wdjweb/api/category/more?'
7        # 实例化分类标签
8        self.wandou_category = Get_category()
9def start_requests(self):
10        yield scrapy.Request(self.cate_url,callback=self.get_category)
11
12def get_category(self,response):    
13        cate_content = self.wandou_category.parse_category(response)
14        # ...

这里,首先定义几个 URL,包括:分类页面、子分类首页、子分类 AJAX 页,也就是第 2 页开始的 URL,然后又定义了一个类 Get_category() 专门用于提取全部的子分类 URL,稍后我们将展开该类的代码。

程序从 start_requests 开始运行,解析首页获得响应,调用 get_category() 方法,然后使用 Get_category() 类中的 parse_category() 方法提取出所有 URL,具体代码如下:

 1class Get_category():
2    def parse_category(self, response):
3        category = response.css('.parent-cate')
4        data = [{
5            'cate_name': item.css('.cate-link::text').extract_first(),
6            'cate_code': self.get_category_code(item),
7  &

相关阅读