一起看一下主流應用使用了哪些三方庫

背景

我們在進行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.pswp.cn/news/453166.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/453166.shtml
英文地址,請注明出處:http://en.pswp.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;它…