一起看一下主流应用使用了哪些三方库

背景

我们在进行Android开发时往往会面临技术选型的问题, 面对如此多的开源框架如何进行选择、选择的标准是什么,这是一个值得思考的问题. 为此我在后台爬取了6000多个主流应用,逐个反编译统计它们使用了哪些开源框架,因此做了一个款应用

基本思路

首先我们要有Apk才可以进行分析,我选择爬取酷安的应用数据(感觉酷安比较好爬一点),将每个应用的apk下载到本地,通过apktool进行反编译,查看反编译后的结果。虽然大部分应用都会进行混淆,但是涉及三方库的包一般是不会进行混淆的,所以我们只需要统计出代码的目录结构基本就可以推敲出该应用使用了哪些三方库。

使用pyspider爬取酷安数据

一般提到爬虫我们首先选择Python,在GitHub上Python中star最多的爬虫框架就是pyspider了,这是由国人开发的一个爬虫框架,用起来还算方便。只是在windows上安装不易,建议还是在linux安装,具体安装方式这里就不多介绍了,网上有很多教程。安装之后的界面是这样的

直接点击右边的Create新建任务就可以了

我们只需要在右边写代码,保存之后在左边点击run就可以查看执行结果 我们先来看一下要爬取的对象

一共有653页,每页10个,一共6530个应用。爬取的就基本思路就是首先根据Url:https://www.coolapk.com/apk?p=1生成爬取的任务。在pyspider中通过self.crawl创建爬取任务,该方法有两个参数,第一个为要爬去的url,第二个为回调函数。如爬取每页数据的代码为

    @config(age=10 * 24 * 60 * 60)def index_page(self, response):url = 'https://www.coolapk.com/apk?p='# 从第1页到653页生成任务for i in range(1, 654):self.crawl(url + str(i), callback=self.list_page)复制代码

这样爬虫会自动访问每页的数据,在访问成功之后回调list_page方法,在list_page方法中会提取该页中每个App的详情页对应的url,然后继续生成抓取任务

根据酷安App列表页面的dom结构可以看到我们首先要找到classapp_left_listdiv,该diva标签的href值即为App详情页对应的url,具体代码如下

    @config(priority=2)def list_page(self, response):# 从每一页中打开App详情页面for each in response.doc('div[class="app_left_list"]').children('a').items():self.crawl(each.attr.href, callback=self.detail_page)复制代码

最后就是在App详情页面提取我们需要的App的信息,然后将提取的信息保存到数据库中,并根据提取到的apk链接下载该apk,实际测试中发现酷安在进行apk文件下载时是有session校验的,所以下载时需要携带上session信息,由于下载过程比较耗时,pyspider不支持这种耗时操作,所以我们需要单独开启线程下载。

对于稍微具备一点前端知识的同学,然后查阅一下pyquery的用法,基本上提取我们需要的信息就没什么大问题。

完整的爬取代码如下

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Created on 2017-12-13 20:17:00
# Project: kuanfrom pyspider.libs.base_handler import *
import requests
import _thread
import jsonclass Handler(BaseHandler):crawl_config = {}# bomb应用配置信息Bomb_Application_Id = 'bomb对应的Application Id'Bomb_Rest_Api_Key = 'bomb对应的Rest Api Key'headers = {'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)','Referer': 'https://www.coolapk.com/apk/com.evernote'}    @every(minutes=24 * 60)def on_start(self):self.crawl('https://www.coolapk.com/apk', callback=self.index_page)    @config(age=10 * 24 * 60 * 60)def index_page(self, response):url = 'https://www.coolapk.com/apk?p='# 从第1页到653页生成任务for i in range(1, 654):self.crawl(url + str(i), callback=self.list_page)    @config(priority=2)def list_page(self, response):# 从每一页中打开App详情页面for each in response.doc('div[class="app_left_list"]').children('a').items():self.crawl(each.attr.href, callback=self.detail_page)    @config(priority=2)def detail_page(self, response):url = response.urlpackageName = url[28:len(url)]imgUrl = list(response.doc('div[class="apk_topbar"]').items())[0].children('img').attr("src")scriptLine = list(response.doc('script').items())[2].text().split('\n')[2]apkUrl = scriptLine[36:len(scriptLine) - 2]appName = response.doc('p[class="detail_app_title"]').text().split(" ")[0]desc = list(response.doc('div[class="apk_left_title_info"]').items())[0].html()left_info_list = list(response.doc('p[class="apk_left_title_info"]').items())detail = left_info_list[len(left_info_list) - 1].html()# 获取下载量apk_topba_message = response.doc('p[class="apk_topba_message"]').text()download_count = self.get_download_count(apk_topba_message.split('/')[1])cookie = 'SESSID=' + response.cookies['SESSID']_thread.start_new_thread(self.downloadFile, (apkUrl, packageName, cookie,))appInfo = {"url": url,"packageName": packageName,"name": appName,"detail": detail,"imgUrl": imgUrl,'downloadCount': download_count,"description": desc}self.saveAppInfo(appInfo)return appInfodef get_download_count(self, download_str):download_str = download_str.strip()if download_str.endswith('万下载'):return float(download_str.split('万下载')[0]) * 10000elif download_str.endswith('次下载'):return float(download_str.split('次下载')[0])elif download_str.endswith('下载'):return float(download_str.split('下载')[0])else:return 0def downloadFile(self, apkUrl, packageName, cookie):headers = self.headersheaders['cookie'] = cookier = requests.get(apkUrl, headers=self.headers,allow_redirects=True, verify=False)# 保存下载的文件with open("/root/apk/" + packageName + ".apk", "wb") as f:f.write(r.content)# Bomb的唯一键不靠谱,每次保存之前先查询是否存在,然后再进行更新或者保存def saveAppInfo(self, data):headers = {'X-Bmob-Application-Id': self.Bomb_Application_Id,'X-Bmob-REST-API-Key': self.Bomb_Rest_Api_Key, 'Content-Type': 'application/json'}url = 'https://api.bmob.cn/1/classes/app_info'exitInfo = self.queryAppByPackageName(data['packageName'])if(len(exitInfo['results']) > 0):url = url + '/' + exitInfo['results'][0]['objectId']res = requests.put(url, headers=headers,data=json.dumps(data), verify=False)else:res = requests.post(url, headers=headers,data=json.dumps(data), verify=False)def queryAppByPackageName(self, packageName):headers = {'X-Bmob-Application-Id': self.Bomb_Application_Id,'X-Bmob-REST-API-Key': self.Bomb_Rest_Api_Key, 'Content-Type': 'application/json'}url = 'https://api.bmob.cn/1/cloudQuery'bql = 'select * from app_info where packageName=?'values = '[\'' + packageName + '\']'data = {'bql': bql, 'values': values}url = url + '?bql=' + bql + '&values=' + valuesres = requests.get(url, headers=headers, verify=False)return json.loads(res.text)复制代码

使用Apktool反编译apk文件

apk文件下载完成之后我们就可以使用apktool进行反编译了。基本命令是java -jar apktool_2.3.0.jar d xxx.apk -o destDir -f。这里我使用的apktool版本为2.3.0。

具体做法是依次反编译每个apk文件,一般情况下apk反编译之后的文件目录大致包含以下内容

第一个文件就不解释了,做Android开发的同学都知道。值得注意的是Apk的版本信息没有在AndroidManifest文件中,而是在apktool.yml文件中,这个文件里面包含很多apk有价值的信息。另一个值得我们关注的是smali文件夹,如果apk进行了分包可能还会出现smali_class2、smali_class3之类的文件夹。我们分析该app引用了哪些三方库主要看smali下的文件目录结构是什么样的。虽然这种方式并不完全准确,但是也能涵盖绝大部分三方库。

具体代码如下

#!/usr/bin/env python
# -*- coding:utf-8 -*-from __future__ import print_functionimport requests
import json
import yaml
import os
import subprocess
import sys
import zipfile
from xml.dom import minidom
import threadpool
import shutilapktool = "apktool_2.3.0.jar"
headers = {'X-Bmob-Application-Id': 'bomb对应的Application Id','X-Bmob-REST-API-Key': 'bomb对应的Rest Api Key', 'Content-Type': 'application/json'}def sh(command):print(command)p = subprocess.Popen(command, shell=True,stdout=subprocess.PIPE, stderr=subprocess.STDOUT)print(p.stdout.read())def decompileApk(f):# fix windows pathif ":\\" in f and not ":\\\\" in f:f = f.replace("\\", "\\\\")dexes = []jars = []if f.endswith(".apk"):package_name = f[0:len(f) - 4]tempDir = os.path.splitext(f)[0]sh("java -jar %s d  %s -o %s -f" % (apktool, f, tempDir))if os.path.isdir(os.path.join(tempDir, 'smali_classes2')):sh("cp -rf smali_classes2/* smali/")jarDir = os.path.join(tempDir, 'smali')if os.path.exists(jarDir):packageList = []getPackageName(jarDir, jarDir, packageList)packageList = cleanPackageName(packageList)savePackageList(packageList, package_name)sh('sed -i 1d %s' % (tempDir + '/apktool.yml'))versionInfo = getVersionInfo(tempDir + '/apktool.yml')saveApkInfo(package_name,versionInfo['versionCode'], versionInfo['versionName'])shutil.rmtree(tempDir)print("Done")def mapFunc(package):return package.replace('/', '.')def cleanPackageName(packageList):return list(map(mapFunc, packageList))def getVersionInfo(file):f = open(file)y = yaml.load(f)return y['versionInfo']def getPackageName(root, dir, packageList):files = [f for f in os.listdir(dir) if os.path.isfile(os.path.join(dir, f))]if len(files) > 0 and root != dir:if len(dir.split(root + '/')) > 1:packageList.append(dir.split(root + '/')[1])else:print('error root:%s dir:%s' % (root, dir))elif len([f for f in os.listdir(dir) if len(f) > 1]) == 0:if len(dir.split(root + '/')) > 1:packageList.append(dir.split(root + '/')[1])else:print('error root:%s dir:%s' % (root, dir))else:for file in [f for f in os.listdir(dir) if os.path.isdir(os.path.join(dir, f))]:if len(file) > 1:getPackageName(root, os.path.join(dir, file), packageList)def packageToRequest(package):return {'method': 'POST', 'path': '/1/classes/lib_info', 'body': {'packageName': package}}def savePackageList(packageList, apk_id):url = 'https://api.bmob.cn/1/batch'i = 0while i < len(packageList):subList = packageList[i:i + 50]params = {}params['requests'] = list(map(packageToRequest, subList))res = saveDataToBomb(url, params)saveLibApkRelation(subList, apk_id)i += 50def lib_id_to_request(lib_id):return {'method': 'POST', 'path': '/1/classes/r_apk_lib', 'body': {'libPackageName': lib_id}}def saveLibApkRelation(lib_id_list, apk_id):url = 'https://api.bmob.cn/1/batch'params = {}params['requests'] = list(map(lib_id_to_request, lib_id_list))for req in params['requests']:req['body']['apkPackageName'] = apk_idres = saveDataToBomb(url, params)def saveApkInfo(packageName, versionCode, versionName):data = {"packageName": packageName,"versionCode": versionCode, "versionName": versionName}url = 'https://api.bmob.cn/1/classes/apk_info'oldInfo = json.loads(queryDataFromBomb(url, data))if len(oldInfo['results']) > 0:print('%s is exits' % {str(data)})else:saveDataToBomb(url, data)def saveDataToBomb(url, data):res = requests.post(url, headers=headers,data=json.dumps(data), verify=False)return resdef queryDataFromBomb(url, data):print('%s ?where=%s' %(url, json.dumps(data)))res = requests.get('%s?where=%s' %(url, json.dumps(data)),  headers=headers, verify=False)return res.textif __name__ == "__main__":f = sys.argv[1]if os.path.isdir(f):pool = threadpool.ThreadPool(1)name_list = os.listdir(f)# 单线程运行for name in name_list:decompileApk(name)# 多线程运行# myrequets = threadpool.makeRequests(decompileApk, name_list)# [pool.putRequest(req) for req in myrequets]# pool.wait()print('All Finished')else:print('参数必须为一个目录')复制代码

从实际分析结果来看,目前的分析算法还有很多问题,统计出来的包名和我们实际使用的三方库不能完全匹配,有时会把子包名统计进去。所以只能靠大家经验还判断每个包名对应的是哪个三方库了。

App展示统计结果

最后将上面抓取和分析的结果以App的形式展示出来,相比上两步而言这个是最简单的了。目前主要提供两个维度的展示,一是按照酷安上的下载量展示App信息,在App详情中展示该app下统计出来的包信息;另一个维度是按照库被引用的次数展示,详情页面中展示哪些应用中包含这个库。功能比较简单所以就不多解释了,直接放代码地址:github.com/dumingxin/A…,欢迎大家star、提issue,或者有更好的想法一起来实现。

App目前已经发布在酷安市场,下载地址为:www.coolapk.com/apk/172597

二维码:

总结

从开始着手准备,到最终完成第一个版本的功能大概两周时间,由于没有正经学习过python,所以python相关代码写的可能不太规范,仅供大家参考。

目前实际下载下来的apk文件只有5000+,还有1000多没有下载下来。apk反编译还在进行,目前已经分析了2000+,所以统计结果可能还会不断变化

感谢

https://www.coolapk.com/ 感谢酷安提供的数据(手动滑稽)

https://github.com/binux/pyspider 感谢pyspider让我一个新手也可以爬数据

https://github.com/tp7309/AndroidOneKeyDecompiler 感谢作者提供python反编译apk的思路

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/453166.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

黑客——技术的凝聚者???

写在前面&#xff1a; 不知道在中国&#xff0c;有多少人能真正的理解什么是hack,当hack被加上商业的成分就失去了本来的色彩&#xff0c;如今多如繁星的大小类黑客站点如雨后春笋般出现&#xff0c;不过是否有技术&#xff0c;统统叫hack&#xff0c;拿着别人的工具去砍掉几个…

SOA架构设计经验分享—架构、职责、数据一致性

1.背景介绍2.SOA的架构层次 2.1.应用服务&#xff08;原子服务&#xff09;2.2.组合服务2.3.业务服务&#xff08;编排服务&#xff09;3.SOA化的重构 3.1.保留服务空间&#xff0c;为了将来服务的组合4.运用DDDGRASP进行分析和设计&#xff08;防止主观的判断导致错误的假设&a…

计算机设备没有音频,电脑没有音频设备怎么办

有些朋友的的电脑没有声音&#xff0c;任务栏右下角也没有小喇叭声音图标&#xff0c;进入到控制面板里面的声音选项里面去设置&#xff0c;在“音量”项目中显示“没有音频设备”&#xff0c;很多朋友遇到这种情况不知道应该怎么解决&#xff0c;今天笔者就电脑没有音频设备怎…

HDLC协议概述

一 HDLC概述 1.1 HDLC的发展历史 高级数据链路控制&#xff08;High-Level Data Link Control或简称HDLC&#xff09;&#xff0c;是一个在同步网上传输数据、面向比特的数据链路层协议&#xff0c;它是由国际标准化组织(ISO)根据IBM公司的SDLC(SynchronousData Link Control…

全志科技公司A83T Qt 支持双屏显示

目前A83T支持单屏显示&#xff0c;首屏为LCD或者首屏为hdmi&#xff0c;都使用无论使用SCREEN0还是SCREEN1都是使用FB0作为framebuffer&#xff0c;在android下可以实现LCD和HDMI同样屏幕显示&#xff0c;而我们需要LCD和HDMI分别显示。FrameBuffer采用的是linux下的framebuffe…

明日之后怎么跳过实名认证_明日之后宝箱达人活动怎么玩 明日之后宝箱达人可以开箱多少次...

《明日之后》宝箱达人是今天游戏中更新的活动&#xff0c;玩家们在部分的野外地图中可以找到宝箱。很多玩家都想知道这个宝箱获得的奖励是什么。接下来就让小编给大家带来明日之后宝箱达人活动奖励介绍&#xff0c;一起来看看吧。明日之后宝箱达人活动介绍 一、活动时间2020年9…

JVM内存模型与垃圾回收GC

Java开发有个很基础的问题&#xff0c;虽然我们平时接触的不多&#xff0c;但是了解它却成为Java开发的必备基础——这就是JVM。在C中我们需要手动申请内存然后释放内存&#xff0c;否则就会出现对象已经不再使用内存却仍被占用的情况。在Java中JVM内置了垃圾回收的机制&#x…

Windows批处理命令学习

Windows批处理命令学习一 Windows的批处理命令固然比不上unix的shell脚本强大&#xff0c;但用好了仍能给我们的工作带来很大作用。一个朋友问我为什么学习批处理命令&#xff0c;我以《程序员修炼之道——从小工到专家》一书的一句话答复他&#xff1a;图形界面的优点是所见即…

介质控制访问为什么不适用多路复用技术来解决冲突

因为使用频分多路复用或者时分多路复用会增加延迟时间&#xff0c;同时信道利用率也比较低

CAE所表示的计算机术语是,计算机应用中,英文缩略语CAE所表示的计算机术语是()。...

_刚果红染色可呈红色阳性反应的疾病有()。当管流过水断面流速按抛物线规律分布时&#xff0c;管中水流为紊流。一般含硫量较低的石油多产自碳酸盐岩系和膏岩系含油层。转移因子属于免疫()剂&#xff0c;适用于()。某客户在南京分行营业部开立的一卡通凭证失磁/损坏&#xff0c;…

第 11 章 Paragraphs

目录 11.1. para11.2. simpara11.3. formalpara11.4. bridgehead11.5. blockquote11.6. sidebar11.7. TM 商标 11.8. epigraph 题词11.9. Font Formatting Codes11.9.1. strong11.9.2. bold11.9.3. italic11.9.4. literal11.9.5. remark11.1. para <para>helloworld</…

伤疤好了有黑印怎么办_春藤家长圈|家有二孩,老大老二一起抢东西,家长该怎么办?...

本期主讲&#xff1a;齐静美国G.T.I.认证父母效能训练师高级家庭教育指导师未来春藤家长学院特聘讲师(西安)五年一线幼儿教学从业经验两个孩子的妈妈每周一次的春藤家长圈线上案例分享时间到啦&#xff01;本周是我们线上案例分享的第十四期&#xff01;本周案例&#xff1a;本…

Java_方法

方法就是将一段代码封装在一个结构体中&#xff0c;并且可反复调用 方法的定义&#xff1a; public static 返回值类型 方法名称 &#xff08;[参数类型 变量&#xff0c; 参数类型 变量&#xff0c;参数类型 变量...]&#xff09;{ 方法中包含的代码&#xff1b; [return 返回…

2011年度最佳代码“不管你们信不信,我反正信了”

最近有段十分流行的代码&#xff0c;是从江湖传闻“身怀八蛋”的铁道部发言人王勇平同志的一句名言&#xff1a;“不管你们信不信&#xff0c;我反正信了……这是生命的奇迹……它就是发生了”所引申出来的。这段代码虽然只是在调侃&#xff0c;但是围绕这段代码也产生了一些讨…

无锡计算机硬件培训,无锡锡山办公软件电脑基础培训随到随学 学会为止

一、办公 2个月WORD文字处理&#xff1b;EXCEL电子表格应用&#xff1b;PowerPoint动画幻灯片制作&#xff1b;国际互联网、电子邮件、网上传真&#xff1b;软件安装、计算机维护、基础、常用办公硬件的使用(打印机、扫描仪、刻录机、数码相机等)从事行政、管理、文秘、销售、…

以太网和局域网的关系

以太网 以太网是当今现有局域网采用的通用通信协议标准&#xff0c;组建于七十年代早期。Ethernet(以太网&#xff09;是一种传输速率为10Mbps的常用局域网&#xff08;LAN&#xff09;标准。在以太网中&#xff0c;所有计算机被连接一条同轴电缆上&#xff0c;采用具有冲突检…

不懂卷积神经网络?别怕,看完这几张萌图你就明白了

本文来自AI新媒体量子位&#xff08;QbitAI&#xff09;这篇文章用最简明易懂的方式解释了卷积神经网络&#xff08;CNN&#xff09;的基本原理&#xff0c;并绕开了里面的数学理论。 同时&#xff0c;如果想对从头开始构建CNN网络之类的问题感兴趣&#xff0c;作者推荐去读《 …

xrd连续扫描和步进扫描_一种提高xrd实验精度的方法

一种提高xrd实验精度的方法【专利摘要】本发明公开了一种提高XRD实验精度的方法&#xff0c;包括如下步骤&#xff1a;步骤1&#xff1a;将待测试样进行处理至表面平整光洁&#xff1b;步骤2&#xff1a;选用步进扫描方式对待测钢试样进行XRD实验&#xff0c;步进扫描方式的扫描…

2018双一流排名 计算机,2018中国双一流高校名单

目前全球范围内&#xff0c;比较有影响力的世界大学排名包括《QS世界大学排名》、《世界大学学术排名(ARWU)》、《泰晤士高等教育世界大学排名》、《usnews世界大学排名》和CWUR沙特阿拉伯全球大学评级中心世界大学排名。这些排名从各个方面对世界范围内的高校进行综合排名&…

交换机和集线器的区别

集线器采用的是共享带宽的工作方式&#xff0c;简单打个比如&#xff0c;集线器就好比一条单行道&#xff0c;“10M”的带宽分多个端口使用&#xff0c;当一个端口占用了大部分带宽后&#xff0c;另外的端口就会显得很慢。相反&#xff0c;交换机是一个独享的通道&#xff0c;它…