Como extrair resultados de pesquisa do Bing com Python

Comentários: 0

A análise da Web não se limita ao Google. O Bing oferece uma visão alternativa do SERP que é útil para investigação de SEO, prospeção de ligações, monitorização de marcas, análise da concorrência e investigação de conteúdos. O Python é uma ferramenta ideal para este tipo de automatização: um ecossistema maduro, uma sintaxe simples e bibliotecas robustas para análise de HTML e trabalho com JSON permitem-lhe extrair resultados de pesquisa do Bing de forma mais rápida e conveniente.

Porquê concentrar-se no Bing em vez de no Google?

O Bing utiliza as suas próprias diretrizes de classificação e sinais de qualidade, pelo que os resultados diferem frequentemente dos do Google. Este facto é valioso para descobrir oportunidades adicionais na pesquisa orgânica e nas consultas de cauda longa. Nas suas recomendações para webmasters, o Bing dá ênfase à relevância, qualidade/confiança, envolvimento do utilizador, atualidade, factores geográficos e velocidade da página - um equilíbrio de sinais diferente do Google. É por isso que algumas páginas têm uma classificação mais elevada especificamente no Bing.

Casos práticos de utilização quando se extraem resultados de pesquisa do Bing:

  • Expandir a sua lista de doadores de construção de ligações - este motor eleva por vezes os sites que não aparecem no top 10 do Google.
  • Acompanhar o PAA ("As pessoas também perguntam") e os elementos SERP universais do Bing (vídeo, carrosséis) para ajustar a sua estratégia de conteúdos.

Que dados podem ser extraídos do Bing Search?

De um SERP "clássico" é possível extrair com fiabilidade:

  • Título;
  • URL (ligação do documento);
  • Snippet (descrição);
  • Posição nos resultados (índice ordinal);
  • Alguns resultados universais: "Relacionado/Pessoas também perguntam", resultados de imagem/vídeo incorporados (quando incluídos diretamente no SERP principal).

Importante: A marcação do Bing é alterada periodicamente, pelo que os selectores no código abaixo podem necessitar de ajustes.

Considerações legais e éticas ao raspar a pesquisa do Bing

  • Siga os Termos de Utilização da Microsoft: para acesso "oficial" aos dados da Web, a Microsoft oferece agora o Grounding com o Bing Search como parte dos Agentes de IA do Azure. As APIs públicas do Bing Search foram totalmente descontinuadas em 11 de agosto de 2025.
  • O aterramento com a Pesquisa Bing tem os seus próprios TOU e restrições: é utilizado através de agentes Azure e os resultados são devolvidos nas respostas do agente e não como dados SERP JSON "em bruto".
  • Respeite o ficheiro robots.txt e evite sobrecarregar os anfitriões - respeitar os robots é um princípio ético de raspagem.

Configurando seu ambiente Python para raspagem

Instalar o básico:

pip install requests beautifulsoup4 lxml fake-useragent selenium
  • requests - Cliente HTTP (permite-lhe definir cabeçalhos como User-Agent);
  • beautifulsoup4 + lxml - Análise de HTML;
  • fake-useragent - geração aleatória de UA (ou construir a sua própria lista);
  • selenium - apresenta blocos dinâmicos quando necessário.

Método 1 - Recolha de dados do Bing através de Requests e BeautifulSoup

Vamos utilizar isto como base para demonstrar o fluxo de trabalho: emitir pedidos GET, definir um User-Agent, analisar cartões de resultados e recolher título, URL, snippet e posição.

import time
import random
from typing import List, Dict
import requests
from bs4 import BeautifulSoup

BING_URL = "https://www.bing.com/search"

HEADERS_POOL = [
    # You can add more — or use fake-useragent
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
    "(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 13_5) AppleWebKit/605.1.15 "
    "(KHTML, like Gecko) Version/17.0 Safari/605.1.15",
]

def fetch_serp(query: str, count: int = 10, first: int = 1,
               proxy: str | None = None) -> List[Dict]:
    """
Returns a list of results: title, url, snippet, position.
`first` — starting position (pagination), `count` — how many records to fetch.

    """
    params = {"q": query, "count": count, "first": first}
    headers = {"User-Agent": random.choice(HEADERS_POOL)}
    proxies = {"http": proxy, "https": proxy} if proxy else None

    resp = requests.get(BING_URL, params=params, headers=headers,
                        proxies=proxies, timeout=15)
    resp.raise_for_status()
    soup = BeautifulSoup(resp.text, "lxml")

   # Typical Bing markup: <li class="b_algo"> ... <h2><a href="">Title</a></h2>
    items = []
    for idx, li in enumerate(soup.select("li.b_algo"), start=first):
        a = li.select_one("h2 a")
        if not a:
            continue
        title = a.get_text(strip=True)
        url = a.get("href")
         # Snippet is often in .b_caption p or simply the first <p>
        sn_el = li.select_one(".b_caption p") or li.select_one("p")
        snippet = sn_el.get_text(" ", strip=True) if sn_el else ""
        items.append({
            "position": idx,
            "title": title,
            "url": url,
            "snippet": snippet
        })
    return items

if __name__ == "__main__":
    data = fetch_serp("python web scraping tutorial", count=10)
    for row in data:
        print(f"{row['position']:>2}. {row['title']} -- {row['url']}")
        print(f"   {row['snippet']}\n")

Explicação:

  • Utilizar parâmetros de contagem/primeiro para paginação.
  • Os selectores li.b_algo h2 a e .b_caption p são de base; o esquema pode mudar (inspecionar no DevTools).
  • Adicionar um proxy quando necessário e regular as pausas entre os pedidos.
  • Vamos melhorar este exemplo um pouco mais abaixo, uma vez que é a abordagem mais eficaz para os nossos objectivos nas condições actuais.

Método 2 - Extrair os resultados de pesquisa do Bing através da API (estado em 2025)

A API de raspagem pública do Bing da Microsoft foi aposentada em agosto de 2025. A Microsoft recomenda a migração para o aterramento com a Pesquisa do Bing nos Agentes de IA do Azure.

O que isto significa na prática

  • O ponto de extremidade REST clássico com dados JSON SERP "brutos" já não está disponível para a maioria dos programadores.
  • O aterramento com a Pesquisa do Bing é conectado como uma ferramenta dentro de um agente do Azure; o agente pode "procurar" a Web e retornar uma resposta sintetizada. O serviço tem os seus próprios TOU e especificidades: não foi concebido para a extração em massa de resultados SERP em bruto.

Alternativa para SERP em bruto em JSON

Utilize APIs/plataformas SERP de terceiros (por exemplo, Apify Bing Search Scraper) que devolvem resultados estruturados: título, URL, snippet, posição, etc.

Exemplo de pedido mínimo do Apify:

import requests

API_TOKEN = "apify_xxx"  # store in ENV
actor = "tri_angle/bing-search-scraper"
payload = {
    "queries": ["python web scraping tutorial"],
    "countryCode": "US",
    "includeUnfilteredResults": False
}

r = requests.post(
    f"https://api.apify.com/v2/acts/{actor}/runs?token={API_TOKEN}",
    json=payload, timeout=30
)
run = r.json()
# Retrieve dataset items using run['data']['defaultDatasetId']

A Apify documenta o suporte para resultados orgânicos, PAA, consultas relacionadas e muito mais. Certifique-se de que o seu caso de utilização está em conformidade com as regras da plataforma e com as leis da sua jurisdição.

Dica: Se você trabalha na pilha do Azure AI Agents e só precisa de referências aterradas para um LLM (em vez de JSON bruto), leia o guia em Ligação à terra com a Pesquisa Bing.

Método 3 - Analisar conteúdo dinâmico com o Selenium

Quando o SERP inclui carrosséis, blocos interactivos ou conteúdo processado por JavaScript, mude para Selenium (Headless Chrome/Firefox).

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options

def selenium_bing(query: str, headless: bool = True):
    opts = Options()
    if headless:
        opts.add_argument("--headless=new")
    opts.add_argument("--disable-gpu")
    opts.add_argument("--no-sandbox")
    with webdriver.Chrome(options=opts) as driver:
        driver.get("https://www.bing.com/")
        box = driver.find_element(By.NAME, "q")
        box.send_keys(query)
        box.submit()

        # Consider adding explicit waits via WebDriverWait
        cards = driver.find_elements(By.CSS_SELECTOR, "li.b_algo h2 a")
        results = []
        for i, a in enumerate(cards, start=1):
            results.append({"position": i, "title": a.text, "url": a.get_attribute("href")})
        return results

if __name__ == "__main__":
    print(selenium_bing("site:docs.python.org requests headers"))

Consultar o sítio oficial Documentos do Selenium para instalação do driver e exemplos de WebDriverWait.

Solução prática: Estratégia de análise e código de exemplo

Para a implementação final, vamos efetuar a recolha de dados do Bing diretamente do HTML:

  1. Enviar pedidos HTTP para https://www.bing.com/search.
  2. Definir um User-Agent.
  3. Analise HTML através de BeautifulSoup + lxml para extrair títulos, URLs e snippets.

Desta forma, não precisa de contas Microsoft e não está vinculado a APIs pagas de terceiros. Para a seleção de resultados, utilizamos o contentor do cartão de resultados li.b_algo, que é normalmente utilizado para os blocos orgânicos do Bing.

Exemplo de funcionamento (paginação, atrasos, proxy opcional)

from __future__ import annotations

import argparse
import csv
import dataclasses
import pathlib
import random
import sys
import time
from typing import List, Optional, Tuple

import requests
from bs4 import BeautifulSoup, FeatureNotFound

BING_URL = "https://www.bing.com/search"

# Pool of user agents
UA_POOL = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
]

@dataclasses.dataclass
class SerpItem:
    position: int
    title: str
    url: str
    snippet: str


def build_session(proxy: Optional[str] = None) -> requests.Session:
    """Create a session with baseline headers and an optional proxy."""
    s = requests.Session()
    s.headers.update(
        {
            "User-Agent": random.choice(UA_POOL),
            "Accept-Language": "uk-UA,uk;q=0.9,en;q=0.8",
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        }
    )
    if proxy:
        # Requests proxy dict format: {'http': 'http://host:port', 'https': 'http://host:port'}
        s.proxies.update({"http": proxy, "https": proxy})
    return s


def _soup_with_fallback(html: str) -> BeautifulSoup:
    """Parse HTML with a forgiving fallback chain: lxml -> html.parser -> html5lib (if available)."""
    for parser in ("lxml", "html.parser", "html5lib"):
        try:
            return BeautifulSoup(html, parser)
        except FeatureNotFound:
            continue
    # If none are installed, bs4 will raise; let it propagate
    return BeautifulSoup(html, "html.parser")


def parse_serp_html(html: str, start_pos: int) -> List[SerpItem]:
    """Extract organic results from Bing SERP HTML."""
    soup = _soup_with_fallback(html)
    items: List[SerpItem] = []

    # Organic blocks typically look like <li class="b_algo"> with h2>a and a snippet under .b_caption p or the first <p>.
    for i, li in enumerate(soup.select("li.b_algo"), start=start_pos):
        a = li.select_one("h2 > a")
        if not a:
            continue
        title = (a.get_text(strip=True) or "").strip()
        url = a.get("href") or ""
        p = li.select_one(".b_caption p") or li.select_one("p")
        snippet = (p.get_text(" ", strip=True) if p else "").strip()
        items.append(SerpItem(position=i, title=title, url=url, snippet=snippet))

    return items


def fetch_bing_page(
    session: requests.Session,
    query: str,
    first: int = 1,
    count: int = 10,
    cc: str = "UA",
    setlang: str = "uk",
    timeout: int = 20,
) -> List[SerpItem]:
    """Download one results page and return parsed items."""
    params = {
        "q": query,
        "count": count,   # 10, 15, 20...
        "first": first,   # 1, 11, 21...
        "cc": cc,         # country code for results
        "setlang": setlang,  # interface/snippet language
    }
    r = session.get(BING_URL, params=params, timeout=timeout)
    r.raise_for_status()
    return parse_serp_html(r.text, start_pos=first)


def search_bing(
    query: str,
    pages: int = 1,
    count: int = 10,
    pause_range: Tuple[float, float] = (1.2, 2.7),
    proxy: Optional[str] = None,
    cc: str = "UA",
    setlang: str = "uk",
    timeout: int = 20,
) -> List[SerpItem]:
    """Iterate over pages and return an aggregated list of results."""
    session = build_session(proxy=proxy)
    all_items: List[SerpItem] = []
    first = 1
    for _ in range(pages):
        items = fetch_bing_page(
            session, query, first=first, count=count, cc=cc, setlang=setlang, timeout=timeout
        )
        all_items.extend(items)
        time.sleep(random.uniform(*pause_range))  # polite delay
        first += count
    return all_items


def _normalize_cell(s: str) -> str:
    """Optional: collapse internal whitespace so simple viewers show one‑line cells."""
    # Convert tabs/newlines/multiple spaces to a single space
    return " ".join((s or "").split())


def save_csv(
    items: List[SerpItem],
    path: str,
    excel_friendly: bool = False,
    normalize: bool = False,
    delimiter: str = ",",
) -> int:
    """
Write results to CSV.
— excel_friendly=True -> write UTF‑8 with BOM (utf‑8‑sig) so Excel auto‑detects Unicode.
— normalize=True -> collapse whitespace inside string fields.
— delimiter -> change if your consumer expects ';', etc.
Returns the number of rows written (excluding header).

    """
    p = pathlib.Path(path)
    p.parent.mkdir(parents=True, exist_ok=True)

    encoding = "utf-8-sig" if excel_friendly else "utf-8"

    # newline='' is required so Python's csv handles line endings correctly on all platforms
    with p.open("w", newline="", encoding=encoding) as f:
        writer = csv.DictWriter(
            f,
            fieldnames=["position", "title", "url", "snippet"],
            delimiter=delimiter,
            quoting=csv.QUOTE_MINIMAL,
        )
        writer.writeheader()
        for it in items:
            row = dataclasses.asdict(it)
            if normalize:
                row = {k: _normalize_cell(v) if isinstance(v, str) else v for k, v in row.items()}
            writer.writerow(row)
    return len(items)


def main() -> int:
    ap = argparse.ArgumentParser(description="Bing SERP scraper (Requests + BS4)")
    ap.add_argument("-q", "--query", required=True, help="Search query")
    ap.add_argument("--pages", type=int, default=1, help="Number of pages (x count)")
    ap.add_argument("--count", type=int, default=10, help="Results per page")
    ap.add_argument("--cc", default="UA", help="Country code for results (cc)")
    ap.add_argument("--setlang", default="uk", help="Interface/snippet language (setlang)")
    ap.add_argument("--proxy", help="Proxy, e.g. http://user:pass@host:port")
    ap.add_argument("--csv", help="Path to CSV to save results")
    ap.add_argument(
        "--excel-friendly",
        action="store_true",
        help="Add BOM (UTF‑8‑SIG) so Excel opens the file correctly",
    )
    ap.add_argument(
        "--normalize-cells",
        action="store_true",
        help="Remove line breaks and extra spaces in cells",
    )
    ap.add_argument(
        "--delimiter",
        default=",",
        help="CSV delimiter (default ','); e.g.: ';'",
    )
    args = ap.parse_args()

    try:
        items = search_bing(
            args.query,
            pages=args.pages,
            count=args.count,
            proxy=args.proxy,
            cc=args.cc,
            setlang=args.setlang,
        )
    except requests.HTTPError as e:
        print(f"[ERROR] HTTP error: {e}", file=sys.stderr)
        return 2
    except requests.RequestException as e:
        print(f"[ERROR] Network error: {e}", file=sys.stderr)
        return 2

    if args.csv:
        try:
            n = save_csv(
                items,
                args.csv,
                excel_friendly=args.excel_friendly,
                normalize=args.normalize_cells,
                delimiter=args.delimiter,
            )
            print(f"Saved {n} rows to {args.csv}")
        except OSError as e:
            print(f"[ERROR] Could not write CSV to {args.csv}: {e}", file=sys.stderr)
            return 3
    else:
        for it in items:
            print(f"{it.position:>2}. {it.title} -- {it.url}")
            if it.snippet:
                print("   ", it.snippet[:180])

    return 0


if __name__ == "__main__":
    sys.exit(main())

Exemplo de utilização com parâmetros extra e um proxy:

python bing_scraper.py -q "Python web scraping" --pages 3 --csv out.csv \
  --proxy "http://username:password@proxy:port"

O que faz o guião:

  1. Envia pedidos GET ao Bing com parâmetros controlados (q, count, first) e definições de localidade (cc, setlang).
  2. Substitui User-Agent e adiciona Accept-Language para trechos mais estáveis.
  3. Analisa HTML via BeautifulSoup(..., "lxml"), localiza os cartões de resultado li.b_algo, e extrai o título, url, e snippet. Os seletores CSS .select() no BS4 são uma abordagem padrão e flexível.
  4. Suporta um proxy opcional. Para Pedidos, o formato proxy correto é um mapeamento protocolo→URL.

Conselhos de estabilidade:

  • Adicionar pausas (aleatorizar os intervalos entre os pedidos).
  • Rodar o User-Agent (dinamicamente ou a partir da sua lista). Os pedidos mostram como definir corretamente os cabeçalhos - fazemo-lo no exemplo de trabalho.
  • Utilizar a infraestrutura de proxy/rotação de IP quando necessário para escalar eficazmente dentro das limitações da plataforma.
  • Mantenha um volume global de pedidos razoável e verifique as respostas aos avisos CAPTCHA.
  • Para cenários complexos, considere APIs SERP geridas (Apify, etc.) que incluem infraestrutura antibot.

Onde ler mais sobre as ferramentas

Sugestão: Se precisar de uma infraestrutura proxy para uma recolha de dados mais estável, consulte o melhores proxies para o Bing.

Como evitar bloqueios ao fazer scraping do Bing

Princípios fundamentais para garantir que o seu raspador não "morre" durante o primeiro ciclo:

  • Adicionar atrasos (aleatorizar os intervalos entre os pedidos).
  • Rode o seu User-Agent (dinamicamente ou a partir da sua própria lista); a forma correta de definir cabeçalhos nos pedidos está descrita na documentação - utilizamos a mesma abordagem no nosso exemplo de trabalho.
  • Utilizar proxies ou rotação de IP (respeitando as condições de utilização do serviço).
  • Limitar o número total de pedidos e monitorizar as respostas aos avisos CAPTCHA.
  • Para tarefas complexas, considere APIs SERP geridas (Apify, etc.) com infraestrutura antibot incorporada.

Conclusão

A raspagem do Bing é útil quando se pretende expandir a investigação para além do Google, reunir domínios doadores adicionais, acompanhar caraterísticas SERP alternativas e obter uma visão independente do panorama. Para uma integração estável e "oficial", a Microsoft promove o Grounding com a Pesquisa Bing nos Agentes de IA do Azure; é mais seguro do ponto de vista dos termos de serviço, mas não devolve dados SERP JSON em bruto. Se a sua tarefa for extrair resultados estruturados, escolha a análise direta de HTML via Requests/BS4 ou Selenium, ou use uma API SERP especializada. Escolha a ferramenta para o trabalho: análise rápida de HTML para protótipos, agentes para respostas fundamentadas em LLM e APIs SERP para coleta em grande escala.

Comentários:

0 Comentários