Cómo raspar los resultados de búsqueda de Bing con Python

Comentarios: 0

El análisis web no se limita a Google. Bing ofrece una visión alternativa de las SERP que resulta útil para la investigación SEO, la prospección de enlaces, el seguimiento de marcas, el análisis de la competencia y la investigación de contenidos. Python es una herramienta ideal para este tipo de automatización: un ecosistema maduro, una sintaxis sencilla y bibliotecas sólidas para el análisis sintáctico de HTML y el trabajo con JSON permiten extraer los resultados de búsqueda de Bing de forma más rápida y cómoda.

¿Por qué centrarse en Bing y no en Google?

Bing utiliza sus propias directrices de clasificación y señales de calidad, por lo que los resultados suelen diferir de los de Google. Esto resulta útil para descubrir nuevas oportunidades en la búsqueda orgánica y en las consultas de cola larga. En sus recomendaciones para webmasters, Bing hace hincapié en la relevancia, la calidad/confianza, la participación del usuario, la frescura, los factores geográficos y la velocidad de la página, un equilibrio de señales diferente al de Google. Por eso algunas páginas tienen una clasificación más alta específicamente en Bing.

Casos prácticos de uso al raspar los resultados de búsqueda de Bing:

  • Ampliar su lista de donantes de enlaces: este motor a veces eleva sitios que no aparecen entre los 10 primeros de Google.
  • Seguimiento del PAA ("La gente también pregunta") y de los elementos universales de las SERP de Bing (vídeo, carruseles) para ajustar su estrategia de contenidos.

¿Qué datos se pueden extraer de la búsqueda en Bing?

De una SERP "clásica" se puede extraer de forma fiable:

  • Título;
  • URL (enlace al documento);
  • Fragmento (descripción);
  • Posición en los resultados (índice ordinal);
  • Algunos resultados universales: "Relacionado/Personas que también preguntan", resultados de imágenes/vídeos incrustados (cuando se incluyen directamente en la SERP principal).

Importante: El marcado de Bing cambia periódicamente, por lo que los selectores del código que aparece a continuación pueden necesitar ajustes.

Consideraciones legales y éticas al raspar la búsqueda en Bing

  • Siga las condiciones de uso de Microsoft: para el acceso "oficial" a los datos web, Microsoft ofrece ahora Grounding con Bing Search como parte de Azure AI Agents. Las API públicas de Bing Search desaparecieron por completo el 11 de agosto de 2025.
  • La conexión a tierra con Bing Search tiene su propio TOU y restricciones: se utiliza a través de agentes Azure, y los resultados se devuelven en las respuestas del agente en lugar de como datos SERP JSON "en bruto".
  • Respete el archivo robots.txt y evite sobrecargar los hosts: respetar los robots es la ética básica del scraping.

Configuración del entorno Python para el scraping

Instala lo básico:

pip install requests beautifulsoup4 lxml fake-useragent selenium
  • requests - Cliente HTTP (permite establecer cabeceras como User-Agent);
  • beautifulsoup4 + lxml - Análisis sintáctico de HTML;
  • fake-useragent - generación aleatoria de UA (o construye tu propia lista);
  • selenio - renderizar bloques dinámicos cuando sea necesario.

Método 1 - Scraping Bing a través de Requests y BeautifulSoup

Utilizaremos esto como base para demostrar el flujo de trabajo: emitir solicitudes GET, establecer un User-Agent, analizar las tarjetas de resultados y recopilar el título, la URL, el fragmento y la posición.

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")

Explicación:

  • Utilice los parámetros count/first para la paginación.
  • Los selectores li.b_algo h2 a y .b_caption p son de línea de base; el diseño puede cambiar (inspeccionar en DevTools).
  • Añade un proxy cuando sea necesario y regula las pausas entre peticiones.
  • Mejoraremos este ejemplo un poco más adelante, ya que es el enfoque más eficaz para nuestros propósitos en las condiciones actuales.

Método 2 - Extraer los resultados de búsqueda de Bing a través de la API (estado en 2025)

La API pública de Bing scraper de Microsoft se retiró en agosto de 2025. Microsoft recomienda migrar a Grounding con Bing Search dentro de Azure AI Agents.

Qué significa esto en la práctica

  • El punto final REST clásico con datos SERP JSON "en bruto" ya no está disponible para la mayoría de los desarrolladores.
  • La conexión a tierra con Bing Search se realiza como una herramienta dentro de un agente Azure; el agente puede "buscar" en la web y devolver una respuesta sintetizada. El servicio tiene sus propios términos de uso y especificaciones: no está diseñado para la extracción masiva de resultados SERP sin procesar.

Alternativa para SERP en bruto en JSON

Utilizar API/plataformas SERP de terceros (por ejemplo, Apify Bing Search Scraper) que devuelvan resultados estructurados: título, URL, fragmento, posición, etc.

Ejemplo de solicitud mínima de 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']

Apify documenta el soporte para resultados orgánicos, PAA, consultas relacionadas y más. Asegúrate de que tu caso de uso cumple con las reglas de la plataforma y con las leyes de tu jurisdicción.

Sugerencia: Si trabaja en la pila Azure AI Agents y sólo necesita referencias en tierra para un LLM (en lugar de JSON en bruto), lea la guía sobre Toma de tierra con Bing Search.

Método 3 - Análisis de contenido dinámico con Selenium

Cuando la SERP incluya carruseles, bloques interactivos o contenido renderizado por JavaScript, cambie a 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"))

Consulte el Documentación sobre selenio para ver ejemplos de instalación de controladores y WebDriverWait.

Solución práctica: Estrategia de análisis sintáctico y código de ejemplo

Para la implementación final, realizaremos el scraping de Bing directamente desde HTML:

  1. Enviar peticiones HTTP a https://www.bing.com/search.
  2. Establezca un User-Agent.
  3. Analiza HTML mediante BeautifulSoup + lxml para extraer títulos, URL y fragmentos.

De esta forma no necesitas cuentas de Microsoft y no estás atado a APIs de pago de terceros. Para la selección de resultados utilizamos el contenedor de tarjetas de resultados li.b_algo, que se usa habitualmente para los bloques orgánicos de Bing.

Ejemplo práctico (paginación, retrasos, 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())

Ejemplo de uso con parámetros adicionales y un proxy:

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

Qué hace el guión:

  1. Envía peticiones GET a Bing con parámetros controlados (q, count, first) y configuración regional (cc, setlang).
  2. Anula User-Agent y añade Accept-Language para obtener fragmentos más estables.
  3. Analiza HTML mediante BeautifulSoup(..., "lxml"), localiza las tarjetas de resultados li.b_algo y extrae el título, la url y el fragmento. Los selectores CSS .select() en BS4 son un enfoque estándar y flexible.
  4. Admite un proxy opcional. Para solicitudes, el formato de proxy correcto es una asignación de protocolo→URL.

Consejos de estabilidad:

  • Añadir pausas (intervalos aleatorios entre solicitudes).
  • Gire User-Agent (dinámicamente o de su lista). Requests muestra cómo configurar las cabeceras correctamente - lo hacemos en el ejemplo de trabajo.
  • Utilizar infraestructura proxy/rotación de IP cuando sea necesario para escalar eficazmente dentro de las limitaciones de la plataforma.
  • Mantenga un volumen de solicitudes razonable y compruebe las respuestas a las preguntas CAPTCHA.
  • Para escenarios complejos, considere las API SERP gestionadas (Apify, etc.) que incluyen infraestructura antibot.

Más información sobre las herramientas

Sugerencia: Si necesita una infraestructura de proxy para una recopilación de datos más estable, consulte la sección mejores proxies para Bing.

Cómo evitar bloqueos al hacer scraping en Bing

Principios clave para garantizar que su rascador no "muera" durante su primer ciclo:

  • Añade retardos (aleatoriza los intervalos entre peticiones).
  • Gire su User-Agent (dinámicamente o de su propia lista); la forma correcta de establecer cabeceras en las peticiones se describe en la documentación - utilizamos el mismo enfoque en nuestro ejemplo de trabajo.
  • Utilizar proxies o rotación de IP (respetando las condiciones de uso del servicio).
  • Limite el número total de solicitudes y controle las respuestas a las preguntas CAPTCHA.
  • Para tareas complejas, considere las API SERP gestionadas (Apify, etc.) con infraestructura antibot incorporada.

Conclusión

El scraping de Bing es útil cuando se desea ampliar la investigación más allá de Google, recopilar dominios donantes adicionales, rastrear características SERP alternativas y obtener una visión independiente del panorama. Para una integración estable y "oficial", Microsoft promueve Grounding con Bing Search en Azure AI Agents; es más seguro desde el punto de vista de los términos de servicio, pero no devuelve datos SERP JSON sin procesar. Si su tarea consiste en extraer resultados estructurados, elija el análisis sintáctico HTML directo a través de Requests/BS4 o Selenium, o utilice una API SERP especializada. Elija la herramienta adecuada para cada tarea: análisis HTML rápido para prototipos, agentes para respuestas basadas en LLM y API de SERP para recopilación a gran escala.

Comentarios:

0 Comentarios