import xmltodict import requests import datetime import multiprocessing as mp COUNTRY_CODES = { "AF": "AFGHANISTAN", # "AX": "ÅLAND ISLANDS", "AL": "ALBANIA", "DZ": "ALGERIA", # "AS": "AMERICAN SAMOA", "AD": "ANDORRA", "AO": "ANGOLA", "AI": "ANGUILLA", # "AQ": "ANTARCTICA", "AG": "ANTIGUA AND BARBUDA", "AR": "ARGENTINA", "AM": "ARMENIA", # "AW": "ARUBA", "AU": "AUSTRALIA", "AT": "AUSTRIA", "AZ": "AZERBAIJAN", "BS": "BAHAMAS", "BH": "BAHRAIN", "BD": "BANGLADESH", "BB": "BARBADOS", "BY": "BELARUS", "BE": "BELGIUM", "BZ": "BELIZE", "BJ": "BENIN", "BM": "BERMUDA", "BT": "BHUTAN", "BO": "BOLIVIA, PLURINATIONAL STATE OF", # "BQ": "BONAIRE, SINT EUSTATIUS AND SABA", "BA": "BOSNIA AND HERZEGOVINA", "BW": "BOTSWANA", # "BV": "BOUVET ISLAND", "BR": "BRAZIL", # "IO": "BRITISH INDIAN OCEAN TERRITORY", "BN": "BRUNEI DARUSSALAM", "BG": "BULGARIA", "BF": "BURKINA FASO", # "BI": "BURUNDI", "KH": "CAMBODIA", "CM": "CAMEROON", "CA": "CANADA", "CV": "CAPE VERDE", "KY": "CAYMAN ISLANDS", "CF": "CENTRAL AFRICAN REPUBLIC", "TD": "CHAD", "CL": "CHILE", "CN": "CHINA", # "CX": "CHRISTMAS ISLAND", # "CC": "COCOS (KEELING) ISLANDS", "CO": "COLOMBIA", # "KM": "COMOROS", "CG": "CONGO", "CD": "CONGO, THE DEMOCRATIC REPUBLIC OF THE", # "CK": "COOK ISLANDS", "CR": "COSTA RICA", "CI": "CÔTE D'IVOIRE", "HR": "CROATIA", # "CU": "CUBA", # "CW": "CURAÇAO", "CY": "CYPRUS", "CZ": "CZECH REPUBLIC", "DK": "DENMARK", # "DJ": "DJIBOUTI", "DM": "DOMINICA", "DO": "DOMINICAN REPUBLIC", "EC": "ECUADOR", "EG": "EGYPT", "SV": "EL SALVADOR", # "GQ": "EQUATORIAL GUINEA", # "ER": "ERITREA", "EE": "ESTONIA", "ET": "ETHIOPIA", # "FK": "FALKLAND ISLANDS (MALVINAS)", # "FO": "FAROE ISLANDS", "FJ": "FIJI", "FI": "FINLAND", "FR": "FRANCE", # "GF": "FRENCH GUIANA", # "PF": "FRENCH POLYNESIA", # "TF": "FRENCH SOUTHERN TERRITORIES", "GA": "GABON", "GM": "GAMBIA", "GE": "GEORGIA", "DE": "GERMANY", "GH": "GHANA", # "GI": "GIBRALTAR", "GR": "GREECE", # "GL": "GREENLAND", "GD": "GRENADA", # "GP": "GUADELOUPE", # "GU": "GUAM", "GT": "GUATEMALA", # "GG": "GUERNSEY", "GN": "GUINEA", "GW": "GUINEA-BISSAU", "GY": "GUYANA", # "HT": "HAITI", # "HM": "HEARD ISLAND AND MCDONALD ISLANDS", # "VA": "HOLY SEE (VATICAN CITY STATE)", "HN": "HONDURAS", "HK": "HONG KONG", "HU": "HUNGARY", "IS": "ICELAND", "IN": "INDIA", "ID": "INDONESIA", # "IR": "IRAN, ISLAMIC REPUBLIC OF", "IQ": "IRAQ", "IE": "IRELAND", # "IM": "ISLE OF MAN", "IL": "ISRAEL", "IT": "ITALY", "JM": "JAMAICA", "JP": "JAPAN", # "JE": "JERSEY", "JO": "JORDAN", "KZ": "KAZAKHSTAN", "KE": "KENYA", # "KI": "KIRIBATI", # "KP": "KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF", "KR": "KOREA, REPUBLIC OF", "KW": "KUWAIT", "KG": "KYRGYZSTAN", "LA": "LAO PEOPLE'S DEMOCRATIC REPUBLIC", "LV": "LATVIA", "LB": "LEBANON", # "LS": "LESOTHO", "LR": "LIBERIA", "LY": "LIBYA", "LI": "LIECHTENSTEIN", "LT": "LITHUANIA", "LU": "LUXEMBOURG", "MO": "MACAO", "MK": "MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF", "MG": "MADAGASCAR", "MW": "MALAWI", "MY": "MALAYSIA", "MV": "MALDIVES", "ML": "MALI", "MT": "MALTA", # "MH": "MARSHALL ISLANDS", # "MQ": "MARTINIQUE", "MR": "MAURITANIA", "MU": "MAURITIUS", # "YT": "MAYOTTE", "MX": "MEXICO", "FM": "MICRONESIA, FEDERATED STATES OF", "MD": "MOLDOVA, REPUBLIC OF", "MC": "MONACO", "MN": "MONGOLIA", "ME": "MONTENEGRO", "MS": "MONTSERRAT", "MA": "MOROCCO", "MZ": "MOZAMBIQUE", "MM": "MYANMAR", "NA": "NAMIBIA", "NR": "NAURU", "NP": "NEPAL", "NL": "NETHERLANDS", # "NC": "NEW CALEDONIA", "NZ": "NEW ZEALAND", "NI": "NICARAGUA", "NE": "NIGER", "NG": "NIGERIA", # "NU": "NIUE", # "NF": "NORFOLK ISLAND", # "MP": "NORTHERN MARIANA ISLANDS", "NO": "NORWAY", "OM": "OMAN", "PK": "PAKISTAN", "PW": "PALAU", "PS": "PALESTINE, STATE OF", "PA": "PANAMA", "PG": "PAPUA NEW GUINEA", "PY": "PARAGUAY", "PE": "PERU", "PH": "PHILIPPINES", # "PN": "PITCAIRN", "PL": "POLAND", "PT": "PORTUGAL", # "PR": "PUERTO RICO", "QA": "QATAR", # "RE": "RÉUNION", "RO": "ROMANIA", "RU": "RUSSIAN FEDERATION", "RW": "RWANDA", # "BL": "SAINT BARTHÉLEMY", # "SH": "SAINT HELENA, ASCENSION AND TRISTAN DA CUNHA", "KN": "SAINT KITTS AND NEVIS", "LC": "SAINT LUCIA", # "MF": "SAINT MARTIN (FRENCH PART)", # "PM": "SAINT PIERRE AND MIQUELON", "VC": "SAINT VINCENT AND THE GRENADINES", "WS": "SAMOA", # "SM": "SAN MARINO", "ST": "SAO TOME AND PRINCIPE", "SA": "SAUDI ARABIA", "SN": "SENEGAL", "RS": "SERBIA", "SC": "SEYCHELLES", "SL": "SIERRA LEONE", "SG": "SINGAPORE", # "SX": "SINT MAARTEN (DUTCH PART)", "SK": "SLOVAKIA", "SI": "SLOVENIA", "SB": "SOLOMON ISLANDS", # "SO": "SOMALIA", "ZA": "SOUTH AFRICA", # "GS": "SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS", # "SS": "SOUTH SUDAN", "ES": "SPAIN", "LK": "SRI LANKA", # "SD": "SUDAN", "SR": "SURINAME", # "SJ": "SVALBARD AND JAN MAYEN", "SZ": "SWAZILAND", "SE": "SWEDEN", "CH": "SWITZERLAND", # "SY": "SYRIAN ARAB REPUBLIC", "TW": "TAIWAN, PROVINCE OF CHINA", "TJ": "TAJIKISTAN", "TZ": "TANZANIA, UNITED REPUBLIC OF", "TH": "THAILAND", # "TL": "TIMOR-LESTE", # "TG": "TOGO", # "TK": "TOKELAU", "TO": "TONGA", "TT": "TRINIDAD AND TOBAGO", "TN": "TUNISIA", "TR": "TURKEY", "TM": "TURKMENISTAN", "TC": "TURKS AND CAICOS ISLANDS", # "TV": "TUVALU", "UG": "UGANDA", "UA": "UKRAINE", "AE": "UNITED ARAB EMIRATES", "GB": "UNITED KINGDOM", "US": "UNITED STATES", # "UM": "UNITED STATES MINOR OUTLYING ISLANDS", "UY": "URUGUAY", "UZ": "UZBEKISTAN", "VU": "VANUATU", "VE": "VENEZUELA, BOLIVARIAN REPUBLIC OF", "VN": "VIET NAM", "VG": "VIRGIN ISLANDS, BRITISH", # "VI": "VIRGIN ISLANDS, U.S.", # "WF": "WALLIS AND FUTUNA", # "EH": "WESTERN SAHARA", "YE": "YEMEN", "ZM": "ZAMBIA", "ZW": "ZIMBABWE", } class Review: def __init__(self, updated, country, title, review, rating, version, author): self.updated = updated self.country = country self.title = title self.review = review self.rating = rating self.version = version self.author = author def review_from_entry(entry, country) -> Review: updated = datetime.datetime.fromisoformat(entry["updated"]) title = entry["title"] review = entry["content"][0]["#text"] rating = entry["im:rating"] version = entry["im:version"] author = entry["author"]["name"] return Review(updated, country, title, review, rating, version, author) def reviews_for_country_code(app_id, country_code) -> [Review]: reviews = [] url = f"https://itunes.apple.com/{country_code}/rss/customerreviews/id={app_id}/xml" headers = {'User-Agent': 'Mozilla/5.0', 'Cache-Control': 'no-cache'} response = requests.get(url, headers=headers) if response.status_code == 400: print(f"NO APP STORE: {country_code}") return reviews data = xmltodict.parse(response.content) if "entry" in data['feed'].keys(): # has review entries entries = data['feed']['entry'] if isinstance(entries, list): # list of reviews for entry in data["feed"]["entry"]: reviews.append(review_from_entry(entry, country_code)) elif "title" in entries.keys(): # only one review reviews.append(review_from_entry(entries, country_code)) # print(f"{country_code}: {len(reviews)}") return reviews def get_reviews(app_id, country_codes): pool = mp.Pool() for country_code in country_codes: pool.apply_async(reviews_for_country_code, args = (app_id, country_code, ), callback = log_reviews) pool.close() pool.join() review_list = [] def log_reviews(reviews): global review_list review_list += reviews app_id = "428395953" if __name__ == '__main__': get_reviews(app_id, COUNTRY_CODES) sorted_reviews = sorted(review_list, key=lambda review: review.updated, reverse=True) review_sum = 0 for review in sorted_reviews: review_sum += int(review.rating) print(f"\ndate: {review.updated}\ncountry: {COUNTRY_CODES[review.country]}\nversion: {review.version}\nrating: {review.rating}\nauthor: {review.author}\ntitle: {review.title}\nreview:\n{review.review}\n") print(f"REVIEWS: {len(review_list)}") print(f"RATING: {round(review_sum / len(review_list), 2)}")