Google Play Store Review Scraper
A production-ready web scraper for extracting app reviews using Selenium, with modern Python best practices.
Python Selenium Web Scraping Data Collection
Executive Summary
This project demonstrates a production-ready web scraper for collecting app reviews from the Google Play Store. The implementation showcases the evolution from a basic script to a robust, maintainable solution using modern Python best practices.
The Challenge
Collecting app reviews at scale from Google Play Store presents several challenges:
- Dynamic Content: Reviews are loaded via JavaScript (lazy loading)
- Pagination: βLoad Moreβ button reveals additional reviews
- Rate Limiting: Aggressive scraping can trigger blocks
- Data Quality: Reviews may be truncated (short vs. full versions)
Before: The Original Approach
The initial implementation had several issues common in quick prototypes:
# Hardcoded paths and URLs
PATH = "C:\\Program Files (x86)\\chromedriver.exe"
driver = webdriver.Chrome(PATH)
# User input instead of CLI args
namefile = input('Input file name to export data : ')
count = input("Amount of data : ")
# Deprecated Selenium API
button = driver.find_element_by_css_selector('span.CwaK9')
# Duplicated logic
def dataload():
try:
for i in range(50):
button = driver.find_element_by_css_selector('span.CwaK9')
# ... same code repeated in except block
except:
try:
# ... duplicated logic here
except:
scrolldown()
Problems identified:
- β Hardcoded ChromeDriver path (breaks on different systems)
- β Deprecated
find_element_by_*methods - β Repetitive code blocks
- β Bare
exceptclauses (catches everything, including KeyboardInterrupt) - β No CLI arguments (requires interactive input)
- β No logging or proper progress tracking
After: The Optimized Solution
1. Configuration with Dataclasses
@dataclass
class ScraperConfig:
"""Configuration for the scraper."""
app_id: str
output_file: str
target_count: int = 1000
language: str = "id"
headless: bool = False
scroll_pause: float = 2.0
max_retries: int = 3
backup_interval: int = 500
2. Modern Selenium 4+ API
# Before (deprecated)
driver.find_element_by_css_selector('span.CwaK9')
# After (modern)
from selenium.webdriver.common.by import By
driver.find_element(By.CSS_SELECTOR, 'span.CwaK9')
3. Centralized Selectors
class PlayStoreReviewScraper:
SELECTORS = {
"review_container": "div.LXrl4c",
"review_item": "div.UD7Dzf",
"short_comment": 'span[jsname="bN97Pc"]',
"long_comment": 'span[jsname="fbQN7e"]',
"load_more_button": "span.CwaK9",
}
4. Robust Error Handling
from selenium.common.exceptions import (
NoSuchElementException,
TimeoutException,
ElementClickInterceptedException,
)
def _click_load_more(self) -> bool:
try:
button = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR, self.SELECTORS["load_more_button"])
)
)
button.click()
return True
except (NoSuchElementException, TimeoutException, ElementClickInterceptedException):
return False
5. CLI Interface with argparse
# Now runs with simple command-line arguments
python webscrape.py --app-id com.gojek.app --output reviews.csv --count 1000 --headless
Architecture Overview
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PlayStoreReviewScraper β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β ScraperConfig (dataclass) β
β βββ app_id: str β
β βββ output_file: str β
β βββ target_count: int β
β βββ language: str β
β βββ headless: bool β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Core Methods β
β βββ scrape() β Main loop β
β βββ _load_more_reviews() β Pagination handling β
β βββ _extract_review_text() β Text extraction β
β βββ _export_reviews() β CSV export β
β βββ _create_backup() β Automatic backups β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Key Improvements Summary
| Aspect | Before | After |
|---|---|---|
| Configuration | Hardcoded values | Dataclass + CLI args |
| Selenium API | Deprecated methods | Modern By locators |
| Error Handling | Bare except | Specific exceptions |
| Code Structure | Procedural script | OOP with single responsibility |
| Logging | Print statements | Python logging module |
| Progress Tracking | Manual prints | Structured progress updates |
| Backups | Manual | Automatic interval-based |
| Portability | Windows-only path | webdriver-manager fallback |
Usage
# Install dependencies
pip install selenium pandas webdriver-manager
# Basic usage
python webscrape.py --app-id com.gojek.app --count 500
# Full options
python webscrape.py \
--app-id com.tokopedia.tkpd \
--output tokopedia_reviews.csv \
--count 2000 \
--language en \
--headless
Conclusion
This refactoring demonstrates how a quick prototype can be transformed into a maintainable, production-ready tool. The key principles applied were:
- Single Responsibility: Each method does one thing well
- Configuration as Data: Dataclasses make settings explicit
- Explicit Error Handling: Catch only what you expect
- CLI Over Input: Scripts should be automatable
- Progressive Enhancement: Backups and logging for reliability