scrapy分页抓取网页 6000 多款 App,看我如何搞定她们并将其洗白白~

优采云 发布时间: 2022-05-09 21:33

  scrapy分页抓取网页 6000 多款 App,看我如何搞定她们并将其洗白白~

  项目文件创建好以后,我们就可以开始写爬虫程序了。

  首先,需要在 items.py 文件中,预先定义好要爬取的字段信息名称,如下所示:

   1class KuanItem(scrapy.Item):<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 2# define the fields for your item here like:<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 3name = scrapy.Field()<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 4volume = scrapy.Field()<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 5download = scrapy.Field()<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 6follow = scrapy.Field()<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 7comment = scrapy.Field()<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 8tags = scrapy.Field()<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 9score = scrapy.Field()<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />10num_score = scrapy.Field()<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />

  这里的字段信息就是我们前面在网页中定位的 8 个字段信息,包括:name 表示 App 名称、volume 表示体积、download 表示下载数量。在这里定义好之后,我们在后续的爬取主程序中会利用到这些字段信息。

  2.3.3. 爬取主程序

  创建好 kuan 项目后,Scrapy 框架会自动生成爬取的部分代码,我们接下来就需要在 parse 方法中增加网页抓取的字段解析内容。

  1class KuanspiderSpider(scrapy.Spider):<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />2    name = 'kuan'<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />3    allowed_domains = ['www.coolapk.com']<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />4    start_urls = ['http://www.coolapk.com/']<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />5<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />6    def parse(self, response):<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />7        pass<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />

  打开主页 Dev Tools,找到每项抓取指标的节点位置,然后可以采用 CSS、Xpath、正则等方法进行提取解析,这些方法 Scrapy 都支持,可随意选择,这里我们选用 CSS 语法来定位节点,不过需要注意的是,Scrapy 的 CSS 语法和之前我们利用 pyquery 使用的 CSS 语法稍有不同,举几个例子,对比说明一下。

  

  首先,我们定位到第一个 APP 的主页 URL 节点,可以看到 URL 节点位于 class 属性为app_left_list的 div 节点下的 a 节点中,其 href 属性就是我们需要的 URL 信息,这里是相对地址,拼接后就是完整的 URL。

  接着我们进入酷安详情页,选择 App 名称并进行定位,可以看到 App 名称节点位于 class 属性为.detail_app_title的 p 节点的文本中。

  

  定位到这两个节点之后,我们就可以使用 CSS 提取字段信息了,这里对比一下常规写法和 Scrapy 中的写法:

  1# 常规写法<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />2url = item('.app_left_list>a').attr('href')<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />3name = item('.list_app_title').text()<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />4# Scrapy 写法<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />5url = item.css('::attr("href")').extract_first()<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />6name = item.css('.detail_app_title::text').extract_first()<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />

  可以看到,要获取 href 或者 text 属性,需要用 :: 表示,比如获取 text,则用 ::text。extract_first() 表示提取第一个元素,如果有多个元素,则用 extract() 。接着,我们就可以参照写出 8 个字段信息的解析代码。

  首先,我们需要在主页提取 App 的 URL 列表,然后再进入每个 App 的详情页进一步提取 8 个字段信息。

  1def parse(self, response):<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />2    contents = response.css('.app_left_list>a')<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />3    for content in contents:<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />4        url = content.css('::attr("href")').extract_first()<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />5        url = response.urljoin(url)  # 拼接相对 url 为绝对 url<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />6        yield scrapy.Request(url,callback=self.parse_url)<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />

  这里,利用 response.urljoin() 方法将提取出的相对 URL 拼接为完整的 URL,然后利用 scrapy.Request() 方法构造每个 App 详情页的请求,这里我们传递两个参数:url 和 callback,url 为详情页 URL,callback 是回调函数,它将主页 URL 请求返回的响应 response 传给专门用来解析字段内容的 parse_url() 方法,如下所示:

   1def parse_url(self,response):<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 2    item = KuanItem()<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 3    item['name'] = response.css('.detail_app_title::text').extract_first()<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 4    results = self.get_comment(response)<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 5    item['volume'] = results[0]<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 6    item['download'] = results[1]<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 7    item['follow'] = results[2]<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 8    item['comment'] = results[3]<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 9    item['tags'] = self.get_tags(response)<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />10    item['score'] = response.css('.rank_num::text').extract_first()<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />11    num_score = response.css('.apk_rank_p1::text').extract_first()<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />12    item['num_score'] = re.search('共(.*?)个评分',num_score).group(1)<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />13    yield item<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />14<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />15def get_comment(self,response):<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />16    messages = response.css('.apk_topba_message::text').extract_first()<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />17    result = re.findall(r'\s+(.*?)\s+/\s+(.*?)下载\s+/\s+(.*?)人关注\s+/\s+(.*?)个评论.*?',messages)  # \s+ 表示匹配任意空白字符一次以上<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />18    if result: # 不为空<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />19        results = list(result[0]) # 提取出list 中第一个元素<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />20        return results<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />21<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />22def get_tags(self,response):<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />23    data = response.css('.apk_left_span2')<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />24    tags = [item.css('::text').extract_first() for item in data]<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />25    return tags<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />

  这里,单独定义了 get_comment() 和 get_tags() 两个方法.

  get_comment() 方法通过正则匹配提取 volume、download、follow、comment 四个字段信息,正则匹配结果如下:

   1result = re.findall(r'\s+(.*?)\s+/\s+(.*?)下载\s+/\s+(.*?)人关注\s+/\s+(.*?)个评论.*?',messages)<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 2print(result) # 输出第一页的结果信息<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 3# 结果如下:<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 4[('21.74M', '5218万', '2.4万', '5.4万')]<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 5[('75.53M', '2768万', '2.3万', '3.0万')]<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 6[('46.21M', '1686万', '2.3万', '3.4万')]<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 7[('54.77M', '1603万', '3.8万', '4.9万')]<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 8[('3.32M', '1530万', '1.5万', '3343')]<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 9[('75.07M', '1127万', '1.6万', '2.2万')]<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />10[('92.70M', '1108万', '9167', '1.3万')]<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />11[('68.94M', '1072万', '5718', '9869')]<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />12[('61.45M', '935万', '1.1万', '1.6万')]<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />13[('23.96M', '925万', '4157', '1956')]<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />

  然后利用 result[0]、result[1] 等分别提取出四项信息,以 volume 为例,输出第一页的提取结果:

   1item['volume'] = results[0]<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 2print(item['volume'])<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 321.74M<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 475.53M<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 546.21M<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 654.77M<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 73.32M<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 875.07M<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 992.70M<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />1068.94M<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />1161.45M<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />1223.96M<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />

  这样一来,第一页 10 款 App 的所有字段信息都被成功提取出来,然后返回到 yied item *敏*感*词*中,我们输出一下它的内容:

  1[<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />2{'name': '酷安', 'volume': '21.74M', 'download': '5218万', 'follow': '2.4万', 'comment': '5.4万', 'tags': "['酷市场', '酷安', '市场', 'coolapk', '装机必备']", 'score': '4.4', 'num_score': '1.4万'}, <br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />3{'name': '微信', 'volume': '75.53M', 'download': '2768万', 'follow': '2.3万', 'comment': '3.0万', 'tags': "['微信', 'qq', '腾讯', 'tencent', '即时聊天', '装机必备']",'score': '2.3', 'num_score': '1.1万'},<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />4...<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />5]<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />

  2.3.4. 分页爬取

  以上,我们爬取了第一页内容,接下去需要遍历爬取全部 610 页的内容,这里有两种思路:

  这里,我们分别写出两种方法的解析代码,第一种方法很简单,直接接着 parse 方法继续添加以下几行代码即可:

  1def parse(self, response):<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />2    contents = response.css('.app_left_list>a')<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />3    for content in contents:<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />4        ...<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />5<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />6    next_page = response.css('.pagination li:nth-child(8) a::attr(href)').extract_first()<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />7    url = response.urljoin(next_page)<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />8    yield scrapy.Request(url,callback=self.parse )<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />

  第二种方法,我们在最开头的 parse() 方法前,定义一个 start_requests() 方法,用来批量生成 610 页的 URL,然后通过 scrapy.Request() 方法中的 callback 参数,传递给下面的 parse() 方法进行解析。

  1def start_requests(self):<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />2        pages = []<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />3        for page in range(1,610):  # 一共有610页<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />4            url = 'https://www.coolapk.com/apk/?page=%s'%page<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />5            page =  scrapy.Request(url,callback=self.parse)<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />6            pages.append(page)<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />7        return pages<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />

  以上就是全部页面的爬取思路,爬取成功后,我们需要存储下来。这里,我面选择存储到 MongoDB 中,不得不说,相比 MySQL,MongoDB 要方便省事很多。

  2.3.5. 存储结果

  我们在 pipelines.py 程序中,定义数据存储方法,MongoDB 的一些参数,比如地址和数据库名称,需单独存放在 settings.py 设置文件中去,然后在 pipelines 程序中进行调用即可。

   1import pymongo<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 2class MongoPipeline(object):<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 3    def __init__(self,mongo_url,mongo_db):<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 4        self.mongo_url = mongo_url<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 5        self.mongo_db = mongo_db<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 6    @classmethod<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 7    def from_crawler(cls,crawler):<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 8        return cls(<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 9            mongo_url = crawler.settings.get('MONGO_URL'),<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />10            mongo_db = crawler.settings.get('MONGO_DB')<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />11        )<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />12    def open_spider(self,spider):<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />13        self.client = pymongo.MongoClient(self.mongo_url)<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />14        self.db = self.client[self.mongo_db]<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />15    def process_item(self,item,spider):<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />16        name = item.__class__.__name__<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />17        self.db[name].insert(dict(item))<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />18        return item<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />19    def close_spider(self,spider):<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />20        self.client.close()<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />

  首先,我们定义一个 MongoPipeline()存储类,里面定义了几个方法,简单进行一下说明:

  from crawler() 是一个类方法,用 @class method 标识,这个方法的作用主要是用来获取我们在 settings.py 中设置的这几项参数:

  1MONGO_URL = 'localhost'<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />2MONGO_DB = 'KuAn'<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />3ITEM_PIPELINES = {<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />4   'kuan.pipelines.MongoPipeline': 300,<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />5}<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />

  open_spider() 方法主要进行一些初始化操作 ,在 Spider 开启时,这个方法就会被调用 。

  process_item() 方法是最重要的方法,实现插入数据到 MongoDB 中。

  

  完成上述代码以后,输入下面一行命令就可以开始整个爬虫的抓取和存储过程了,单机跑的话,6000 个网页需要不少时间才能完成,保持耐心。

  1scrapy crawl kuan<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />

  这里,还有两点补充:

  第一,为了减轻网站压力,我们最好在每个请求之间设置几秒延时,可以在 KuanSpider() 方法开头出,加入以下几行代码:

  1custom_settings = {<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />2        "DOWNLOAD_DELAY": 3, # 延迟3s,默认是0,即不延迟<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />3        "CONCURRENT_REQUESTS_PER_DOMAIN": 8 # 每秒默认并发8次,可适当降低<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />4    }<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />

  第二,为了更好监控爬虫程序运行,有必要设置输出日志文件,可以通过 Python 自带的 logging 包实现:

  1import logging<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />2<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />3logging.basicConfig(filename='kuan.log',filemode='w',level=logging.WARNING,format='%(asctime)s %(message)s',datefmt='%Y/%m/%d %I:%M:%S %p')<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />4logging.warning("warn message")<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />5logging.error("error message")<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />

  这里的 level 参数表示警告级别,严重程度从低到高分别是:DEBUG < INFO < WARNING < ERROR < CRITICAL,如果想日志文件不要记录太多内容,可以设置高一点的级别,这里设置为 WARNING,意味着只有 WARNING 级别以上的信息才会输出到日志中去。

  添加 datefmt 参数是为了在每条日志前面加具体的时间,这点很有用处。

  

  以上,我们就完成了整个数据的抓取,有了数据我们就可以着手进行分析,不过这之前还需简单地对数据做一下清洗和处理。

  3. 数据清洗处理

  首先,我们从 MongoDB 中读取数据并转化为 DataFrame,然后查看一下数据的基本情况。

   1def parse_kuan():<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 2    client = pymongo.MongoClient(host='localhost', port=27017)<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 3    db = client['KuAn']<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 4    collection = db['KuAnItem']<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 5    # 将数据库数据转为DataFrame<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 6    data = pd.DataFrame(list(collection.find()))<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 7    print(data.head())<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 8    print(df.shape)<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 9    print(df.info())<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />10    print(df.describe())<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />

  

  从 data.head() 输出的前 5 行数据中可以看到,除了 score 列是 float 格式以外,其他列都是 object 文本类型。

  comment、download、follow、num_score 这 5 列数据中部分行带有「万」字后缀,需要将字符去掉再转换为数值型;volume 体积列,则分别带有「M」和「K」后缀,为了统一大小,则需将「K」除以 1024,转换为 「M」体积。

  整个数据一共有 6086 行 x 8 列,每列均没有缺失值。

  df.describe() 方法对 score 列做了基本统计,可以看到,所有 App 的平均得分是 3.9 分(5 分制),最低得分 1.6 分,最高得分 4.8 分。

  下面,我们将以上几列文本型数据转换为数值型数据,代码实现如下:

   1def data_processing(df):<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 2#处理'comment','download','follow','num_score','volume' 5列数据,将单位万转换为单位1,再转换为数值型<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 3    str = '_ori'<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 4    cols = ['comment','download','follow','num_score','volume']<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 5    for col in cols:<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 6        colori = col+str<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 7        df[colori] = df[col] # 复制保留原始列<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 8        if not (col == 'volume'):<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 9            df[col] = clean_symbol(df,col)# 处理原始列生成新列<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />10        else:<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />11            df[col] = clean_symbol2(df,col)# 处理原始列生成新列<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />12<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />13    # 将download单独转换为万单位<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />14    df['download'] = df['download'].apply(lambda x:x/10000)<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />15    # 批量转为数值型<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />16    df = df.apply(pd.to_numeric,errors='ignore')<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />17<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />18def clean_symbol(df,col):<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />19    # 将字符“万”替换为空<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />20    con = df[col].str.contains('万$')<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />21    df.loc[con,col] = pd.to_numeric(df.loc[con,col].str.replace('万','')) * 10000<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />22    df[col] = pd.to_numeric(df[col])<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />23    return df[col]<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />24<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />25def clean_symbol2(df,col):<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />26    # 字符M替换为空<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />27    df[col] = df[col].str.replace('M$','')<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />28    # 体积为K的除以 1024 转换为M<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />29    con = df[col].str.contains('K$')<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />30    df.loc[con,col] = pd.to_numeric(df.loc[con,col].str.replace('K$',''))/1024<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />31    df[col] = pd.to_numeric(df[col])<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />32    return df[col]<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />

  以上,就完成了几列文本型数据的转换,我们再来查看一下基本情况:

  

  download 列为 App 下载数量,下载量最多的 App 有 5190 万次,最少的为 0 (很少很少),平均下载次数为 14 万次;从中可以看出以下几点信息:

  以上,就完成了基本的数据清洗处理过程,下一期将对这6000多款App进行探索性分析,看看有多少佳软神器你没有使用过哦。

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线