fix: captura precisa de notas e total de comentarios reais por estabelecimento no Google Maps

This commit is contained in:
2026-08-26 18:16:12 -03:00
parent ea91767d8b
commit 52c2ba90d6
+39 -47
View File
@@ -16,7 +16,7 @@ class ScraperService:
def _extract_card_rating_and_reviews(card, detail_panel=None) -> tuple:
"""
Extrai a nota (rating) e a quantidade total de avaliações do container
exclusivo do estabelecimento, com fallback para o painel de detalhes.
exclusivo do estabelecimento (ex: "4,5 estrelas 56.778 comentários" ou "4,5(56.778)").
"""
rating = 0.0
total_avaliacoes = 0
@@ -38,59 +38,51 @@ class ScraperService:
except Exception:
container = card
# 2. Procurar aria-labels no container do lugar
aria_elems = container.query_selector_all("[aria-label*='estrela'], [aria-label*='star'], [aria-label*='avaliaç']")
# 2. Checar ARIA labels do container (ex: "4,5 estrelas 56.778 comentários")
aria_elems = container.query_selector_all("[aria-label]")
container_label = container.get_attribute("aria-label") or ""
card_label = card.get_attribute("aria-label") or ""
combined_aria = " ".join([e.get_attribute("aria-label") or "" for e in aria_elems] + [container_label, card_label])
all_labels = [e.get_attribute("aria-label") or "" for e in aria_elems] + [container_label, card_label]
r_match = re.search(r'([1-5][.,]\d)\s*(?:estrelas?|stars?)', combined_aria, re.IGNORECASE)
if r_match:
try:
rating = float(r_match.group(1).replace(',', '.'))
except ValueError:
pass
rev_match = re.search(r'([\d.,]+)\s*avaliaç', combined_aria, re.IGNORECASE)
if rev_match:
try:
total_avaliacoes = int(re.sub(r'\D', '', rev_match.group(1)))
except ValueError:
pass
# 3. Ler o texto bruto (inner_text) do container do lugar
container_text = container.inner_text() or card.inner_text() or ""
if rating == 0.0:
# O rating no card do Maps vem como "4,8 (120)" ou "4.8 ★"
m = re.search(r'([1-5][.,]\d)\s*(?:\(|\s*★|\s*estrelas?)', container_text, re.IGNORECASE)
if not m:
m = re.search(r'([1-5][.,]\d)', container_text)
for lbl in all_labels:
m = re.search(r'([1-5][.,]\d)\s*(?:estrelas?|stars?)\s*([\d.,]+)', lbl, re.IGNORECASE)
if m:
try:
val = float(m.group(1).replace(',', '.'))
r_val = float(m.group(1).replace(',', '.'))
c_val = int(re.sub(r'\D', '', m.group(2)))
if 1.0 <= r_val <= 5.0:
rating = r_val
total_avaliacoes = c_val
return rating, total_avaliacoes
except ValueError:
pass
# 3. Ler linhas de texto do container (ex: "4,5(56.778)" ou "3,8(360)")
container_text = container.inner_text() or card.inner_text() or ""
for line in container_text.split('\n'):
line = line.strip()
# Testar formato oficial "4,5(56.778)"
m_line = re.search(r'([1-5][.,]\d)\s*\(([\d.,]+)\)', line)
if m_line:
try:
rating = float(m_line.group(1).replace(',', '.'))
total_avaliacoes = int(re.sub(r'\D', '', m_line.group(2)))
return rating, total_avaliacoes
except ValueError:
pass
if rating == 0.0:
m_rat = re.search(r'^([1-5][.,]\d)', line)
if m_rat:
try:
val = float(m_rat.group(1).replace(',', '.'))
if 1.0 <= val <= 5.0:
rating = val
except ValueError:
pass
if total_avaliacoes == 0:
# O total de avaliações no card vem entre parênteses "(120)" ou "(1.250)"
m = re.search(r'\(([\d.,]+)\)', container_text)
if m:
try:
total_avaliacoes = int(re.sub(r'\D', '', m.group(1)))
except ValueError:
pass
else:
m = re.search(r'([\d.,]+)\s*avaliaç', container_text, re.IGNORECASE)
if m:
try:
total_avaliacoes = int(re.sub(r'\D', '', m.group(1)))
except ValueError:
pass
# 4. Fallback para o detail_panel se o container do card não possuir a nota
# 4. Fallback no detail_panel da empresa clicada caso o card na lista não exiba a contagem
if (rating == 0.0 or total_avaliacoes == 0) and detail_panel:
detail_text = detail_panel.inner_text() or ""
@@ -106,14 +98,14 @@ class ScraperService:
pass
if total_avaliacoes == 0:
rev_elem = detail_panel.query_selector("button[aria-label*='avaliaç'], span[aria-label*='avaliaç']")
rev_elem = detail_panel.query_selector("button[aria-label*='avaliaç'], button[aria-label*='comentário'], span[aria-label*='comentário']")
if rev_elem:
rv_txt = rev_elem.get_attribute("aria-label") or rev_elem.inner_text()
rv_m = re.search(r'([\d.,]+)\s*avaliaç', rv_txt, re.IGNORECASE)
rv_m = re.search(r'([\d.,]+)\s*(?:comentários|avaliações)', rv_txt, re.IGNORECASE)
if rv_m:
try:
cnt = int(re.sub(r'\D', '', rv_m.group(1)))
if cnt < 20000:
if cnt < 200000:
total_avaliacoes = cnt
except ValueError:
pass