이전 포스팅에서 Python 크롤러인 Selenium으로 https://apkpure.com 사이트에서 Android apk를 package name을 기준으로 apk으로 다운로드하는 방법을 설명하였습니다.
[모바일 SW 개발/Android] - 파이썬으로 Apk Download 자동화: Selenium기반의 Apk 크롤러
Python selenium 모듈을 활용 방법은 chrome driver를 사용하고 있기 때문에 ① Chrome 버전 업데이트되면 driver도 같이 업데이트해야 하고 ② apk 다운로드를 완료를 시점을 정확하게 모르기 때문에 일정 시간 동안 sleep()으로 대기를 해야 합니다. 일반적으로 apk 사이즈가 100MB 이하에는 1~2분 안에 다운로드가 완료하지만, 일부 게임은 2GB가 넘어가는 경우도 있어 다운로드 완료 시간을 정확하게 산정할 수 없는 단점이 있습니다. 또한 ③ 일부 서버 (예를 들어 Google Cloud or AWS EC2)는 리눅스 배포판의 최소 OS(minimal os) 소프트웨어 패키지만 설치되어 있어 Chrome 이 설치되지 않은 경우도 있습니다.
본 포스팅은 이러한 Selenium의 문제점을 보안하기 위한 내용을 설명하고자 합니다. Python Selenium 모듈 대신 HTML을 분석하고 특정한 link 나 item을 찾는 것이 가능한 BeautifulSoup(BS) 모듈을 활용할 예정입니다. Python code를 만들기 전에 Chrome으로 apkpure.com 사이트에서 아래와 같은 1) Apk 검색, 2) 다운로드하는 방법을 먼저 설명합니다.
- Apkpure 검색 사이트에서 apk 검색
- 검색된 Apk의 링크로 이동 후 Download 사이트에서 다운로드 진행
- 1번과 2번 내용을 Python BeatifulSoup를 활용해서 python code로 작성
Apkpure검색 사이트에서 apk 검색
찾고자 하는 apk의 keyword 또는 Package name이름으로 apkpure 검색 사이트(https://apkpure.com/search)에서 검색합니다. Package Name을 정확하게 알고 있는 경우에는 package name을 넣어주는 것이 정확하게 검색됩니다. 예를 들어 apkpure.com/search 사이트에서 ① SKT T-Map을 찾기 위해서 'tmap'으로 검색하는 경우 아래와 같인 유사한 apk가 여러 개가 같이 검색됩니다. 검색된 Apk 중에서 다운로드하고자 하는 apk를 선택합니다.
Chrome의 개발자 메뉴(F12)에서 ② apk를 선택 시 html에서 미리 정의된 download link를 알 수 있습니다. Html 상에서는 <id=serach-res> 하위에 <class=search-dl>에 포함된 리킁하가 해당 apk를 다운로드할 수 있는 링크입니다. Apkpure 사이트에서는 package name의 조합의 링크가 구성되며, 본 T-map 예제는 https://apkpure.com/t-map-내비게이션-지도/com.skt.tmap.ku로 링크가 걸려있습니다.
검색된 Apk의 링크로 이동 후 Download 사이트에서 다운로드 진행
Chrome에서 Apk 링크를 이동하면 이동하면 Chrome에서는 다운로드가 바로 시작됩니다. 이 단계에서는 다운로드 링크를 확인할 수 없기 때문에 "Click here (직접 다운로드)"를 누르면 /download?from=detail 사이트로 이동되며 실제 apk를 다운로드할 수 있는 링크를 확인할 수 있습니다. 해당 링크로 접속하면 Apk 가 다운로드됩니다.
Apk download 링크는 HTML 상에서는 "download_link" element로 아래 그램에서 ③과 ④입니다.
Python BeatifBeautifulSoup 모듈로 Apk 다운로드 code 작성
Python 3.8 버전으로 기준으로 requests, BeautifulSoup4, argparse 모듈을 설치합니다.
Python 모듈 설치
$ sudo pip3 install requests
$ sudo pip3 install BeautifulSoup4
$ sudo pip3 install argparse
Python 소스 코드와 실행 방법
앞에서 Chrome으로 apk를 다운로드하는 방법을 Python code로 옮기는 과정입니다. 해당 내용을 구현하고 있는 중간에 동일한 방법으로 이미 구현된 코드가 dawand/download_apk.py에 공개되어 있어 이를 기반으로 코드를 정리하였습니다. 소스 코드에서 search() 함수가 Apkpure 사이트에서 찾고자 하는 apk를 검색하는 함수이고, download() 함수를 apk의 링크를 기반으로 Apkpure 사이트에서 apk를 다운로드하여 저장하는 코드입니다.
첨부의 download_apk 사용법은 $ python3 ./download_apk.py -p PACKAGE_NAME [-d DIR_OUTPUT] 입니다. Package name 대신 Keyword를 입력하는 경우 Apkpure 검색 사이트에서 가장 우선순위가 높은 apk를 다운로드합니다.
아래 예제에서 T-map 같은 경우에는 URL에 한글이 포함되어 있어 글씨가 깨져 보이는 것으로 이에 대한 설명은 별도로 포스팅 예정입니다. (kibua20.tistory.com/159)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
File name: download_apk.py
Author: Dawand Sulaiman
Download APK files from Google Play Store with Python
This script scraps https://apkpure.com to get the apk download link
Make sure you have BeautifulSoup and urllib libraries
* It comes from https://gist.github.com/7b4308d568c6b955b645dd7e707e5cf1.git
- Add input parameter and comments
- Remove redundant query parameter to apkpure.com
"""
from bs4 import BeautifulSoup
from urllib.parse import quote_plus
from urllib.parse import unquote_plus
import requests
import argparse
import os
def search(query):
res = requests.get('https://apkpure.com/search?q={}'.format(quote_plus(query))).text
soup = BeautifulSoup(res, "html.parser")
search_result = soup.find('div', {'id': 'search-res'}).find('dl', {'class': 'search-dl'})
app_tag = search_result.find('p', {'class': 'search-title'}).find('a')
download_link = 'https://apkpure.com' + app_tag['href']
return download_link
def download(link, dir_output=None):
print('Downloading {}'.format(unquote_plus(link)))
res = requests.get(link + '/download?from=details').text
soup = BeautifulSoup(res, "html.parser").find('a', {'id': 'download_link'})
if soup['href']:
r = requests.get(soup['href'], stream=True)
file_name = link.split('/')[-1] + '.apk'
if dir_output != None:
if not os.path.exists(dir_output):
os.makedirs(dir_output)
file_name = os.path.join(dir_output, file_name)
with open(file_name, 'wb') as file:
for chunk in r.iter_content(chunk_size=1024):
if chunk:
file.write(chunk)
print ('Download completed!',file_name)
def download_apk(app_id, dir_output=None):
download_link = search(app_id)
if download_link is not None:
download(download_link, dir_output)
else:
print('No results for %s' %(app_id))
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Apk downloader')
parser.add_argument('-p', '--package', help='Input package name to be download', required=True)
parser.add_argument('-d', '--dir_output', help='Specifiy output dir')
args = parser.parse_args()
download_apk(args.package, args.dir_output)
download.py 코드 설명
Code가 길지 않아서 code에 inline으로 설명을 추가하였습니다.
def download_apk(app_id, dir_output=None):
# https://apkpure.com/search 에서 검색해서 apk의 다운로드 링크를 얻어온다
download_link = search(app_id)
if download_link is not None:
# 다운로드 링크의 HTML을 parsing 해서 apk를 다운로드한다.
download(download_link, dir_output)
else:
print('No results for %s' %(app_id))
def search(query):
# request 모듈로 https://apkpure.com/serach?q=package에 대한 응답 (=html + java script)를 받아서 저장한다.
res = requests.get('https://apkpure.com/search?q={}'.format(quote_plus(query))).text
# BS 모듈 객체 생성
soup = BeautifulSoup(res, "html.parser")
# Html element 중 id=search-res를 찾고, class=search-dl을 찾는다
search_result = soup.find('div', {'id': 'search-res'}).find('dl', {'class': 'search-dl'})
# Html element 중 p tag 중 class=search-title인 것을 찾아서 링크를 얻어온다
app_tag = search_result.find('p', {'class': 'search-title'}).find('a')
download_link = 'https://apkpure.com' + app_tag['href']
# 결과적으로 Apkpure에서 처리하는 해당 apk의 고유한 hash 값을 얻어옴
return download_link
def download(link, dir_output=None):
print('Downloading {}'.format(unquote_plus(link)))
# download link에 html을 요청하여 받음
res = requests.get(link + '/download?from=details').text
# BS로 id=download_link를 찾고 hyperlink을 찾음
soup = BeautifulSoup(res, "html.parser").find('a', {'id': 'download_link'})
# download link가 있는 경우 apk 파일 다운로드해서 r에 저장함
if soup['href']:
r = requests.get(soup['href'], stream=True)
# output 파일 이름을 package name으로 하고 확장자를 apk로 함, 음수 index는 상세 설명은 링크를 참조
file_name = link.split('/')[-1] + '.apk'
# output dir 이 있는 경우 폴더를 생성하고 파일명에 output dir을 추가함
if dir_output != None:
if not os.path.exists(dir_output):
os.makedirs(dir_output)
file_name = os.path.join(dir_output, file_name)
# apk의 stream 내용을 r을 binary 파일로 저장함
with open(file_name, 'wb') as file:
for chunk in r.iter_content(chunk_size=1024):
if chunk:
file.write(chunk)
관련 글
[모바일 SW 개발/Android] - 파이썬으로 Apk Download 자동화: Selenium기반의 Apk 크롤러
[모바일 SW 개발/Android] - 안드로이드 스마트 폰 화면 미러링 방법: scrcpy 사용법
[개발환경/Tips] - IKnowWhatYouDownload.com에 대한 Torrent 개인 정보 노출 방지법: Socks5 및 익명모드
[모바일 SW 개발/Android] - Android 에서 리눅스 App 실행: Linux Terminal Emulator (Termux) 활용
[모바일 SW 개발/Android] - Android 소스 최적화 (100GB에서 65GB로 줄이기)
[모바일 SW 개발/Android] - Soong 빌드 시스템을 활용한 Android.bp 작성법
[모바일 SW 개발/Android] - Ubuntu에서 Android 10 빌드하기
댓글