酷安
最近app用荒了,想要看看有些哪些好玩的app可以安装下来玩玩,酷安就是安卓应用的聚集地网页内容
模块
- import requests--网页请求
- import time--延时
- import traceback--输出异常
- from multiprocessing import Pool--多进程
- from lxml import etree--解析数据
- from pymysql import connect--数据库连接
- from pymysql import cursors--数据库游标
- from PIL import Image--图像读取
- import wordcloud as wc--词云
- import numpy as np--图像转换
- import jieba--分词
- from pyecharts import Line--折线图
- from pyecharts import Overlap--图形整合
- from pyecharts import Bar--柱状图
网页结构分析
来到酷安应用的网页,一如既往的打开chrome的开发者工具,对网页请求进行分析,右键->检查->netword->xhr->刷新,看看数据是不是异步加载的,结果发现并不是,刷新并不能看到什么东西。
既然不是异步加载的情况,转而研究网页的源代码,看网页源代码里面有没有我们想要的数据,有没有什么字体加密的过程等,右键->查看网页源代码->搜索网易云音乐,就出现结果,而且数据并没有出现乱码,很可能请求该网页,后台直接发过来了,减少了我们很多的麻烦,直接进行网页抓取,然后提取数据,清洗数据即可。
xpath提取
这里主要是利用xpath来提取数据,通过快速定位,然后对数据进行提取,或者目标元素的文本,超链接等,很多时候我们自己写的xpath表达式比较容易出错,提取不了想要的数据,但是我们可以借助浏览器给我们提供的xpath表达式的提取,可以不用自己写了,右键->检查->选择浏览器里面的小图标(选择元素)->出现相应的网页代码->右键->copy->copy xpath.
数据提取
利用浏览器获取相应的xpath表达式之后,我们就可以进行提取数据、清洗数据和存储数据了,下一步还得进行总页面的提取,方便我们去构造url网页获取
def get_html(url):
time.sleep(2)
# 请求头设置
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
}
try:
resp=requests.get(url,headers=headers)
if resp.status_code==200:
return resp.text
except Exception:
print("获取网页失败,",Exception)
return None
总页数的提取
由于要抓取酷安提供的全部app的信息,所以需要提取app总的页面(需要提取列表的a标签的href即可),方便进行url的构建,以及多进程的实现。
# 获取最后一页
def get_last_page(content):
if content:
html = etree.HTML(content)
href = html.xpath('/html/body/div/div[2]/div[2]/div[3]/div[2]/ul/li[9]/a/@href')
page = str(href).split("=")[1].split("'")
return page[0]
else:
return None
app的具体信息
主要提取酷安应用页面的app列表信息和点进每个app里面的分类标签和评分个数
def parse_html(detail):
if(detail==None):
print("不可用--detail")
return None
html = etree.HTML(detail)
root = html.xpath('/html/body/div/div[2]/div[2]/div[3]/a')
for children in root:
# app名字
name = children.xpath('./div/div/div/p[1]/text()')[0]
grade_size = children.xpath('./div/div/div/p[2]/text()')
temp = grade_size[0].split(' ')
# app评分
grade = temp[0][:-1]
# 下载次数
downlaod_count = children.xpath('./div/div/div/p[2]/span/text()')[0].split(" ")[0].split("次")[0]
# 数据清洗‘万’转化为数字
if '万' in downlaod_count:
downlaod_count = float(downlaod_count.split('万')[0])*10000
yield{
'name':name,
'grade':grade,
'download_count':downlaod_count,
'href':children.xpath('./@href')
}
获取app的分类标签和评分人数
在分类标签和评分人数的xpath提取的时候,会发现xpath的表达式会出现不一样的,这是有些app详细的页面上面存在“酷安点评”这部分,而有的不存在这部分,而且还有就是,有些app详细页面上不存在“新版特性”这部分,所以造就我们在提取xpath表达式的时候,要分类提取出来。
- 没有"酷安点评的xpath"
- 没有"新版特性"的xpath
- 没有"分类标签"的xpath(不存在的情况直接为空好了)
def get_detail(app_detail):
html = etree.HTML(app_detail)
comment = html.xpath('/html/body/div/div[2]/div[2]/div[2]/div/div[4]/div/div[1]/p/text()')
# 对获取的节点进行判断
if len(comment) == 0:
comment= html.xpath('/html/body/div/div[2]/div[2]/div[2]/div/div[5]/div/div[1]/p/text()')
if len(comment) == 0:
comment= html.xpath('/html/body/div/div[2]/div[2]/div[2]/div/div[3]/div/div[1]/p/text()')
# 对获取的节点进行判断
tags = html.xpath('/html/body/div/div[2]/div[2]/div[2]/div/div[5]/p[2]/a/@href')
if len(tags) == 0:
tags = html.xpath('/html/body/div/div[2]/div[2]/div[2]/div/div[6]/p[2]/a/@href')
if len(tags) == 0:
tags = html.xpath('/html/body/div/div[2]/div[2]/div[2]/div/div[4]/p[2]/a/@href')
comment_count = comment[0].split('个')[0][1:]
if '万' in comment_count:
comment_count = float(comment_count.split('万')[0])*10000
return comment_count,"".join(tag.split('/')[-1]+"/" for tag in tags)
数据存储
把抓取的数据存储到MySQL数据库中去,再从数据库中提取相关的数据进行分析数据库和表的创建
def create_database_table():
data_base = 'app_db'
base_sql = "create database if not exists "+data_base
table_sql = '''
CREATE TABLE IF NOT EXISTS `app_table` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) COLLATE utf8_bin NOT NULL,
`grade` DECIMAL(2,1) NOT NULL,
`grade_count` VARCHAR(50) NOT NULL,
`download_count` VARCHAR(50) NOT NULL,
`tag` VARCHAR(400) COLLATE utf8_bin NOT NULL,
`href` VARCHAR(300) COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
AUTO_INCREMENT=1
'''
# 连接数据库
con = connect(host="localhost",user="root",password="root",charset="utf8mb4")
try:
# 创建游标
cur = con.cursor()
# 创建数据库app_db
cur.execute(base_sql)
# 选择数据库
con.select_db(data_base)
# 创建表
cur.execute(table_sql)
# 提交事务
con.commit()
print("创建数据库和表成功啦!")
except Exception:
print("创建数据库或者表出错啦!",traceback.print_exc())
# 回滚事务
con.rollback()
finally:
cur.close()
con.close()
数据的插入
def insert_to_table(msg_dict):
con = connect(host="localhost",user="root",password="root",db="app_db",charset="utf8mb4")
# python默认插入的字符串类型,字段都是字符串类型
sql = "insert into `app_table` (`name`,`grade`,`grade_count`,`download_count`,`tag`,`href`) values(%s,%s,%s,%s,%s,%s)"
try:
# 创建游标
cur = con.cursor()
# 执行sql语句
cur.execute(sql,(msg_dict['name'],msg_dict['grade'],msg_dict['grade_count'],msg_dict['download_count'],msg_dict['tag'],msg_dict['href']))
# 提交事务
con.commit()
print('插入成功,%s'%msg_dict['name'])
except Exception:
print("插入数据失败,",traceback.print_exc())
finally:
cur.close()
con.close()
抓取数据的程序入口
def main(offset):
content=get_html('https://www.coolapk.com/apk?p='+str(offset))
details = parse_html(content)
for detail in details:
# `name`,`grade`,`grade_count`,`tag`,`download_count`,`href`
content = get_html('https://www.coolapk.com'+detail['href'][0])
grade_tage = get_detail(content)
app_msg = {
"name":detail['name'],
"grade":detail['grade'],
"grade_count":grade_tage[0],
"download_count":detail['download_count'],
"tag":grade_tage[1],
"href":detail['href'][0]
}
# print(app_msg)
insert_to_table(app_msg)
print("完成时间,%s"%time.strftime('%Y-%m-%d-%H-%M-%S',time.localtime(time.time())))
if __name__ == '__main__':
create_database_table()
content = get_html("https://www.coolapk.com/apk/")
last_page = get_last_page(content)
# 创建进程池
pool=Pool()
offset=([x for x in range(1,int(last_page)+1)])
# 第一个参数是函数,第二个参数是一个迭代器
pool.map(main,offset)
# 关闭进程池
pool.close()
pool.join()
数据分析
利用pyecharts库进行数据的绘画,对数据进行分析。
数据查询
从数据库中获取相关的数据
def get_data(sql):
con = connect(host="localhost",user="root",password="root",db="app_db",charset="utf8mb4")
try:
cur = con.cursor()
cur.execute(sql)
result = cur.fetchall()
# print(result)
return result
except Exception:
print("连接数据库出错",traceback.print_exc())
finally:
cur.close()
con.close()
词云生成
# 词云生成
def draw_tag_wordcloud():
sql = "select tag from app_table"
datas = get_data(sql)
tags = []
if len(datas) > 0:
for data in datas:
if len(data) == 0:
continue
else:
res = data[0].split('/')
for r in res:
tags.append(r)
else:
print('没有获取到数据')
return
# 设置停用词
stop_word = ['小编推荐','装机必备']
android = np.array(Image.open('Android.png'))
res = wc.WordCloud(
font_path='C:/Windows/Fonts/SIMYOU.TTF',
background_color='white',
max_words=2000,
max_font_size=120,
mask=android,
random_state=40,
stopwords=stop_word)
# ---------------------记住拼接字符串需要加空格--------------------
words = ' '.join(tags)
words=jieba.cut(words)
words=' '.join(words)
res.generate(words)
res.to_file('app.png')
柱状图和折线的生成
从数据库中查询app下载次数的前50排名,对下载次数前50的app进行评分和下载次数的折线图和柱状图的绘画
def draw_table():
# 选择下载量前50的app数据
'''
sql语句中的(download_count+0)是因为下载次数在数据库中是字符串类型,+0进行类型的转换,这样才有可比性
'''
sql = 'SELECT NAME,grade,download_count FROM app_table ORDER BY (download_count+0) DESC LIMIT 0,50'
datas = get_data(sql)
name = []
grade = []
download = []
if len(datas) > 0:
for data in datas:
name.append(data[0])
grade.append(float(data[1]))
download.append(float(data[2]))
else:
print('没有结果输出')
return
bar = Bar()
bar.add('app',name,download, yaxis_formatter="次",yaxis_interval=200)
line = Line()
line.add('评分',name,grade,yaxis_formatter="分",yaxis_interval=1,xaxis_rotate=30)
# 使用overlap进行折线图和柱状图的混合
overlap = Overlap(width=1200, height=600)
overlap.add(bar)
overlap.add(line,yaxis_index=1, is_add_yaxis=True)
overlap.render('app_top50.html')
程序入口
def main():
draw_table()
draw_tag_wordcloud()
if __name__ == '__main__':
main()
实现效果
折线图和柱状图
词云
项目源代码:链接:https://pan.baidu.com/s/1t7LNR4HdSDOkTezSaca2ng 提取码:d6ur
总结:项目虽然简单,但其中的坑还有的,涉及多方面的使用,如MySQL、词云、折线图、柱状图等,还是得自己实现,发现以往不常出现的问题。本项目这是用于学习,不存在商业用途,如有侵权必删。