Come eseguire lo scraping dei risultati di ricerca di Bing con Python

Commenti: 0

L'analisi web non è limitata a Google. Bing offre una visione alternativa della SERP, utile per la ricerca SEO, la ricerca di link, il monitoraggio del marchio, l'analisi della concorrenza e la ricerca di contenuti. Python è uno strumento ideale per questo tipo di automazione: un ecosistema maturo, una sintassi semplice e librerie robuste per l'analisi dell'HTML e il lavoro con JSON consentono di effettuare lo scraping dei risultati di ricerca di Bing in modo più rapido e pratico.

Perché concentrarsi su Bing piuttosto che su Google?

Bing utilizza le proprie linee guida per il ranking e i propri segnali di qualità, pertanto i risultati differiscono spesso da quelli di Google. Questo è prezioso per scoprire ulteriori opportunità nella ricerca organica e nelle query a coda lunga. Nelle sue raccomandazioni per i webmaster, Bing enfatizza la rilevanza, la qualità/fiducia, il coinvolgimento degli utenti, la freschezza, i fattori geografici e la velocità della pagina, un equilibrio di segnali diverso da quello di Google. Ecco perché alcune pagine si classificano più in alto proprio su Bing.

Casi d'uso pratici per lo scraping dei risultati di ricerca di Bing:

  • Ampliare l'elenco dei donatori di link: questo motore a volte valorizza i siti che non compaiono nella top 10 di Google.
  • Tracciare il PAA ("People also ask") e gli elementi universali della SERP di Bing (video, caroselli) per adattare la strategia dei contenuti.

Quali dati si possono estrarre dalla ricerca Bing?

Da una SERP "classica" è possibile estrarre in modo affidabile:

  • Titolo;
  • URL (collegamento al documento);
  • Snippet (descrizione);
  • Posizione nei risultati (indice ordinale);
  • Alcuni risultati universali: "Related/People also ask", risultati di immagini/video incorporati (se inclusi direttamente nella SERP principale).

Importante: il markup di Bing cambia periodicamente, pertanto i selettori nel codice sottostante potrebbero richiedere modifiche.

Considerazioni legali ed etiche quando si esegue lo scraping della ricerca Bing

  • Seguire le condizioni d'uso di Microsoft: per l'accesso "ufficiale" ai dati web, Microsoft offre ora Grounding con Bing Search come parte di Azure AI Agents. Le API pubbliche di Bing Search sono state completamente abbandonate l'11 agosto 2025.
  • Il grounding con Bing Search ha le sue TOU e i suoi vincoli: viene utilizzato attraverso gli agenti Azure e i risultati vengono restituiti nelle risposte dell'agente piuttosto che come dati SERP JSON "grezzi".
  • Rispettate il file robots.txt ed evitate di sovraccaricare gli host: rispettare i robots è un'etica di base dello scraping.

Impostazione dell'ambiente Python per lo scraping

Installare le basi:

pip install requests beautifulsoup4 lxml fake-useragent selenium
  • requests - client HTTP (consente di impostare intestazioni come User-Agent);
  • beautifulsoup4 + lxml - Analisi HTML;
  • fake-useragent - generazione casuale di UA (o creazione di un proprio elenco);
  • selenium - rende i blocchi dinamici quando necessario.

Metodo 1 - Scraping di Bing tramite Requests e BeautifulSoup

Lo useremo come base per dimostrare il flusso di lavoro: inviare richieste GET, impostare un User-Agent, analizzare le schede dei risultati e raccogliere titolo, URL, snippet e posizione.

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

Spiegazione:

  • Utilizzare i parametri count/first per la paginazione.
  • I selettori li.b_algo h2 a e .b_caption p sono di base; il layout può cambiare (ispezionare in DevTools).
  • Aggiungere un proxy quando necessario e regolare le pause tra le richieste.
  • Questo esempio verrà ulteriormente migliorato di seguito, poiché è l'approccio più efficace per i nostri scopi nelle condizioni attuali.

Metodo 2 - Scrape dei risultati di ricerca di Bing tramite API (stato 2025)

L'API pubblica di Bing scraper di Microsoft è stata ritirata nell'agosto 2025. Microsoft consiglia di migrare a Grounding con Bing Search all'interno di Azure AI Agents.

Cosa significa in pratica

  • Il classico endpoint REST con i dati SERP JSON "grezzi" non è più disponibile per la maggior parte degli sviluppatori.
  • Il Grounding con Bing Search è collegato come strumento all'interno di un agente Azure; l'agente può "cercare" sul web e restituire una risposta sintetizzata. Il servizio ha le sue TOU e le sue specifiche: non è progettato per l'estrazione in massa dei risultati grezzi delle SERP.

Alternativa per la SERP grezza in JSON

Utilizzare API/piattaforme SERP di terze parti (ad esempio, Apify Bing Search Scraper) che restituiscono risultati strutturati: titolo, URL, snippet, posizione, ecc.

Esempio di richiesta minima di 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 il supporto per risultati organici, PAA, query correlate e altro ancora. Assicuratevi che il vostro caso d'uso sia conforme alle regole della piattaforma e alle leggi della vostra giurisdizione.

Suggerimento: se si lavora nello stack Azure AI Agents e si ha bisogno solo di riferimenti a terra per un LLM (piuttosto che di JSON grezzo), leggere la guida su Messa a terra con la ricerca Bing.

Metodo 3 - Analizzare il contenuto dinamico con Selenium

Quando la SERP include caroselli, blocchi interattivi o contenuti resi da JavaScript, passare 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"))

Fare riferimento al documento ufficiale Documenti Selenium per l'installazione del driver e gli esempi di WebDriverWait.

Soluzione pratica: Strategia di parsing e codice di esempio

Per l'implementazione finale, eseguiremo lo scraping di Bing direttamente dall'HTML:

  1. Inviare richieste HTTP a https://www.bing.com/search.
  2. Impostare un User-Agent.
  3. Analizzare l'HTML tramite BeautifulSoup + lxml per estrarre titoli, URL e snippet.

In questo modo non è necessario disporre di account Microsoft e non si è legati ad API a pagamento di terze parti. Per la selezione dei risultati utilizziamo il contenitore li.b_algo, comunemente utilizzato per i blocchi organici di Bing.

Esempio di lavoro (paginazione, ritardi, proxy opzionale)

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

Esempio di utilizzo con parametri aggiuntivi e un proxy:

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

Cosa fa il copione:

  1. Invia richieste GET a Bing con parametri controllati (q, count, first) e impostazioni locali (cc, setlang).
  2. Sovrascrive User-Agent e aggiunge Accept-Language per frammenti più stabili.
  3. Analizza l'HTML tramite BeautifulSoup(..., "lxml"), individua le schede risultato li.b_algo ed estrae titolo, url e snippet. I selettori CSS .select() di BS4 sono un approccio standard e flessibile.
  4. Supporta un proxy opzionale. Per le richieste, il formato corretto del proxy è una mappatura protocollo→URL.

Suggerimenti per la stabilità:

  • Aggiungere pause (randomizzare gli intervalli tra le richieste).
  • Ruotare l'User-Agent (dinamicamente o dal proprio elenco). Le richieste mostrano come impostare correttamente le intestazioni; lo facciamo nell'esempio di lavoro.
  • Utilizzare l'infrastruttura proxy/la rotazione IP quando necessario per scalare efficacemente entro i limiti della piattaforma.
  • Mantenere un volume complessivo di richieste ragionevole e controllare le risposte alle richieste CAPTCHA.
  • Per gli scenari complessi, considerate le API SERP gestite (Apify, ecc.) che includono l'infrastruttura antibot.

Dove leggere ulteriori informazioni sugli strumenti

Suggerimento: se avete bisogno di un'infrastruttura proxy per una raccolta dati più stabile, consultate il programma I migliori proxy per Bing.

Come evitare i blocchi durante lo scraping di Bing

Principi chiave per garantire che il vostro raschiatore non "muoia" durante il primo ciclo:

  • Aggiungere ritardi (randomizzare gli intervalli tra le richieste).
  • Ruotare l'User-Agent (dinamicamente o da un proprio elenco); il modo corretto di impostare le intestazioni nelle richieste è descritto nella documentazione; noi usiamo lo stesso approccio nel nostro esempio di lavoro.
  • Utilizzare i proxy o la rotazione degli IP (rispettando le condizioni di utilizzo del servizio).
  • Limitare il numero complessivo di richieste e monitorare le risposte alle richieste CAPTCHA.
  • Per le attività più complesse, considerate le API SERP gestite (Apify, ecc.) con infrastruttura antibot integrata.

Conclusione

Lo scraping di Bing è utile quando si vuole espandere la ricerca oltre Google, raccogliere altri domini donatori, tracciare caratteristiche SERP alternative e ottenere una visione indipendente del panorama. Per un'integrazione stabile e "ufficiale", Microsoft promuove il Grounding con Bing Search in Azure AI Agents; è più sicuro dal punto di vista dei termini di servizio, ma non restituisce dati SERP JSON grezzi. Se il vostro compito è quello di estrarre risultati strutturati, scegliete il parsing HTML diretto tramite Requests/BS4 o Selenium, oppure utilizzate un'API SERP specializzata. Scegliete lo strumento adatto al lavoro: parsing HTML rapido per i prototipi, agenti per le risposte basate su LLM e API SERP per la raccolta su larga scala.

Commenti:

0 Commenti