diff --git a/__pycache__/reviews.cpython-310.pyc b/__pycache__/reviews.cpython-310.pyc new file mode 100644 index 0000000..47c9092 Binary files /dev/null and b/__pycache__/reviews.cpython-310.pyc differ diff --git a/google_reviews_ratings.csv b/google_reviews_ratings.csv new file mode 100644 index 0000000..861b070 --- /dev/null +++ b/google_reviews_ratings.csv @@ -0,0 +1,166 @@ +review_id,rating,date_raw,date_approx +Ci9DQUlRQUNvZENodHljRjlvT2xNd1EyNXNWRWxYUmpsR2NEZ3hTV3hSTURkT2VtYxAB,5,miesiąc temu,2026-07-31 +Ci9DQUlRQUNvZENodHljRjlvT210bE9XcEZUMWRpUXpSWlozZzFVa3BFTVhoc1VHYxAB,5,2 miesiące temu,2026-06-30 +Ci9DQUlRQUNvZENodHljRjlvT21kQ2IzbHphRVZYVm14V1dXTTFNV0Z3TkU0MmVsRRAB,5,3 miesiące temu,2026-05-31 +Ci9DQUlRQUNvZENodHljRjlvT2pocVF6QTFTalF3UzNoSGJsbHlPVTVxZEVGblJrRRAB,5,2 miesiące temu,2026-06-30 +Ci9DQUlRQUNvZENodHljRjlvT2tkb1pteDJaa3BFZGs5ZmNYaDJheTEwUjJwbGIzYxAB,1,miesiąc temu,2026-07-31 +Ci9DQUlRQUNvZENodHljRjlvT2pOdFdGaHBhRVJQWkVOS1JHdEJjbTFVYzBJMVkwRRAB,5,miesiąc temu,2026-07-31 +Ci9DQUlRQUNvZENodHljRjlvT25OWU1HRlNVR3hYWTFVelRXWnBTM1JrWVdrdFRIYxAB,5,8 miesięcy temu,2025-12-31 +Ci9DQUlRQUNvZENodHljRjlvT2xsbmJGRmlSWFl5TmpGS2QxTXdObm94VWtWdmJVRRAB,5,3 miesiące temu,2026-05-31 +Ci9DQUlRQUNvZENodHljRjlvT2xOWGFFVkpiV3hZTW1WdVEzaGpOVEYyYlhVd1VIYxAB,5,4 miesiące temu,2026-04-30 +Ci9DQUlRQUNvZENodHljRjlvT2xjNFIwWkNNRnB6Y1RSUU5IZzRNa3hYV25WaFdsRRAB,5,5 miesięcy temu,2026-03-31 +Ci9DQUlRQUNvZENodHljRjlvT21acmQwUTVhbk0yZDFkdExYaEdVRjlzYXpkWVdXYxAB,5,4 miesiące temu,2026-04-30 +Ci9DQUlRQUNvZENodHljRjlvT2t4RlgzUk1YMUZ3ZDBWd1VYcFNlakpJWVdGd2QwRRAB,5,5 miesięcy temu,2026-03-31 +Ci9DQUlRQUNvZENodHljRjlvT2t4RlIzZG9WVEJmU1RkWU1VUjJNemxUU3pWWldXYxAB,5,3 miesiące temu,2026-05-31 +Ci9DQUlRQUNvZENodHljRjlvT2paaWVHaHRTRkZ2WWpsR1JUTlhjak5NTFhRMGRIYxAB,5,5 miesięcy temu,2026-03-31 +Ci9DQUlRQUNvZENodHljRjlvT2xkNlJIVTVXRUpJV1VGYWVVNTFOMGRZZWxCcmRtYxAB,5,5 miesięcy temu,2026-03-31 +Ci9DQUlRQUNvZENodHljRjlvT21KeVZWY3RZbHBJUmtONGR5MUhiMXBOU0ZOVlpGRRAB,5,4 miesiące temu,2026-04-30 +Ci9DQUlRQUNvZENodHljRjlvT21sbWJFRTBUVVJXUm1SbVpsVnFWV1ZtVDBkVFQyYxAB,5,6 miesięcy temu,2026-02-28 +Ci9DQUlRQUNvZENodHljRjlvT2tGNFpqSXpTMHBHWldkeVJHdDBReTFvTUV0a1NGRRAB,5,2 miesiące temu,2026-06-30 +Ci9DQUlRQUNvZENodHljRjlvT21KeVQzZHRjR2xoVGxsdGRsZzJhR2w2V1hCM05VRRAB,5,5 miesięcy temu,2026-03-31 +Ci9DQUlRQUNvZENodHljRjlvT2xORlNXRk1XbE5ZYkZCWVJXZFBVVWxOVmpFNU1rRRAB,5,2 miesiące temu,2026-06-30 +Ci9DQUlRQUNvZENodHljRjlvT25CSlZYb3liVTgxTTBocFpsTjRTbFF0YUc1WmNtYxAB,5,5 miesięcy temu,2026-03-31 +Ci9DQUlRQUNvZENodHljRjlvT21WaGRITnFhVVpEVEd0WVZXSTFWVWMzU1dWdWNYYxAB,5,2 miesiące temu,2026-06-30 +Ci9DQUlRQUNvZENodHljRjlvT21Oa1pGTkJhelYzTUROZmJEUTNaMk5oYzE5NWJIYxAB,5,6 miesięcy temu,2026-02-28 +Ci9DQUlRQUNvZENodHljRjlvT2tNeVJrOTVhSEZqYzNOS1YzaHZZV05sY1ZwdVZGRRAB,1,7 miesięcy temu,2026-01-31 +Ci9DQUlRQUNvZENodHljRjlvT210VFpsODVjWE14YXpodlFXNXNURUo0UjNCalFtYxAB,5,2 miesiące temu,2026-06-30 +Ci9DQUlRQUNvZENodHljRjlvT2pWTU1qTklaMGhFVnpoYWVuSnhRbE16Y2s1bk1FRRAB,5,4 miesiące temu,2026-04-30 +Ci9DQUlRQUNvZENodHljRjlvT21Zd1ZreDZNRWw1TFhkd1ZHUTRUR1YwT0RWbFlYYxAB,1,10 miesięcy temu,2025-10-31 +Ci9DQUlRQUNvZENodHljRjlvT2pkNWVVaEdZM3BmUVdGVWFHZFBNMEoyWDBkWE1tYxAB,5,7 miesięcy temu,2026-01-31 +Ci9DQUlRQUNvZENodHljRjlvT2tWNVFYVmhTSEo2U0VKVWFWbEJNbGxTZDJWRllsRRAB,5,4 miesiące temu,2026-04-30 +Ci9DQUlRQUNvZENodHljRjlvT2xOWU9IWlhhSFp3TjJ4cVNFTmhjVTlIVDBOR1dGRRAB,5,2 miesiące temu,2026-06-30 +Ci9DQUlRQUNvZENodHljRjlvT21oRkxWWXpObkY2ZGsxNkxUVjBkbUZ4WmpaS2JGRRAB,5,miesiąc temu,2026-07-31 +Ci9DQUlRQUNvZENodHljRjlvT2xoTlIwbDJlbFk0WnpGR1ZtRklhamRLVUZWVlRsRRAB,5,6 miesięcy temu,2026-02-28 +ChdDSUhNMG9nS0VJQ0FnSUNwOHNubHZRRRAB,5,3 lata temu,2023-08-31 +Ci9DQUlRQUNvZENodHljRjlvT2tWU1oyRm5kVkpwVFRkUVozSXhRbFJ3Y3kwNFEwRRAB,5,8 miesięcy temu,2025-12-31 +Ci9DQUlRQUNvZENodHljRjlvT2xock16QjJaM2RMV1ZneWJtUm9WVUpyUTNkSmEyYxAB,5,3 miesiące temu,2026-05-31 +Ci9DQUlRQUNvZENodHljRjlvT2pOVGFqRkNTVGxyTW5CalltUmlVM1JHUlRKcFlWRRAB,5,10 miesięcy temu,2025-10-31 +Ci9DQUlRQUNvZENodHljRjlvT2xGMU5WaE5jblppWlVsdWFuWmhhSFUzY1dGS05XYxAB,5,2 miesiące temu,2026-06-30 +Ci9DQUlRQUNvZENodHljRjlvT2taZlUzTTJTRVJ0V1d4T2NtaDNjMk5rZDJwVFJVRRAB,5,8 miesięcy temu,2025-12-31 +Ci9DQUlRQUNvZENodHljRjlvT2pKVFpUVkVNVlowYVcweGVuRnlWRzVWUzFkWWNsRRAB,5,10 miesięcy temu,2025-10-31 +Ci9DQUlRQUNvZENodHljRjlvT2xCamNXcFJRVlZEWW01cmQwOUtORXRrTTJSa1ZWRRAB,5,2 miesiące temu,2026-06-30 +Ci9DQUlRQUNvZENodHljRjlvT2xCemFHUlNNakpoYmxJd1luTnRVRWRXU3pkdVJWRRAB,5,5 miesięcy temu,2026-03-31 +ChdDSUhNMG9nS0VJQ0FnSUNIbGFHQXRnRRAB,1,rok temu,2025-08-31 +Ci9DQUlRQUNvZENodHljRjlvT2t0VGFHZG5XSHBpWlU5eU9WOTRTRUl0TkdOUmVIYxAB,5,3 miesiące temu,2026-05-31 +Ci9DQUlRQUNvZENodHljRjlvT21SUldFcHJlRGxsVEZjd1MzVmlVRVI1YkdwSVVsRRAB,5,9 miesięcy temu,2025-11-30 +Ci9DQUlRQUNvZENodHljRjlvT21jMFR6WXhjVEV6ZDJkWlRGOTRlSEJsY2xGMFVuYxAB,5,5 miesięcy temu,2026-03-31 +Ci9DQUlRQUNvZENodHljRjlvT21aVlJGWmZXVGx4V1dkdWFqUnFMVGh1YVhKdlIwRRAB,5,3 miesiące temu,2026-05-31 +Ci9DQUlRQUNvZENodHljRjlvT21sbVQzWm1jRVZUVUdsTlRGQlBXbnBZTkhoVU1FRRAB,5,9 miesięcy temu,2025-11-30 +Ci9DQUlRQUNvZENodHljRjlvT25WQkxUaFFiVjl3VDJGS1YzZFlORXhpZERKaVgyYxAB,5,5 miesięcy temu,2026-03-31 +Ci9DQUlRQUNvZENodHljRjlvT2kxMFNXTlFUSGgyWm05c09WRjBhelZCVVhGQloyYxAB,5,rok temu,2025-08-31 +ChZDSUhNMG9nS0VJQ0FnTUNRellLYllnEAE,5,rok temu,2025-08-31 +Ci9DQUlRQUNvZENodHljRjlvT21OQ05EUjVSakZyYlhWcWRESk1hMFppVlZsa1YwRRAB,5,miesiąc temu,2026-07-31 +Ci9DQUlRQUNvZENodHljRjlvT2psbFJUZzRRMlpmU0V4a04wNVlNbWhDUzJVM1NGRRAB,5,9 miesięcy temu,2025-11-30 +ChZDSUhNMG9nS0VJQ0FnSUMzbHNpOGZBEAE,5,rok temu,2025-08-31 +Ci9DQUlRQUNvZENodHljRjlvT2s5eU9FMURWMDVCVFZOSlkwUklRbXRzTW5oeFNXYxAB,5,2 miesiące temu,2026-06-30 +Ci9DQUlRQUNvZENodHljRjlvT25odVgyWldjMjlEVjNKV01WSnViRTlSYldaYWJYYxAB,5,2 miesiące temu,2026-06-30 +Ci9DQUlRQUNvZENodHljRjlvT2tJd1pXeHFWemx0Ym5OcVNra3pZbk4zVFVkQmRrRRAB,5,8 miesięcy temu,2025-12-31 +Ci9DQUlRQUNvZENodHljRjlvT2t4SldHRTNOVVI0VFVsMGEwVkNOWFZXVFhrNFVXYxAB,5,4 miesiące temu,2026-04-30 +Ci9DQUlRQUNvZENodHljRjlvT2toc1NXUkJhemN4VjBnd1JETTBUMlYzV1V0RVJsRRAB,5,5 miesięcy temu,2026-03-31 +Ci9DQUlRQUNvZENodHljRjlvT2t0b0xVbEVMVUpFYVcxUk5sRXpjMlE0TFZZMFIwRRAB,5,rok temu,2025-08-31 +ChdDSUhNMG9nS0VJQ0FnSURwNDdQRjhBRRAB,5,3 lata temu,2023-08-31 +ChdDSUhNMG9nS0VJQ0FnTURvbTYzTWxBRRAB,1,rok temu,2025-08-31 +Ci9DQUlRQUNvZENodHljRjlvT25CR1ZVOW1UM2RxTFdsdFIxZEhSRWhrVTJoMWVGRRAB,5,10 miesięcy temu,2025-10-31 +ChdDSUhNMG9nS0VJQ0FnSUQ5NksyTDZRRRAB,5,2 lata temu,2024-08-31 +Ci9DQUlRQUNvZENodHljRjlvT2xRd1ZrTkpRa2RNZG1KQ1dEaFRNME00ZGtsSWQxRRAB,5,7 miesięcy temu,2026-01-31 +ChdDSUhNMG9nS0VJQ0FnSUNYcXR5MDBRRRAB,3,rok temu,2025-08-31 +ChdDSUhNMG9nS0VJQ0FnSUNONFp5UTFRRRAB,5,2 lata temu,2024-08-31 +Ci9DQUlRQUNvZENodHljRjlvT21KUlVXOVNSWHBFYldwNlMxSlNTak5hYzNoTmEyYxAB,5,9 miesięcy temu,2025-11-30 +ChdDSUhNMG9nS0VJQ0FnSURwNE9fNHhnRRAB,5,3 lata temu,2023-08-31 +ChdDSUhNMG9nS0VJQ0FnSURUX09yLXJBRRAB,5,2 lata temu,2024-08-31 +Ci9DQUlRQUNvZENodHljRjlvT205eGNWbExiMWRVTkRWQ2VHSkxTR3cwVEdkd1ozYxAB,5,rok temu,2025-08-31 +ChdDSUhNMG9nS0VJM0h1c1hlbC1iOC1nRRAB,5,rok temu,2025-08-31 +Ci9DQUlRQUNvZENodHljRjlvT2xJMWEwbHFTa3c1UkZkR1prZG9XRmxUZEhJNFJGRRAB,5,9 miesięcy temu,2025-11-30 +Ci9DQUlRQUNvZENodHljRjlvT2xCVWNYbFdka1ZQVkZCVVNHYzVXV0p2VFhvNWRrRRAB,5,9 miesięcy temu,2025-11-30 +ChdDSUhNMG9nS0VJQ0FnSUQ2dXFqUm13RRAB,1,4 lata temu,2022-08-31 +Ci9DQUlRQUNvZENodHljRjlvT2pSdFJuSTVYM1JDWmxaamVWVXhhRVJ0WDFOSmNrRRAB,5,4 miesiące temu,2026-04-30 +ChdDSUhNMG9nS0VJQ0FnSUNHNlBHWXZ3RRAB,5,4 lata temu,2022-08-31 +Ci9DQUlRQUNvZENodHljRjlvT2xCWldtcHljVkU1TVdrM1NubFFOVEpqZERodWEwRRAB,5,5 miesięcy temu,2026-03-31 +Ci9DQUlRQUNvZENodHljRjlvT2xVemNtaGFkbUl0VDFkdlQxcElRVzVqY0ZocWVsRRAB,5,9 miesięcy temu,2025-11-30 +ChZDSUhNMG9nS0VJQ0FnSUR1NTZIb2JBEAE,1,4 lata temu,2022-08-31 +ChdDSUhNMG9nS0VJQ0FnSUN6ek5peGxnRRAB,5,2 lata temu,2024-08-31 +ChdDSUhNMG9nS0VJQ0FnSUNILThuR2dnRRAB,1,rok temu,2025-08-31 +ChdDSUhNMG9nS0VJQ0FnSUNYanUzSXZBRRAB,5,rok temu,2025-08-31 +Ci9DQUlRQUNvZENodHljRjlvT210SlVHdExXRjlvYjFKVFdtTnJVek5uVkc1RVVsRRAB,5,2 miesiące temu,2026-06-30 +Ci9DQUlRQUNvZENodHljRjlvT2tvMmEwUnlNVzV3TUVsTWNsUndaR2hTYUVSSlFXYxAB,5,3 miesiące temu,2026-05-31 +ChdDSUhNMG9nS0VJQ0FnSUNaOTg3QjZRRRAB,1,2 lata temu,2024-08-31 +ChdDSUhNMG9nS0VJQ0FnSUMtMy1IdG1RRRAB,5,3 lata temu,2023-08-31 +ChdDSUhNMG9nS0VJQ0FnSUR1cFo3TnpBRRAB,5,4 lata temu,2022-08-31 +Ci9DQUlRQUNvZENodHljRjlvT21kalVFeExUMGgwWlZsU1F6ZHdWMEZJUkZBMWJGRRAB,5,9 miesięcy temu,2025-11-30 +Ci9DQUlRQUNvZENodHljRjlvT2xsb01FazVlVUpsTFVJMUxVRmZlVkJVTkVaa1UzYxAB,5,miesiąc temu,2026-07-31 +ChZDSUhNMG9nS0VJQ0FnSUR1OGJlSVpBEAE,5,4 lata temu,2022-08-31 +ChdDSUhNMG9nS0VJQ0FnSUNIc0wtcWd3RRAB,1,2 lata temu,2024-08-31 +ChdDSUhNMG9nS0VJQ0FnSUNLajR6VGlRRRAB,5,5 lat temu,2021-08-31 +Ci9DQUlRQUNvZENodHljRjlvT205UGRITXpWbVpmTUROSFlWSXhOelZ2YTBkbVMxRRAB,4,rok temu,2025-08-31 +ChdDSUhNMG9nS0VJQ0FnSUR1NWFDNTJ3RRAB,5,4 lata temu,2022-08-31 +ChZDSUhNMG9nS0VJQ0FnSURPNGJYQ1lREAE,5,4 lata temu,2022-08-31 +ChZDSUhNMG9nS0VJQ0FnSURteTlUX0Z3EAE,5,4 lata temu,2022-08-31 +ChdDSUhNMG9nS0VJQ0FnSUQ2eTkzWmlRRRAB,1,4 lata temu,2022-08-31 +ChdDSUhNMG9nS0VJQ0FnSUMtdnUzMzJBRRAB,3,3 lata temu,2023-08-31 +ChdDSUhNMG9nS0VJQ0FnSURXbmR6bF93RRAB,5,4 lata temu,2022-08-31 +Ci9DQUlRQUNvZENodHljRjlvT25CMWR6RnJURGd4UkhCTWNIbFRTazFZUm5sdE9WRRAB,5,9 miesięcy temu,2025-11-30 +ChZDSUhNMG9nS0VJQ0FnSUNOeXRQNEN3EAE,5,2 lata temu,2024-08-31 +ChdDSUhNMG9nS0VJQ0FnSURQZ3RIODBRRRAB,1,rok temu,2025-08-31 +ChZDSUhNMG9nS0VJQ0FnSUQ2dDVxdlB3EAE,1,4 lata temu,2022-08-31 +Ci9DQUlRQUNvZENodHljRjlvT21oelMyaG5OMlJNYjFGNWMwSjZlRlJEYlhSVFZYYxAB,5,rok temu,2025-08-31 +Ci9DQUlRQUNvZENodHljRjlvT2pReExYcGZaRlZYV25OUlNrUmxTSFl3WlU0d1pHYxAB,5,3 miesiące temu,2026-05-31 +ChdDSUhNMG9nS0VJQ0FnSURhdGNMMW5BRRAB,1,5 lat temu,2021-08-31 +ChZDSUhNMG9nS0VJQ0FnSUQ2Mk4tVVR3EAE,2,4 lata temu,2022-08-31 +Ci9DQUlRQUNvZENodHljRjlvT2xGWGVIcFVhMUZ3VlhkcFRXSTFSRkkyVFdoRmFWRRAB,5,9 miesięcy temu,2025-11-30 +ChdDSUhNMG9nS0VJQ0FnSURPNDllenhBRRAB,1,4 lata temu,2022-08-31 +ChZDSUhNMG9nS0VJQ0FnSURwdVpUSWVBEAE,1,3 lata temu,2023-08-31 +ChZDSUhNMG9nS0VJQ0FnSUR5OGUyb0V3EAE,1,5 lat temu,2021-08-31 +ChZDSUhNMG9nS0VJQ0FnSUQ4dWVMS2ZBEAE,5,6 lat temu,2020-08-31 +ChZDSUhNMG9nS0VJQ0FnSURXMjVmY0VnEAE,1,4 lata temu,2022-08-31 +Ci9DQUlRQUNvZENodHljRjlvT21WclJrWkpXbUppY0hsdGNqZGxSREUxVkcxdU1XYxAB,5,9 miesięcy temu,2025-11-30 +ChdDSUhNMG9nS0VJQ0FnSUNLZ19DMnpBRRAB,5,5 lat temu,2021-08-31 +ChdDSUhNMG9nS0VJQ0FnSURVeE1qYXB3RRAB,1,7 lat temu,2019-08-31 +ChZDSUhNMG9nS0VJQ0FnSUNxeVBqeFpBEAE,1,5 lat temu,2021-08-31 +ChdDSUhNMG9nS0VJQ0FnSURtN2JlbGhnRRAB,1,4 lata temu,2022-08-31 +ChZDSUhNMG9nS0VJQ0FnSUN1NVlEUlZnEAE,1,4 lata temu,2022-08-31 +ChdDSUhNMG9nS0VJQ0FnSUQyLU9uLWhBRRAB,1,4 lata temu,2022-08-31 +Ci9DQUlRQUNvZENodHljRjlvT2tocWIzSXRWa1ZIUmpsSmFFUnJSbVJXYWpoNFZtYxAB,5,7 godzin temu, +Ci9DQUlRQUNvZENodHljRjlvT2tkWmRIbGpRMUJSTjNSelNqazFRblZ6WTBkc1duYxAB,5,7 godzin temu, +Ci9DQUlRQUNvZENodHljRjlvT2xsd1NGRlJXVmg2T0V3MExXeEdNMXByY0V4SFEzYxAB,5,5 dni temu,2026-08-26 +Ci9DQUlRQUNvZENodHljRjlvT2xaMkxUSjNTVWMwVW05SVFWaHhUekpOTWpabGNGRRAB,5,tydzień temu,2026-08-24 +Ci9DQUlRQUNvZENodHljRjlvT2pGNU1WcDFTR3g1YzFsTk5WUXRTR0pyYkVGdlFYYxAB,5,tydzień temu,2026-08-24 +Ci9DQUlRQUNvZENodHljRjlvT21aQ1pWVjZOM0JRVmxSRE9YTmhTMmhSWDBwaGRHYxAB,5,tydzień temu,2026-08-24 +Ci9DQUlRQUNvZENodHljRjlvT21WMVNFZE9WRXBJYW5rMU1ITlpjbkoyVVVWcGMzYxAB,5,2 tygodnie temu, +Ci9DQUlRQUNvZENodHljRjlvT21KTmIxTlViRE51TFhsdlJEQmlaR2h3TkhWRVZGRRAB,5,2 tygodnie temu, +Ci9DQUlRQUNvZENodHljRjlvT2tsRlQzYzFlalJaVFY5b05FWkRhMVJKYVU5WFlXYxAB,5,2 tygodnie temu, +Ci9DQUlRQUNvZENodHljRjlvT21sbFEzRnNlVlJEWHpKQmIzTmpUVEppZUU1b1dHYxAB,5,miesiąc temu,2026-07-31 +Ci9DQUlRQUNvZENodHljRjlvT21SSVRWcExXVXMxTUhrM2RXUk5Nekp1VkhaaWVYYxAB,5,miesiąc temu,2026-07-31 +Ci9DQUlRQUNvZENodHljRjlvT21vME1VeFBRVVE0VTJoRWIyOTROV2MyZEdkaVJYYxAB,5,miesiąc temu,2026-07-31 +Ci9DQUlRQUNvZENodHljRjlvT25KUWJVbG1hRE0yTmxCS01UaE1WamR0TVVSWGFYYxAB,5,miesiąc temu,2026-07-31 +Ci9DQUlRQUNvZENodHljRjlvT2sxU2VsSjRkVVJLWDBsUGVEQjVTalpTZEVad1JtYxAB,5,miesiąc temu,2026-07-31 +Ci9DQUlRQUNvZENodHljRjlvT2tscVFYVkRZMngwTW00dFJGVkxRV3BMU1UxM05tYxAB,5,miesiąc temu,2026-07-31 +Ci9DQUlRQUNvZENodHljRjlvT2poQlpqRkphRXh4Um01SlRIbEVkRjgxUWxCVE0xRRAB,5,miesiąc temu,2026-07-31 +Ci9DQUlRQUNvZENodHljRjlvT21WbWJqZHdOVGRFU21kck9HcE9hSE5PWDBrMFprRRAB,5,miesiąc temu,2026-07-31 +Ci9DQUlRQUNvZENodHljRjlvT2tVM1ZERnNRbTVMU21KUE1FNHlNa3hDYjBSQ01VRRAB,5,miesiąc temu,2026-07-31 +Ci9DQUlRQUNvZENodHljRjlvT2pWMFUxOU5lVFJJTTNWUmRYQndZelV4V2kxelVWRRAB,5,Edytowano miesiąc temu,2026-07-31 +Ci9DQUlRQUNvZENodHljRjlvT2xkbU5WVTBZMjVXVmtoMWVtRndOMGg0U200NFQzYxAB,5,2 miesiące temu,2026-06-30 +Ci9DQUlRQUNvZENodHljRjlvT2t4MFEzcDJRa3A2WDFkcWJsWllORjloWlVkSWIxRRAB,1,2 miesiące temu,2026-06-30 +Ci9DQUlRQUNvZENodHljRjlvT2s5S1MzaHJjak14Um5aRU1qTlFWVWRPV2toRVRXYxAB,5,4 miesiące temu,2026-04-30 +Ci9DQUlRQUNvZENodHljRjlvT25NeFoxWmpTRmR6U0dsbGJWOTRiMkl6UWs0eFQzYxAB,5,5 miesięcy temu,2026-03-31 +ChdDSUhNMG9nS0VJQ0FnSUMyM0phcTVRRRAB,3,4 lata temu,2022-08-31 +Ci9DQUlRQUNvZENodHljRjlvT2xKWE5qUkhla05rWnpsamFsTnliVXBqVUdSMlFYYxAB,5,4 miesiące temu,2026-04-30 +Ci9DQUlRQUNvZENodHljRjlvT2xNek1GZHhlVWhMZDI1blZtcGZjbGhzYm1neVNIYxAB,5,9 miesięcy temu,2025-11-30 +Ci9DQUlRQUNvZENodHljRjlvT205MVZUVnpTMkpRY3psVVRrRmlOM2cxVWxCUVZIYxAB,5,2 dni temu,2026-08-29 +Ci9DQUlRQUNvZENodHljRjlvT2twTVZqVnpNa2Q1Vm5Sek1uWk9ORTVoTW1Wb2FrRRAB,5,6 dni temu,2026-08-25 +Ci9DQUlRQUNvZENodHljRjlvT25aUVZGbDRTbWRzY1hWdGNGQldabkEwWjNOWVYyYxAB,5,tydzień temu,2026-08-24 +Ci9DQUlRQUNvZENodHljRjlvT21sclEyWlhhVE5IZEZaak1HdHljV1V3VDIxbU1uYxAB,5,3 tygodnie temu, +Ci9DQUlRQUNvZENodHljRjlvT214TVUyOXJTamRMTVZFeE9FMUhTRlI2UTNaMlNWRRAB,5,miesiąc temu,2026-07-31 +Ci9DQUlRQUNvZENodHljRjlvT2xacWNYUXpaVVZzYTBsV1REbGxjR2xVTWxsT2EyYxAB,5,miesiąc temu,2026-07-31 +Ci9DQUlRQUNvZENodHljRjlvT21acVJuTkpNekZaUmtkaE5HZFBUV1pMTm13elNHYxAB,1,2 miesiące temu,2026-06-30 +Ci9DQUlRQUNvZENodHljRjlvT25kSVprWkxVbk5RTlVjeGNWUnJjR3hVTlhwWlRHYxAB,5,2 miesiące temu,2026-06-30 +Ci9DQUlRQUNvZENodHljRjlvT25KRFMwSlBVbFl6U0dFNWRFUXRiR1V6UjNZd05XYxAB,5,4 miesiące temu,2026-04-30 +Ci9DQUlRQUNvZENodHljRjlvT2pkbU5rWmpjRGh1ZWtWMFltVjZNbFZGTUVOTlVWRRAB,5,4 miesiące temu,2026-04-30 +Ci9DQUlRQUNvZENodHljRjlvT2t4T1NraE5hWFZ2WW5KbU1tUmhNbHBDVUhWV1NGRRAB,5,9 miesięcy temu,2025-11-30 +Ci9DQUlRQUNvZENodHljRjlvT21aWE1XOXRSMjlQTW5KdFdFNXVSak5HUlZwRlFtYxAB,5,9 miesięcy temu,2025-11-30 +ChZDSUhNMG9nS0VJQ0FnSUQ3cGFTMGJnEAE,5,2 lata temu,2024-08-31 +ChZDSUhNMG9nS0VJQ0FnSUMxaDRfWkpnEAE,5,2 lata temu,2024-08-31 +ChZDSUhNMG9nS0VJQ0FnSUMtamNPN0t3EAE,5,3 lata temu,2023-08-31 +ChdDSUhNMG9nS0VJQ0FnSURXeU9iVHl3RRAB,5,4 lata temu,2022-08-31 +ChZDSUhNMG9nS0VJQ0FnSURHejdiREhBEAE,5,4 lata temu,2022-08-31 +ChdDSUhNMG9nS0VJQ0FnSUNHallpV3JRRRAB,4,4 lata temu,2022-08-31 +ChZDSUhNMG9nS0VJQ0FnSUNxdm96Yk5REAE,5,5 lat temu,2021-08-31 diff --git a/reviews.py b/reviews.py new file mode 100644 index 0000000..40e5a9b --- /dev/null +++ b/reviews.py @@ -0,0 +1,585 @@ +import re +import time +from datetime import datetime, timedelta + +import pandas as pd +from dateutil.relativedelta import relativedelta +from playwright.sync_api import sync_playwright + + +# ============================================================ +# USTAWIENIA +# ============================================================ + +URL = "https://maps.app.goo.gl/USRCto35jhoVtmj4A" + +HEADLESS = False + +# Ile kolejnych scrolli bez nowych opinii oznacza koniec +MAX_EMPTY_SCROLLS = 8 + +# Czas między scrollami +SCROLL_DELAY = 1.2 + + +# ============================================================ +# PARSOWANIE DATY GOOGLE +# ============================================================ + +def parse_google_date(text: str): + if not text: + return None + + text = text.strip().lower() + now = datetime.now() + + if text == "dzisiaj": + return now.date() + + if text == "wczoraj": + return (now - timedelta(days=1)).date() + + patterns = [ + (r"(\d+)\s+dni?\s+temu", lambda n: now - timedelta(days=n)), + (r"(\d+)\s+dzień\s+temu", lambda n: now - timedelta(days=n)), + (r"(\d+)\s+tygodni?\s+temu", lambda n: now - timedelta(weeks=n)), + ( + r"(\d+)\s+miesi(?:ąc|ące|ęcy)\s+temu", + lambda n: now - relativedelta(months=n) + ), + ( + r"(\d+)\s+lat(?:a)?\s+temu", + lambda n: now - relativedelta(years=n) + ), + ] + + for pattern, func in patterns: + match = re.search(pattern, text) + + if match: + return func(int(match.group(1))).date() + + if "tydzień temu" in text: + return (now - timedelta(weeks=1)).date() + + if "miesiąc temu" in text: + return (now - relativedelta(months=1)).date() + + if "rok temu" in text: + return (now - relativedelta(years=1)).date() + + return None + + +# ============================================================ +# COOKIES +# ============================================================ + +def accept_cookies(page): + labels = [ + "Zaakceptuj wszystko", + "Accept all", + "Odrzuć wszystko", + "Reject all", + ] + + for label in labels: + try: + button = page.get_by_role( + "button", + name=re.compile(label, re.I) + ) + + if button.count() and button.first.is_visible(): + button.first.click(timeout=3000) + time.sleep(2) + return + except: + pass + + +# ============================================================ +# OTWIERANIE PANELU OPINII +# ============================================================ + +def open_reviews_panel(page): + print("Szukam właściwego przycisku z opiniami...") + + buttons = page.locator("button") + + for i in range(buttons.count()): + try: + button = buttons.nth(i) + + if not button.is_visible(): + continue + + aria = button.get_attribute("aria-label") or "" + + try: + text = button.inner_text(timeout=500) + except: + text = "" + + label = f"{aria} {text}".strip() + + # Musi zawierać liczbę oraz opini/recenz/review + if ( + re.search(r"\d+", label) + and re.search(r"(opini|recenz|reviews?)", label, re.I) + ): + print("Kandydat:", repr(label[:200])) + + try: + button.scroll_into_view_if_needed() + time.sleep(0.5) + button.click(timeout=3000) + + print("Kliknięto:", repr(label[:200])) + + time.sleep(3) + + cards = page.locator('div[data-review-id]') + + if cards.count() > 5: + print("Panel opinii otwarty.") + return True + + except Exception as e: + print("Nie udało się kliknąć:", e) + + except: + continue + + # Fallback dla role=button + print("Próbuję alternatywnego selektora...") + + candidates = page.locator( + '[role="button"][aria-label*="opini"], ' + '[role="button"][aria-label*="recenz"], ' + '[role="button"][aria-label*="review"]' + ) + + for i in range(candidates.count()): + try: + el = candidates.nth(i) + + if not el.is_visible(): + continue + + aria = el.get_attribute("aria-label") or "" + + if not re.search(r"\d+", aria): + continue + + print("Klikam:", repr(aria)) + + el.scroll_into_view_if_needed() + el.click(timeout=3000) + + time.sleep(3) + + cards = page.locator('div[data-review-id]') + + if cards.count() > 5: + print("Panel opinii otwarty.") + return True + + except: + continue + + return False + + +# ============================================================ +# ZNAJDOWANIE SCROLLOWANEGO PANELU +# ============================================================ + +def get_scrollable_parent(card): + try: + return card.evaluate_handle(""" + el => { + let p = el.parentElement; + + while (p) { + const style = getComputedStyle(p); + + if ( + (style.overflowY === 'auto' || + style.overflowY === 'scroll') && + p.scrollHeight > p.clientHeight + ) { + return p; + } + + p = p.parentElement; + } + + return null; + } + """) + except: + return None + + +# ============================================================ +# OCENA GWIAZDKOWA +# ============================================================ + +def extract_rating(card): + selectors = [ + '[role="img"][aria-label*="gwiazd"]', + '[aria-label*="gwiazd"]', + '[role="img"][aria-label*="star"]', + '[aria-label*="star"]', + 'span.kvMYJc[aria-label]', + ] + + for selector in selectors: + try: + el = card.locator(selector).first + + if not el.count(): + continue + + label = el.get_attribute("aria-label") or "" + + match = re.search(r"([1-5](?:[.,]\d+)?)", label) + + if match: + value = match.group(1).replace(",", ".") + return int(float(value)) + + except: + pass + + return None + + +# ============================================================ +# DATA OPINII +# ============================================================ + +def extract_date_text(card): + selectors = [ + ".rsqaWe", + "span.rsqaWe", + ] + + for selector in selectors: + try: + el = card.locator(selector).first + + if el.count(): + text = el.inner_text().strip() + + if text: + return text + + except: + pass + + return None + + +# ============================================================ +# ŁADOWANIE WSZYSTKICH OPINII +# ============================================================ + +def count_unique_reviews(cards): + """Policz opinie, a nie zduplikowane elementy DOM Google Maps.""" + try: + return cards.evaluate_all(""" + elements => new Set( + elements + .map(el => el.getAttribute('data-review-id')) + .filter(Boolean) + ).size + """) + except: + return 0 + + +def load_all_reviews(page): + previous_count = 0 + empty_scrolls = 0 + + print("\nŁadowanie opinii...") + + while empty_scrolls < MAX_EMPTY_SCROLLS: + cards = page.locator('div[data-review-id]') + count = count_unique_reviews(cards) + + print( + f"\rZaładowane opinie: {count}", + end="", + flush=True + ) + + if count > previous_count: + previous_count = count + empty_scrolls = 0 + else: + empty_scrolls += 1 + + if count == 0: + page.mouse.wheel(0, 5000) + time.sleep(SCROLL_DELAY) + continue + + last_card = cards.last + + scrollable = get_scrollable_parent(last_card) + + if scrollable: + try: + scrollable.evaluate(""" + el => { + el.scrollTop = el.scrollHeight; + } + """) + except: + page.mouse.wheel(0, 5000) + else: + try: + last_card.scroll_into_view_if_needed() + except: + page.mouse.wheel(0, 5000) + + time.sleep(SCROLL_DELAY) + + print() + + +# ============================================================ +# ZBIERANIE DANYCH +# ============================================================ + +def extract_reviews(page): + results = {} + + cards = page.locator('div[data-review-id]') + + unique_count = count_unique_reviews(cards) + print(f"\nZbieram dane z {unique_count} unikalnych opinii...") + + for i in range(cards.count()): + card = cards.nth(i) + + try: + review_id = card.get_attribute("data-review-id") + + if not review_id: + continue + + # Deduplikacja + if review_id in results: + continue + + rating = extract_rating(card) + + if rating is None: + continue + + date_raw = extract_date_text(card) + date_approx = parse_google_date(date_raw) + + results[review_id] = { + "review_id": review_id, + "rating": rating, + "date_raw": date_raw, + "date_approx": date_approx, + } + + except Exception as e: + print(f"Błąd przy opinii #{i}: {e}") + + return pd.DataFrame(results.values()) + + +# ============================================================ +# PODSUMOWANIE +# ============================================================ + +def print_summary(df): + print("\n==============================") + print("ŁĄCZNIE") + print("==============================") + print(len(df)) + + print("\n==============================") + print("ROZKŁAD GWIAZDEK") + print("==============================") + + distribution = ( + df["rating"] + .value_counts() + .reindex([5, 4, 3, 2, 1], fill_value=0) + ) + + total = len(df) + + for stars, count in distribution.items(): + percent = (count / total * 100) if total else 0 + + print( + f"{stars}★: " + f"{count:>4} " + f"({percent:>5.1f}%)" + ) + + print("\n==============================") + print("ŚREDNIA") + print("==============================") + + if len(df): + print(round(df["rating"].mean(), 3)) + else: + print("Brak danych") + + +# ============================================================ +# FILTROWANIE PO OKRESIE +# ============================================================ + +def print_period_summary(df, date_from=None, date_to=None): + temp = df.copy() + + temp["date_approx"] = pd.to_datetime( + temp["date_approx"], + errors="coerce" + ) + + if date_from: + temp = temp[ + temp["date_approx"] >= pd.to_datetime(date_from) + ] + + if date_to: + temp = temp[ + temp["date_approx"] <= pd.to_datetime(date_to) + ] + + print("\n==============================") + print(f"OKRES: {date_from} -> {date_to}") + print("==============================") + + if temp.empty: + print("Brak opinii w tym okresie.") + return + + distribution = ( + temp["rating"] + .value_counts() + .reindex([5, 4, 3, 2, 1], fill_value=0) + ) + + print(f"Liczba opinii: {len(temp)}") + + for stars, count in distribution.items(): + print(f"{stars}★: {count}") + + print("Średnia:", round(temp["rating"].mean(), 3)) + + +# ============================================================ +# MAIN +# ============================================================ + +def main(): + with sync_playwright() as p: + browser = p.chromium.launch( + headless=HEADLESS + ) + + context = browser.new_context( + locale="pl-PL", + viewport={ + "width": 1400, + "height": 900, + }, + ) + + page = context.new_page() + + print("Otwieram Google Maps...") + + page.goto( + URL, + wait_until="domcontentloaded", + timeout=60_000 + ) + + time.sleep(3) + + accept_cookies(page) + + opened = open_reviews_panel(page) + + if not opened: + print( + "\nNie udało się automatycznie otworzyć panelu opinii." + ) + print( + "Kliknij ręcznie liczbę opinii / „Więcej opinii”." + ) + + input( + "Gdy panel będzie otwarty, naciśnij ENTER..." + ) + + time.sleep(2) + + cards = page.locator('div[data-review-id]') + + print( + "Opinie widoczne po otwarciu panelu:", + count_unique_reviews(cards) + ) + + if count_unique_reviews(cards) < 5: + print( + "\nUWAGA: panel opinii prawdopodobnie " + "nie został poprawnie otwarty." + ) + + input( + "Otwórz ręcznie wszystkie opinie " + "i naciśnij ENTER..." + ) + + load_all_reviews(page) + + df = extract_reviews(page) + + browser.close() + + if df.empty: + print("\nNie znaleziono żadnych opinii.") + return + + df.to_csv( + "google_reviews_ratings.csv", + index=False, + encoding="utf-8-sig" + ) + + print_summary(df) + + print("\nZapisano:") + print("google_reviews_ratings.csv") + + # ======================================================== + # PRZYKŁADOWE FILTROWANIE + # Odkomentuj jeśli chcesz + # ======================================================== + + # print_period_summary( + # df, + # "2025-01-01", + # "2025-12-31" + # ) + + +if __name__ == "__main__": + main()