fix: captura precisa de notas e total de comentarios reais por estabelecimento no Google Maps
This commit is contained in:
@@ -16,7 +16,7 @@ class ScraperService:
|
|||||||
def _extract_card_rating_and_reviews(card, detail_panel=None) -> tuple:
|
def _extract_card_rating_and_reviews(card, detail_panel=None) -> tuple:
|
||||||
"""
|
"""
|
||||||
Extrai a nota (rating) e a quantidade total de avaliações do container
|
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
|
rating = 0.0
|
||||||
total_avaliacoes = 0
|
total_avaliacoes = 0
|
||||||
@@ -38,59 +38,51 @@ class ScraperService:
|
|||||||
except Exception:
|
except Exception:
|
||||||
container = card
|
container = card
|
||||||
|
|
||||||
# 2. Procurar aria-labels no container do lugar
|
# 2. Checar ARIA labels do container (ex: "4,5 estrelas 56.778 comentários")
|
||||||
aria_elems = container.query_selector_all("[aria-label*='estrela'], [aria-label*='star'], [aria-label*='avaliaç']")
|
aria_elems = container.query_selector_all("[aria-label]")
|
||||||
container_label = container.get_attribute("aria-label") or ""
|
container_label = container.get_attribute("aria-label") or ""
|
||||||
card_label = card.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)
|
for lbl in all_labels:
|
||||||
if r_match:
|
m = re.search(r'([1-5][.,]\d)\s*(?:estrelas?|stars?)\s*([\d.,]+)', lbl, re.IGNORECASE)
|
||||||
try:
|
if m:
|
||||||
rating = float(r_match.group(1).replace(',', '.'))
|
try:
|
||||||
except ValueError:
|
r_val = float(m.group(1).replace(',', '.'))
|
||||||
pass
|
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
|
||||||
|
|
||||||
rev_match = re.search(r'([\d.,]+)\s*avaliaç', combined_aria, re.IGNORECASE)
|
# 3. Ler linhas de texto do container (ex: "4,5(56.778)" ou "3,8(360)")
|
||||||
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 ""
|
container_text = container.inner_text() or card.inner_text() or ""
|
||||||
|
for line in container_text.split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
if rating == 0.0:
|
# Testar formato oficial "4,5(56.778)"
|
||||||
# O rating no card do Maps vem como "4,8 (120)" ou "4.8 ★"
|
m_line = re.search(r'([1-5][.,]\d)\s*\(([\d.,]+)\)', line)
|
||||||
m = re.search(r'([1-5][.,]\d)\s*(?:\(|\s*★|\s*estrelas?)', container_text, re.IGNORECASE)
|
if m_line:
|
||||||
if not m:
|
|
||||||
m = re.search(r'([1-5][.,]\d)', container_text)
|
|
||||||
if m:
|
|
||||||
try:
|
try:
|
||||||
val = float(m.group(1).replace(',', '.'))
|
rating = float(m_line.group(1).replace(',', '.'))
|
||||||
if 1.0 <= val <= 5.0:
|
total_avaliacoes = int(re.sub(r'\D', '', m_line.group(2)))
|
||||||
rating = val
|
return rating, total_avaliacoes
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if total_avaliacoes == 0:
|
if rating == 0.0:
|
||||||
# O total de avaliações no card vem entre parênteses "(120)" ou "(1.250)"
|
m_rat = re.search(r'^([1-5][.,]\d)', line)
|
||||||
m = re.search(r'\(([\d.,]+)\)', container_text)
|
if m_rat:
|
||||||
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:
|
try:
|
||||||
total_avaliacoes = int(re.sub(r'\D', '', m.group(1)))
|
val = float(m_rat.group(1).replace(',', '.'))
|
||||||
|
if 1.0 <= val <= 5.0:
|
||||||
|
rating = val
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
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:
|
if (rating == 0.0 or total_avaliacoes == 0) and detail_panel:
|
||||||
detail_text = detail_panel.inner_text() or ""
|
detail_text = detail_panel.inner_text() or ""
|
||||||
|
|
||||||
@@ -106,14 +98,14 @@ class ScraperService:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if total_avaliacoes == 0:
|
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:
|
if rev_elem:
|
||||||
rv_txt = rev_elem.get_attribute("aria-label") or rev_elem.inner_text()
|
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:
|
if rv_m:
|
||||||
try:
|
try:
|
||||||
cnt = int(re.sub(r'\D', '', rv_m.group(1)))
|
cnt = int(re.sub(r'\D', '', rv_m.group(1)))
|
||||||
if cnt < 20000:
|
if cnt < 200000:
|
||||||
total_avaliacoes = cnt
|
total_avaliacoes = cnt
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
Reference in New Issue
Block a user