Jak skrobać wyniki wyszukiwania Bing za pomocą Pythona

Komentarze: 0

Analityka internetowa nie ogranicza się do Google. Bing oferuje alternatywny widok SERP, który jest przydatny do badań SEO, poszukiwania linków, monitorowania marki, analizy konkurencji i badania treści. Python jest idealnym narzędziem do tego rodzaju automatyzacji: dojrzały ekosystem, prosta składnia i solidne biblioteki do parsowania HTML i pracy z JSON pozwalają na szybsze i wygodniejsze skrobanie wyników wyszukiwania Bing.

Dlaczego warto skupić się na Bing, a nie Google?

Bing korzysta z własnych wytycznych rankingowych i sygnałów jakości, więc wyniki często różnią się od wyników Google. Jest to cenne dla odkrywania dodatkowych możliwości w wyszukiwaniu organicznym i zapytaniach z długiego ogona. W swoich zaleceniach dla webmasterów Bing kładzie nacisk na trafność, jakość / zaufanie, zaangażowanie użytkowników, świeżość, czynniki geograficzne i szybkość strony - inną równowagę sygnałów niż Google. Dlatego niektóre strony mają wyższą pozycję w wyszukiwarce Bing.

Praktyczne przypadki użycia podczas skrobania wyników wyszukiwania Bing:

  • Rozszerzenie listy dawców linków - ten silnik czasami podnosi witryny, które nie pojawiają się w pierwszej dziesiątce Google.
  • Śledzenie PAA ("Ludzie też pytają") i uniwersalnych elementów SERP Bing (wideo, karuzele) w celu dostosowania strategii dotyczącej treści.

Jakie dane można wyodrębnić z wyszukiwarki Bing?

Z "klasycznego" SERP można niezawodnie wyodrębnić:

  • Tytuł;
  • URL (łącze do dokumentu);
  • Snippet (opis);
  • Pozycja w wynikach (indeks porządkowy);
  • Niektóre wyniki uniwersalne: "Powiązane/osoby również pytają", osadzone wyniki obrazów/wideo (gdy są zawarte bezpośrednio w głównym SERP).

Ważne: znaczniki Bing zmieniają się okresowo, więc selektory w poniższym kodzie mogą wymagać poprawek.

Rozważania prawne i etyczne podczas scrape'owania wyszukiwarki Bing

  • Postępuj zgodnie z warunkami użytkowania firmy Microsoft: aby uzyskać "oficjalny" dostęp do danych internetowych, firma Microsoft oferuje teraz uziemienie z wyszukiwarką Bing Search w ramach Azure AI Agents. Publiczne interfejsy API wyszukiwarki Bing zostały całkowicie wyłączone 11 sierpnia 2025 r.
  • Uziemienie za pomocą Bing Search ma własne TOU i ograniczenia: jest używane za pośrednictwem agentów Azure, a wyniki są zwracane w odpowiedziach agenta, a nie jako "surowe" dane JSON SERP.
  • Przestrzegaj pliku robots.txt i unikaj przeciążania hostów - przestrzeganie robotów to podstawowa etyka scrapingu.

Konfiguracja środowiska Python do scrapingu

Zainstaluj podstawy:

pip install requests beautifulsoup4 lxml fake-useragent selenium
  • requests - klient HTTP (pozwala ustawić nagłówki, takie jak User-Agent);
  • beautifulsoup4 + lxml - parsowanie HTML;
  • fake-useragent - losowe generowanie UA (lub tworzenie własnej listy);
  • selenium - renderuje dynamiczne bloki w razie potrzeby.

Metoda 1 - Scraping Bing przez Requests i BeautifulSoup

Wykorzystamy to jako punkt odniesienia do zademonstrowania przepływu pracy: wysyłanie żądań GET, ustawianie User-Agent, analizowanie kart wyników i zbieranie tytułu, adresu URL, fragmentu i pozycji.

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

Wyjaśnienie:

  • Użyj parametrów count/first do paginacji.
  • Selektory li.b_algo h2 a i .b_caption p są bazowe; układ może ulec zmianie (sprawdź w DevTools).
  • Dodaj proxy w razie potrzeby i reguluj przerwy między żądaniami.
  • Rozszerzymy ten przykład nieco dalej, ponieważ jest to najbardziej efektywne podejście do naszych celów w obecnych warunkach.

Metoda 2 - Skrobanie wyników wyszukiwania Bing przez API (stan na 2025 r.)

Publiczny interfejs API skrobaka Bing firmy Microsoft został wycofany w sierpniu 2025 r. Firma Microsoft zaleca migrację do Grounding z Bing Search w ramach Azure AI Agents.

Co to oznacza w praktyce

  • Klasyczny punkt końcowy REST z "surowymi" danymi JSON SERP nie jest już dostępny dla większości programistów.
  • Uziemienie z Bing Search jest połączone jako narzędzie wewnątrz agenta Azure; agent może "wyszukać" sieć i zwrócić zsyntetyzowaną odpowiedź. Usługa ma swoje własne TOU i specyfikę: nie jest przeznaczona do masowej ekstrakcji surowych wyników SERP.

Alternatywa dla surowego SERP w JSON

Korzystaj z interfejsów API/platform SERP innych firm (np. Apify Bing Search Scraper), które zwracają ustrukturyzowane wyniki: tytuł, adres URL, fragment, pozycję itp.

Przykład minimalnego żądania 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 dokumentuje obsługę wyników organicznych, PAA, powiązanych zapytań i nie tylko. Upewnij się, że Twój przypadek użycia jest zgodny z zasadami platformy i przepisami obowiązującymi w Twojej jurysdykcji.

Wskazówka: Jeśli pracujesz na stosie Azure AI Agents i potrzebujesz tylko uziemionych referencji dla LLM (a nie surowego JSON), przeczytaj przewodnik na stronie Uziemienie za pomocą wyszukiwarki Bing.

Metoda 3 - Parsowanie dynamicznej zawartości za pomocą Selenium

Gdy SERP zawiera karuzele, interaktywne bloki lub treści renderowane przez JavaScript, przełącz się na 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"))

Patrz oficjalne Dokumenty Selenium dla instalacji sterowników i przykładów WebDriverWait.

Praktyczne rozwiązanie: Strategia analizowania i przykładowy kod

W celu ostatecznej implementacji wykonamy scraping Bing bezpośrednio z HTML:

  1. Wysyłanie żądań HTTP do https://www.bing.com/search.
  2. Ustawienie agenta użytkownika.
  3. Analizuj HTML za pomocą BeautifulSoup + lxml, aby wyodrębnić tytuły, adresy URL i fragmenty.

W ten sposób nie potrzebujesz kont Microsoft i nie jesteś powiązany z płatnymi interfejsami API innych firm. Do wyboru wyników używamy kontenera result-card li.b_algo, który jest powszechnie używany dla bloków organicznych Bing.

Przykład roboczy (paginacja, opóźnienia, opcjonalne proxy)

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

Przykładowe użycie z dodatkowymi parametrami i proxy:

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

Co robi skrypt:

  1. Wysyła żądania GET do Bing z kontrolowanymi parametrami (q, count, first) i ustawieniami regionalnymi (cc, setlang).
  2. Zastępuje User-Agent i dodaje Accept-Language dla bardziej stabilnych fragmentów.
  3. Parsuje HTML za pomocą BeautifulSoup(..., "lxml"), lokalizuje karty wyników li.b_algo i wyodrębnia tytuł, adres url i fragment. Selektory CSS .select() w BS4 są standardowym, elastycznym podejściem.
  4. Obsługuje opcjonalny serwer proxy. W przypadku żądań prawidłowym formatem proxy jest mapowanie protokół→URL.

Wskazówki dotyczące stabilności:

  • Dodaj pauzy (losowe odstępy między żądaniami).
  • Zmień User-Agent (dynamicznie lub z listy). Requests pokazuje, jak poprawnie ustawić nagłówki - robimy to w przykładzie roboczym.
  • Korzystanie z infrastruktury proxy / rotacji IP w razie potrzeby w celu skutecznego skalowania w ramach ograniczeń platformy.
  • Utrzymuj ogólną liczbę żądań na rozsądnym poziomie i sprawdzaj odpowiedzi na monity CAPTCHA.
  • W przypadku złożonych scenariuszy warto rozważyć zarządzane interfejsy API SERP (Apify itp.), które obejmują infrastrukturę antybotową.

Gdzie przeczytać więcej o narzędziach

Wskazówka: Jeśli potrzebujesz infrastruktury proxy do bardziej stabilnego gromadzenia danych, sprawdź najlepsze serwery proxy dla Bing.

Jak uniknąć blokad podczas skrobania Bing

Kluczowe zasady zapewniające, że skrobaczka nie "umrze" podczas pierwszego cyklu:

  • Dodaj opóźnienia (losowe odstępy między żądaniami).
  • Zmień User-Agent (dynamicznie lub z własnej listy); prawidłowy sposób ustawiania nagłówków w żądaniach jest opisany w dokumentacji - używamy tego samego podejścia w naszym przykładzie roboczym.
  • Korzystaj z serwerów proxy lub rotacji adresów IP (przestrzegając warunków korzystania z usługi).
  • Ogranicz ogólną liczbę żądań i monitoruj odpowiedzi na monity CAPTCHA.
  • W przypadku złożonych zadań warto rozważyć zarządzane interfejsy API SERP (Apify itp.) z wbudowaną infrastrukturą antybotową.

Wnioski

Scraping Bing jest pomocny, gdy chcesz rozszerzyć badania poza Google, zebrać dodatkowe domeny dawców, śledzić alternatywne funkcje SERP i uzyskać niezależny widok krajobrazu. Aby uzyskać stabilną i "oficjalną" integrację, Microsoft promuje Grounding z Bing Search w Azure AI Agents; jest to bezpieczniejsze z punktu widzenia warunków świadczenia usług, ale nie zwraca surowych danych JSON SERP. Jeśli Twoim zadaniem jest wyodrębnienie ustrukturyzowanych wyników, wybierz bezpośrednie analizowanie HTML za pomocą Requests/BS4 lub Selenium albo użyj wyspecjalizowanego interfejsu API SERP. Wybierz narzędzie do pracy: szybkie analizowanie HTML dla prototypów, agenci dla odpowiedzi opartych na LLM i interfejsy API SERP do gromadzenia danych na większą skalę.

Komentarze:

0 komentarze