Selenium: Как да извлечем данни на приложения от Google Play?

Случвало ли ви се е да искате да съберете данни от Интернет и да ги анализирате? Например да изтеглите ревюта на потребители с цел да анализирате мнението им или да съберете данни за продукти от различни онлайн магазини и да сравните цените им.

Можете, разбира се, ръчно да го направите това, но със сигурност би ви отнело изключително много време и усилия, особено ако има стотици страници с данни в уеб сайта, който сте си харесали. Всички тези действия могат да се автоматизират с помощта на различни инструменти (езици за програмиране, програми с графичен интерфейс и др.). Това дава един по-бърз и лесен начин за извличане на данни от Интернет. Също така подобни инструменти предоставят големи възможности. Те позволяват на даден бизнес да се сдобие с голям набор от полезни данни, които после могат да се обработят и анализират с цел подобряване на стратегията.

В тази статия ще ви покажа как можете със средствата на Selenium да извлечете данни на приложения от Google Play Store.

Selenium като инструмент за извличане на данни от Интернет (Web Scraping)

Основното предназначение на колекцията от инструменти на Selenium е автоматизирано тестване на уеб сайтове. Единият от тях (Selenium Webdriver) обаче често се използва и за извличане на данни от Интернет. На определен език за програмиране (Python, Java, Ruby и др.) се пишат скриптове, с помощта на които се управлява браузърът, откриват се необходимите елементи на уеб страниците и се извличат нужните данни.

Кои езици за програмиране, операционни системи и браузъри можем да използваме?

Selenium ни дава голям избор относно технологиите, с които можем да работим. Той се поддържа от всички известни браузъри и операционни системи и можем да избираме между няколко езика за програмиране, което педоставя удобство и гъвкавост.

Езици за програмиране: Python, Java, Ruby, C#, JavaScript

Операционни системи: Windows, Linux, macOS

Браузъри: Chrome, Firefox, Edge, Opera, Safari, Internet Explorer

Можем да пишем скриптове и на други езици като PHP, Go, Perl и R например, но поддръжката не се осъществява от официалния проект на Selenium.

От къде да започнем с извличане на данни от Google Play Store?

За целите на нашата задача, ще използваме езика за програмиране Python и браузъра Google Chrome.

Първото нещо, което трябва да направим, е да стартираме браузъра и да заредим началната страница на Google Play Store.

# Създаване на Service обект, в който ще се съхранява пътя към браузъра. 
service = Service(executable_path=ChromeDriverManager().install())

ChromeDriverManager е клас, който е достъпен благодарение на библиотеката Webdriver Manager, препоръчана в официалната документация на Selenium. С метода install() се проверява дали браузърът Chrome е наличен локално. Ако не е, той се тегли автоматично, а ако е наличен, се проверява коя версия е и се актуализира до най-новата при необходимост. Webdriver Manager съдържа такива класове и за Firefox, Edge, Opera и др. Това изключително много улеснява работата, ако искаме да използваме различни браузъри.

Всичко това може и ръчно да се настрои, но отнема повече време и шанса за допускане на грешки е по-голям.

Service обектът съхранява пътя към изпълнимия файл, от който се стартира браузъра.

# Стартиране на браузъра и максимизиране на прозореца
driver = webdriver.Chrome(service=service)
driver.maximize_window()

Selenium Webdriver стартира браузъра и поема контрол над него с помощта на метода Chrome(), който получава Service обекта като стойност на параметъра service. С другия метод – get() задаваме страницата в Интернет, която искаме да ни се зареди в браузъра.

# Създаване на променлива с url, който води към началната страница на Google Play
initial_url = https://play.google.com

# Зареждане на началната страница на Google Play
driver.get(initial_url)

След като Selenium Webdriver е отворил началната страница на Google Play магазина, ще му зададем команда да разкрие падащото меню, което съдържа всички категории с налични приложения. След като кликне върху него, ще трябва да открие елементите, които съдържат хипервръзки, водещи до страниците на категориите.

# Откриване на бутонът, който отваря падащо меню с категориите
categories = driver.find_element(By.ID, action-dropdown-parent-Categories)

# Клик върху бутона, за отваряне на падащото меню с категориите
categories.click()

# Откриване на всички категории 
categories = driver.find_elements(By.XPATH, //a[contains(@href,'/store/apps/category/')])

След като са намерени категориите, ще запазим хипервръзките в списък, за да може след това Selenium Webdriver да премине през всяка една от тях и да извлече данни на приложенията вътре в нея.

# Създаване на празен списък
cat_url_list = []

# Запазване на хипервръзките на категориите в списъка
for cat in categories:
    cat_url_list.append(cat.get_attribute(href))

# Изтриване на първите 2 категории, тъй като се срещат отново към края на списъка
del cat_url_list[0:2]

# Създаване на нов списък само с първите 3 категории
first3_cat_urls = cat_url_list[:3]

Резултат:
[\'https://play.google.com/store/apps/category/ART_AND_DESIGN\',
\'https://play.google.com/store/apps/category/AUTO_AND_VEHICLES\',
\'https://play.google.com/store/apps/category/BEAUTY\']

За целите на нашия пример ще извлечем данни само от първите 3 категории.

Какви данни на приложенията искаме?

На страницата на всяко приложение има голям набор от възможни данни, които можем да извлечем, но не всичките биха ни били полезни. Точно кои ще ни бъдат нужни зависи от задачата, която искаме да решим. Например ако желаем да направим анализ на настроенията (sentiment analysis), ще са ни нужни данни за ревюта, които потребителите са написали под всяко приложение. Ако задачата ни е свързана с прогнозиране на стойности или групиране на обекти, ще ни бъдат нужни повече данни за самите приложения, като например цена, рейтинг, размер, категория и т.н.

В нашия пример няма да решаваме подобни задачи, а просто ще демонстираме самия процес на извличане на данните, така че ще изберем само някои основни данни на приложенията: заглавие (title), създател (creator), категория (category), рейтинг (rating), цена (price), дата на последно обновление (updated) и размер
в мегабайти (size).

Стъпки за извличане на данните

1. Създаване на речник

Ще създадем речник – defaultdict, който за разлика от стандартния dict, когато се опитаме да достъпим или променим несъществуващ ключ, няма да изхвърли изключение, а ще го добави към речника и ще му зададе стойност по подразбиране.

app_data = collections.defaultdict(list)

В нашият случай стойността по подразбиране ще е празен списък, към който ще добавяме данните на приложенията.

2. Създаване на функция за извличане на данни от приложенията

Основни действия, които ще се изпълняват при извикване на функцията:

  • Откриване на всички видими приложения на съответната страница
  • Влизане в страницата на всяко едно от откритите приложения
  • Откриване на елементите на характеристиките, които са ни нужни
  • Добавяне на данните в речника за съответните характеристики
def get_app_data():
    # Откриване на приложенията
    apps = driver.find_elements(By.CSS_SELECTOR, a.poRVub)

    # Създаване на празен списък, в който след това да добавим линкове към приложенията
    urls = []

    # Добавяне на линк към всяко приложение в списъка
    for app in apps:
        urls.append(app.get_attribute(href))

    # Показване на броя приложения 
    print(fNumber of apps: {len(urls)})

    # Влизане в страницата на всяко едно приложение и извличане на данните за него
    for i, url in zip(range(1, len(urls)+1), urls):
        # Изчакване 10 секунди, ако Selenium Webdriver не може да открие някой елемент
        driver.implicitly_wait(10)

        # Влизане в страницата на дадено приложение
        driver.get(url)

        # Задаване на наименования на ключовете на речника и добавяне на данни на приложението
        app_data[title].append(driver.find_element(By.XPATH, //h1/span).text)
        app_data[creator].append(driver.find_element(By.CSS_SELECTOR, a.hrTbp.R8zArc).text)
        app_data[category].append(driver.find_element(By.XPATH, //a[@itemprop='genre']).text)

        # За приложенията без рейтинг ще имаме липсващи стойности в данните
        try:
            app_data[rating].append(float(driver.find_element(By.CSS_SELECTOR, div.BHMmbe).text))
        except NoSuchElementException as e:
            app_data[rating].append(np.nan)

        # При някои приложения може да липсва бутон за инсталация
        try:
            # Откриване на бутона за инсталация на приложението
            install = driver.find_element(By.CSS_SELECTOR, button.LkLjZd.ScJHi.HPiPcc.IfEcue)

            # Задаване дали приложението е безплатно или платено
            if install.text == Install:
                app_data[price].append(Free)
            else:
                app_data[price].append(install.text.split( )[1])
        except NoSuchElementException as e:
            app_data[price].append(np.nan)

        # Задаване дали приложението е безплатно или платено
        if install.text == Install:
            app_data[price].append(Free)
        else:
            app_data[price].append(install.text.split( )[1])

        # Задаване на ключове за допълнителните характеристики на приложението и добавяне на данни за тях
        app_data[updated].append(driver.find_element(By.XPATH, //div[text()='Updated']//parent::div/following-sibling::span/div/span).text)
        app_data[size].append(driver.find_element(By.XPATH, //div[text()='Size']//parent::div/following-sibling::span/div/span).text)

        # Връщане на предишна страница
        driver.back()

3. Функцията в действие

В по-голяма част от категориите, приложенията са разделени на допълнителни групи. За да се достъпят всичките трябва да се кликне върху бутон Вижте повече. На някои категории има повече от 1 такъв бутон, а на други няма изобщо никакъв.

Ние трябва да зададем на Selenium Webdriver да влезе в страницата на всяко приложение от допълнителните групи в съответните категории и да извлече данните му. За тази цел при зареждане на страница на дадена категория, се проверява дали има бутони Вижте повече. Ако има такива, се запазват хипервръзките им, зарежда се всяка от страниците и се извличат данни на приложенията вътре. Ако няма такива бутони, директно се влиза във всяко приложение в категорията и се взимат данните му.

for url in first3_cat_urls:
    try:
        # Изчакване 10 секунди, ако Selenium Webdriver не може да открие някой елемент
        driver.implicitly_wait(10)

        # Влизане в съответната категория
        driver.get(url)
        print(fLink: {url})

        # Откриване на бутони Вижте повече
        sm_btns = driver.find_elements(By.LINK_TEXT, See more)

        # Ако има Вижте повече бутони на страницата, се взимат хипервръзките на всеки, а ако няма, просто се извличат данните на приложенията в категорията
        if len(sm_btns) > 0:
            # Създаване на празен списък
            see_more_links = []

            # Добавяне на хипервръзките за Вижте повече бутоните към списъка
            for btn in sm_btns:
                see_more_links.append(btn.get_attribute(href))

            # Преминаване през всяка хипервръзка, влизане в страницата на всяко едно приложение и извличане на данни за него
            for i, link in zip(range(1, len(see_more_links)+1), see_more_links): 
                try:
                    # Изчакване 10 секунди, ако Selenium Webdriver не може да открие някой елемент
                    driver.implicitly_wait(10)

                    # Отваряне на хипервръзката
                    driver.get(link)

                    # Кой подред Вижте повече бутон е
                    print(fSee more button No: {i})

                    # Извличане на данните
                    get_app_data()
                except TimeoutException as e:
                    # Ще пропуснем хипервръзката и ще минем на следващия ако случайно стане забавяне и Selenium не може да открие елементите
                    print(f'Skipped link {link}')

        else:
            # Извличане на данните
            get_app_data()
    except TimeoutException as e:
        # Ще пропуснем страницата и ще минем на следващата ако случайно стане забавяне и Selenium не може да открие елементите
        print(f'Skipped page: {page}')

driver.quit()

Преглед и запазване на данните

След като сме приключили с извличането на данните на приложенията, ще създадем нов DataFrame от речника и ще запазим всичко в .csv файл.

df = pd.DataFrame(dict([ (k,pd.Series(v)) for k,v in app_data.items() ]))
df.to_csv('./data/google_play_apps_raw.csv', index=False, sep=,)

Имаме общо 7 колони и 105 реда с данни на приложения, като само рейтингът е от числов тип. Останалите колони са категорийни.

Пет произволни реда от данните изглеждат по следния начин.

titlecreatorcategoryratingpriceupdatedsize
55Artsy — Discover, Buy, and Resell Fine ArtArt.sy Inc.Art & DesignnanFreeJanuary 19, 202262M
45Sketchers UnitedColorfulArt & Design5FreeApril 26, 20216.7M
92NIUNIU International Co., Ltd.Auto & VehiclesnanFreeDecember 20, 202125M
72Carly — OBD2 car scannerCarly Solutions GmbH & Co KGAuto & Vehicles4.4FreeJanuary 26, 2022131M
49Infinite DesignInfinite Studio LLCArt & Design3.8FreeDecember 23, 202132M

Примера можете да изтеглите от тук.

За да отворите .ipynb файла можете да използвате Jupyter Notebook или друг подобен софтуер. Необходимо е да имате инсталирани нужните библиотеки и пакети на Python, за да изпълните кода.

Ако искате да научите повече за Selenium, можете да прочетете следната статия Selenium Webdriver: Автоматизирано тестване на уеб сайтове.

Искате да научите повече за Python?

Включете се в курса по програмиране с Python.

Научете повече

Автор: Десислава Христова