[Python] Como evitar o erro 429 em requisições assíncronas em Python? [fechada]

Discussão em 'Python' iniciado por Stack, Outubro 23, 2024.

  1. Stack

    Stack Membro Participativo

    Estou desenvolvendo um script em Python que faz requisições assíncronas a uma API, mas estou enfrentando o erro 429 Too Many Requests. O site limita o número de requisições, e estou tentando burlar isso usando headers aleatórios e trocando de IP, mas mesmo assim algumas requisições repetem números aleatórios e continuam retornando o erro.

    Aqui está um trecho do meu código:

    @app.route('/imoveis', methods=['POST'])
    async def listar_imoveis():
    with lock:
    data = request.get_json()

    estado = data.get('estado')
    cidades = data.get('cidades', [])
    modalidade_venda = data.get('modalidadeVenda')
    tipo_imovel_filtro = data.get('tipoImovel')
    page = data.get('page', 1)
    limit = 15

    minimo_porcentagem = data.get('minimoPorcentagem')
    maximo_porcentagem = data.get('maximoPorcentagem')

    if not estado:
    return jsonify({'error': 'O parâmetro "estado" é obrigatório.'}), 400

    try:
    df = baixar_e_processar_csv(estado)

    # Aplicando filtro de cidades
    if cidades:
    cidades_normalizadas = [cidade.upper() for cidade in cidades]
    df = df[df['Cidade'].str.upper().isin(cidades_normalizadas)]

    # Aplicando filtro de modalidade de venda
    modalidades_validas = [
    "Leilão SFI - Edital Único",
    "Licitação Aberta",
    "Venda Direta Online",
    "Venda Online"
    ]

    if modalidade_venda:
    modalidade_venda_normalizada = remover_acentos(modalidade_venda.strip().lower())

    if modalidade_venda_normalizada not in [remover_acentos(mod.lower()) for mod in modalidades_validas]:
    return jsonify({'error': 'Modalidade de venda inválida. As opções válidas são: ' + ', '.join(modalidades_validas)}), 400

    if 'Modalidade de venda' in df.columns:
    df = df[df['Modalidade de venda'].str.lower().apply(remover_acentos).str.contains(modalidade_venda_normalizada, na=False)]

    if df.empty:
    return jsonify({'error': 'Nenhum imóvel encontrado com os critérios especificados.'}), 404

    # Identificação do tipo de imóvel diretamente no DataFrame
    df = df.copy()
    df.loc[:, 'tipoImovel'] = df['Descrição'].str.split(',').str[0].str.strip()


    # Aplicando filtro de tipo de imóvel, se fornecido
    if tipo_imovel_filtro:
    df = df[df['tipoImovel'] == tipo_imovel_filtro]

    # Conversão para dicionário após processamento no DataFrame
    imoveis_dict = df.to_dict(orient='records')

    erro_count = 0

    # Extração dos detalhes do imóvel em paralelo
    detalhes_imoveis = await asyncio.gather(
    *(extrair_detalhes_imovel(imovel.get('Link de acesso')) for imovel in imoveis_dict if imovel.get('Link de acesso'))
    )

    # Atualiza o dicionário de imóveis com os detalhes extraídos
    for imovel, detalhes in zip(imoveis_dict, detalhes_imoveis):
    if detalhes:
    imovel.update(detalhes)

    # Convertendo para DataFrame novamente após a extração dos detalhes
    detalhes_df = pd.DataFrame(imoveis_dict)

    # Filtrando pela porcentagem, caso seja aplicável
    if 'porcentagemSegundoLeilaoAvaliacao' in detalhes_df.columns and minimo_porcentagem is not None and maximo_porcentagem is not None:
    # Remover '%' e filtrar valores válidos
    detalhes_df['porcentagemSegundoLeilaoAvaliacao'] = detalhes_df['porcentagemSegundoLeilaoAvaliacao'].str.replace('%', '')

    # Filtrando apenas valores que são numéricos
    detalhes_df = detalhes_df[detalhes_df['porcentagemSegundoLeilaoAvaliacao'] != 'Valor não encontrado.']

    # Converter para float
    detalhes_df['porcentagemSegundoLeilaoAvaliacao'] = pd.to_numeric(detalhes_df['porcentagemSegundoLeilaoAvaliacao'], errors='coerce')

    # Aplicando o filtro, ignorando valores NaN resultantes da conversão
    detalhes_df = detalhes_df[detalhes_df['porcentagemSegundoLeilaoAvaliacao'].between(minimo_porcentagem, maximo_porcentagem)]

    # Paginação após os filtros
    total_imoveis = len(detalhes_df)
    total_pages = (total_imoveis // limit) + (1 if total_imoveis % limit > 0 else 0)

    # Aplicando limite e offset para paginação
    start = (page - 1) * limit
    end = start + limit
    imoveis_paginados = detalhes_df.iloc[start:end]

    print(f"Número de imóveis com erro: {erro_count}")

    return app.response_class(
    response=json.dumps({
    'imoveis': imoveis_paginados.to_dict(orient='records'),
    'total_imoveis': total_imoveis,
    'pagina_atual': page,
    'imoveis_por_pagina': limit,
    'total_paginas': total_pages
    }, ensure_ascii=False, indent=4),
    status=200,
    mimetype='application/json; charset=utf-8'
    )

    except Exception as e:
    return jsonify({'error': str(e)}), 500


    Funcao que faz as requisições:

    async def extrair_detalhes_imovel(link):
    global erro_count
    try:
    user_agent = f'Mozilla/5.0 (Windows NT {random.randint(5, 12)}.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{random.randint(40, 120)}.0.{random.randint(0, 10000)}.{random.randint(0, 200)} Safari/537.36'
    headers = {
    'User-Agent': user_agent,
    'Accept-Language': f'en-US,en;q={random.uniform(0.5, 1.0)}', # Variável
    'Accept-Encoding': 'gzip, deflate, br',
    'Connection': 'keep-alive',
    }

    async with aiohttp.ClientSession() as session:
    async with session.get(link, headers=headers) as resposta:
    resposta.raise_for_status()
    soup = BeautifulSoup(await resposta.text(), 'html.parser')

    dados_imovel_div = soup.find('div', id='dadosImovel')
    if not dados_imovel_div:
    return {
    'minimoVendaPrimeiroLeilao': "Valor não encontrado.",
    'minimoVendaSegundoLeilao': "Valor não encontrado.",
    'dataSegundoLeilao': "Data não encontrada.",
    'linkImagem': "Imagem não encontrada.",
    'diferencaSegundoLeilaoAvaliacao': 'Valor não encontrado.',
    'porcentagemSegundoLeilaoAvaliacao': 'Valor não encontrado.',
    }

    texto = dados_imovel_div.get_text()

    valor_primeiro_leilao = re.search(r'Valor mínimo de venda 1º Leilão:\s*(R\$ \d{1,3}(?:\.\d{3})*,\d{2})', texto)
    valor_segundo_leilao = re.search(r'Valor mínimo de venda 2º Leilão:\s*(R\$ \d{1,3}(?:\.\d{3})*,\d{2})', texto)
    data_segundo_leilao = re.search(r'Data do 2º Leilão - (\d{2}/\d{2}/\d{4}) - \d{2}h\d{2}', texto)
    valor_avaliacao = re.search(r'Valor de avaliação:\s*(R\$ \d{1,3}(?:\.\d{3})*,\d{2})', texto)

    imagem_tag = soup.find('img', id='preview')
    link_imagem = None
    if imagem_tag and 'src' in imagem_tag.attrs:
    src = imagem_tag['src']
    link_imagem = f"https://venda-imoveis.caixa.gov.br{src}" if src.startswith('/') else src

    # Garantir que os valores estão disponíveis antes de calcular a diferença
    diferenca, porcentagem = "Valor não encontrado.", "Valor não encontrado."
    if valor_segundo_leilao and valor_avaliacao:
    try:
    valor_segundo_leilao_float = float(re.sub(r'[^\d,]', '', valor_segundo_leilao.group(1)).replace(',', '.'))
    valor_avaliacao_float = float(re.sub(r'[^\d,]', '', valor_avaliacao.group(1)).replace(',', '.'))

    if valor_avaliacao_float != 0:
    diferenca = valor_avaliacao_float - valor_segundo_leilao_float
    porcentagem = (diferenca / valor_avaliacao_float) * 100
    porcentagem = f"{porcentagem:.2f}%"
    else:
    porcentagem = "0.00%"
    except ValueError:
    diferenca, porcentagem = "Valor inválido.", "Valor inválido."

    return {
    'minimoVendaPrimeiroLeilao': valor_primeiro_leilao.group(1) if valor_primeiro_leilao else "Valor não encontrado.",
    'minimoVendaSegundoLeilao': valor_segundo_leilao.group(1) if valor_segundo_leilao else "Valor não encontrado.",
    'dataSegundoLeilao': data_segundo_leilao.group(1) if data_segundo_leilao else "Data não encontrada.",
    'linkImagem': link_imagem if link_imagem else "Imagem não encontrada.",
    'diferencaSegundoLeilaoAvaliacao': locale.currency(diferenca, grouping=True) if isinstance(diferenca, (int, float)) else diferenca,
    'porcentagemSegundoLeilaoAvaliacao': porcentagem
    }

    except Exception as e:
    erro_count += 1
    return None

    Continue reading...

Compartilhe esta Página