日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

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

發(fā)布時(shí)間:2023/12/19 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一起看一下主流应用使用了哪些三方库 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

背景

我們?cè)谶M(jìn)行Android開(kāi)發(fā)時(shí)往往會(huì)面臨技術(shù)選型的問(wèn)題, 面對(duì)如此多的開(kāi)源框架如何進(jìn)行選擇、選擇的標(biāo)準(zhǔn)是什么,這是一個(gè)值得思考的問(wèn)題. 為此我在后臺(tái)爬取了6000多個(gè)主流應(yīng)用,逐個(gè)反編譯統(tǒng)計(jì)它們使用了哪些開(kāi)源框架,因此做了一個(gè)款應(yīng)用

基本思路

首先我們要有Apk才可以進(jìn)行分析,我選擇爬取酷安的應(yīng)用數(shù)據(jù)(感覺(jué)酷安比較好爬一點(diǎn)),將每個(gè)應(yīng)用的apk下載到本地,通過(guò)apktool進(jìn)行反編譯,查看反編譯后的結(jié)果。雖然大部分應(yīng)用都會(huì)進(jìn)行混淆,但是涉及三方庫(kù)的包一般是不會(huì)進(jìn)行混淆的,所以我們只需要統(tǒng)計(jì)出代碼的目錄結(jié)構(gòu)基本就可以推敲出該應(yīng)用使用了哪些三方庫(kù)。

使用pyspider爬取酷安數(shù)據(jù)

一般提到爬蟲(chóng)我們首先選擇Python,在GitHub上Python中star最多的爬蟲(chóng)框架就是pyspider了,這是由國(guó)人開(kāi)發(fā)的一個(gè)爬蟲(chóng)框架,用起來(lái)還算方便。只是在windows上安裝不易,建議還是在linux安裝,具體安裝方式這里就不多介紹了,網(wǎng)上有很多教程。安裝之后的界面是這樣的

直接點(diǎn)擊右邊的Create新建任務(wù)就可以了

我們只需要在右邊寫(xiě)代碼,保存之后在左邊點(diǎn)擊run就可以查看執(zhí)行結(jié)果 我們先來(lái)看一下要爬取的對(duì)象

一共有653頁(yè),每頁(yè)10個(gè),一共6530個(gè)應(yīng)用。爬取的就基本思路就是首先根據(jù)Url:https://www.coolapk.com/apk?p=1生成爬取的任務(wù)。在pyspider中通過(guò)self.crawl創(chuàng)建爬取任務(wù),該方法有兩個(gè)參數(shù),第一個(gè)為要爬去的url,第二個(gè)為回調(diào)函數(shù)。如爬取每頁(yè)數(shù)據(jù)的代碼為

@config(age=10 * 24 * 60 * 60)def index_page(self, response):url = 'https://www.coolapk.com/apk?p='# 從第1頁(yè)到653頁(yè)生成任務(wù)for i in range(1, 654):self.crawl(url + str(i), callback=self.list_page)復(fù)制代碼

這樣爬蟲(chóng)會(huì)自動(dòng)訪問(wèn)每頁(yè)的數(shù)據(jù),在訪問(wèn)成功之后回調(diào)list_page方法,在list_page方法中會(huì)提取該頁(yè)中每個(gè)App的詳情頁(yè)對(duì)應(yīng)的url,然后繼續(xù)生成抓取任務(wù)

根據(jù)酷安App列表頁(yè)面的dom結(jié)構(gòu)可以看到我們首先要找到class為app_left_list的div,該div下a標(biāo)簽的href值即為App詳情頁(yè)對(duì)應(yīng)的url,具體代碼如下

@config(priority=2)def list_page(self, response):# 從每一頁(yè)中打開(kāi)App詳情頁(yè)面for each in response.doc('div[class="app_left_list"]').children('a').items():self.crawl(each.attr.href, callback=self.detail_page)復(fù)制代碼

最后就是在App詳情頁(yè)面提取我們需要的App的信息,然后將提取的信息保存到數(shù)據(jù)庫(kù)中,并根據(jù)提取到的apk鏈接下載該apk,實(shí)際測(cè)試中發(fā)現(xiàn)酷安在進(jìn)行apk文件下載時(shí)是有session校驗(yàn)的,所以下載時(shí)需要攜帶上session信息,由于下載過(guò)程比較耗時(shí),pyspider不支持這種耗時(shí)操作,所以我們需要單獨(dú)開(kāi)啟線程下載。

對(duì)于稍微具備一點(diǎn)前端知識(shí)的同學(xué),然后查閱一下pyquery的用法,基本上提取我們需要的信息就沒(méi)什么大問(wèn)題。

完整的爬取代碼如下

#!/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應(yīng)用配置信息Bomb_Application_Id = 'bomb對(duì)應(yīng)的Application Id'Bomb_Rest_Api_Key = 'bomb對(duì)應(yīng)的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頁(yè)到653頁(yè)生成任務(wù)for i in range(1, 654):self.crawl(url + str(i), callback=self.list_page) @config(priority=2)def list_page(self, response):# 從每一頁(yè)中打開(kāi)App詳情頁(yè)面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('萬(wàn)下載'):return float(download_str.split('萬(wàn)下載')[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的唯一鍵不靠譜,每次保存之前先查詢是否存在,然后再進(jìn)行更新或者保存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)復(fù)制代碼

使用Apktool反編譯apk文件

apk文件下載完成之后我們就可以使用apktool進(jìn)行反編譯了。基本命令是java -jar apktool_2.3.0.jar d xxx.apk -o destDir -f。這里我使用的apktool版本為2.3.0。

具體做法是依次反編譯每個(gè)apk文件,一般情況下apk反編譯之后的文件目錄大致包含以下內(nèi)容

第一個(gè)文件就不解釋了,做Android開(kāi)發(fā)的同學(xué)都知道。值得注意的是Apk的版本信息沒(méi)有在AndroidManifest文件中,而是在apktool.yml文件中,這個(gè)文件里面包含很多apk有價(jià)值的信息。另一個(gè)值得我們關(guān)注的是smali文件夾,如果apk進(jìn)行了分包可能還會(huì)出現(xiàn)smali_class2、smali_class3之類的文件夾。我們分析該app引用了哪些三方庫(kù)主要看smali下的文件目錄結(jié)構(gòu)是什么樣的。雖然這種方式并不完全準(zhǔn)確,但是也能涵蓋絕大部分三方庫(kù)。

具體代碼如下

#!/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對(duì)應(yīng)的Application Id','X-Bmob-REST-API-Key': 'bomb對(duì)應(yīng)的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)# 單線程運(yùn)行for name in name_list:decompileApk(name)# 多線程運(yùn)行# myrequets = threadpool.makeRequests(decompileApk, name_list)# [pool.putRequest(req) for req in myrequets]# pool.wait()print('All Finished')else:print('參數(shù)必須為一個(gè)目錄')復(fù)制代碼

從實(shí)際分析結(jié)果來(lái)看,目前的分析算法還有很多問(wèn)題,統(tǒng)計(jì)出來(lái)的包名和我們實(shí)際使用的三方庫(kù)不能完全匹配,有時(shí)會(huì)把子包名統(tǒng)計(jì)進(jìn)去。所以只能靠大家經(jīng)驗(yàn)還判斷每個(gè)包名對(duì)應(yīng)的是哪個(gè)三方庫(kù)了。

App展示統(tǒng)計(jì)結(jié)果

最后將上面抓取和分析的結(jié)果以App的形式展示出來(lái),相比上兩步而言這個(gè)是最簡(jiǎn)單的了。目前主要提供兩個(gè)維度的展示,一是按照酷安上的下載量展示App信息,在App詳情中展示該app下統(tǒng)計(jì)出來(lái)的包信息;另一個(gè)維度是按照庫(kù)被引用的次數(shù)展示,詳情頁(yè)面中展示哪些應(yīng)用中包含這個(gè)庫(kù)。功能比較簡(jiǎn)單所以就不多解釋了,直接放代碼地址:github.com/dumingxin/A…,歡迎大家star、提issue,或者有更好的想法一起來(lái)實(shí)現(xiàn)。

App目前已經(jīng)發(fā)布在酷安市場(chǎng),下載地址為:www.coolapk.com/apk/172597

二維碼:

總結(jié)

從開(kāi)始著手準(zhǔn)備,到最終完成第一個(gè)版本的功能大概兩周時(shí)間,由于沒(méi)有正經(jīng)學(xué)習(xí)過(guò)python,所以python相關(guān)代碼寫(xiě)的可能不太規(guī)范,僅供大家參考。

目前實(shí)際下載下來(lái)的apk文件只有5000+,還有1000多沒(méi)有下載下來(lái)。apk反編譯還在進(jìn)行,目前已經(jīng)分析了2000+,所以統(tǒng)計(jì)結(jié)果可能還會(huì)不斷變化

感謝

https://www.coolapk.com/ 感謝酷安提供的數(shù)據(jù)(手動(dòng)滑稽)

https://github.com/binux/pyspider 感謝pyspider讓我一個(gè)新手也可以爬數(shù)據(jù)

https://github.com/tp7309/AndroidOneKeyDecompiler 感謝作者提供python反編譯apk的思路

總結(jié)

以上是生活随笔為你收集整理的一起看一下主流应用使用了哪些三方库的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。