Как парсить поиск Bing с помощью Python

Комментарии: 0

Веб‑аналитика не ограничивается Google: Bing дает альтернативный срез поисковой выдачи, полезный для SEO‑исследований, сбора ссылок, мониторинга брендов, конкурентного анализа и контент‑ресерча. Python – отличный инструмент для такой автоматизации: богатая экосистема, понятный синтаксис и зрелые библиотеки для html‑парсинга и работы с json делают парсинг результатов поиска Bing удобным и быстрым.

Почему стоит парсить именно Bing, а не Google?

Bing использует собственные принципы ранжирования и сигналы качества, поэтому результаты часто отличаются от Google. Это помогает находить дополнительные возможности в органическом поиске и длинном хвосте запросов (long‑tail). В руководствах для вебмастеров Bing подчеркивает релевантность, качество/доверие, вовлеченность пользователей, свежесть, геофакторы и скорость страницы — баланс сигналов иной, чем у Google. Поэтому некоторые страницы показываются выше именно в Bing.

Практические кейсы парсинга результатов поиска Bing:

  • Расширение списка доноров для линкбилдинга: эта поисковая система иногда выводит в ТОП сайты, которых нет в ТОП‑10 Google.
  • Отслеживание PAA‑блоков («People also ask») и универсальных элементов выдачи Bing (видео, карусели), чтобы корректировать контент‑стратегию.

Какие данные можно получить из поиска Bing

Из «обычной» SERP реально извлечь:

  • Заголовок (title);
  • URL (ссылка на документ);
  • Сниппет (описание);
  • Позицию в выдаче (порядковый номер);
  • Некоторые элементы универсальной выдачи: блоки «Related/People also ask», результаты изображений/видео (когда они встроены в общую SERP).

Важно: верстка Bing периодически меняется, поэтому селекторы в коде ниже могут потребовать корректировок.

Правовые и этические аспекты парсинга результатов поиска Bing

  • Соблюдайте условия использования Microsoft: для «официального» доступа к веб‑данным Microsoft теперь предлагает Grounding with Bing Search в составе Azure AI Agents. Публичные Bing Search API полностью выведены из эксплуатации 11 августа 2025 года.
  • Grounding with Bing Search имеет собственные условия (TOU) и ограничения: сервис используется через агентов Azure, а результаты возвращаются в ответах агента, а не как «сырой» json с SERP‑данными.
  • Уважайте robots.txt и избегайте чрезмерной нагрузки на хосты — это базовая этика парсинга.

Настройка окружения Python для парсинга

Установите базовые пакеты:


pip install requests beautifulsoup4 lxml fake-useragent selenium
  • requests — HTTP‑клиент (можно добавить заголовки, например User‑Agent).
  • beautifulsoup4 + lxml — парсинг HTML.
  • fake‑useragent — генерация случайного UA (или соберите собственный пул).
  • selenium — рендеринг динамических блоков, когда это необходимо.

Метод 1 — парсинг Bing через Requests и BeautifulSoup

Возьмем этот вариант за основу, чтобы показать алгоритм: выполняем GET‑запросы, подменяем User‑Agent, парсим карточки результатов и собираем title, URL, сниппет и позиции.


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

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

HEADERS_POOL = [
    # Можно добавить больше — или использовать 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]:
    """
    Возвращает список результатов: title, url, snippet, position.
first — с какой позиции начинать (пагинация), count — сколько записей.

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

    # Типичная разметка Bing: <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")
        # Сниппет часто в .b_caption p или просто первый <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")

Пояснения:

  • Используем параметры count/first для пагинации.
  • Селекторы li.b_algo h2 a и .b_caption p — базовые; иногда верстка меняется (проверяйте в DevTools).
  • При необходимости добавьте прокси и регулируйте паузы между запросами.
  • Важно: ниже мы улучшим и расширим этот пример — он наиболее эффективен для наших задач в текущих условиях.

Метод 2 — парсинг результатов поиска Bing через API (актуальное состояние в 2025)

Публичный парсер Bing через Web Search API был прекращен 11 августа 2025 года. Microsoft официально рекомендует миграцию на Grounding with Bing Search в составе Azure AI Agents.

Что это означает на практике

  • Классический REST‑эндпойнт с «чистым» json SERP‑данных больше недоступен для большинства разработчиков.
  • Grounding with Bing Search подключается как инструмент внутри агента Azure; агент может «подсматривать» в веб и возвращать обобщенный ответ. У сервиса отдельные условия (TOU) и специфика: он не предназначен для массового снятия «сырых» результатов.

Альтернатива для «сырого» SERP в json

Воспользуйтесь сторонними SERP‑API/платформами (например, Apify Bing Search Scraper), которые возвращают структурированные результаты: заголовок, URL, сниппет, позицию и др. Минимальный пример запроса к Apify:


import requests

API_TOKEN = "apify_xxx"  # сохраните в 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()
# Получить данные из key-value store (по run['data']['defaultDatasetId'])

Apify документированно поддерживает органические результаты, PAA, связанные запросы и т. п. Убедитесь, что ваш use‑case соответствует правилам площадки и законам вашей юрисдикции.

Важно: если вы работаете со стеком Azure AI Agents и вам достаточно «справочных» ответов для LLM (а не сырого json), посмотрите гайд по Grounding with Bing Search.

Метод 3 — парсинг динамического контента с помощью Selenium

Когда в выдаче появляются карусели, интерактивные блоки или контент рендерится JavaScript‑ом, подключайте 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()

        # Ожидания можно добавить через 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"))

Документация Selenium с примерами установки драйверов и WebDriverWait — в официальных гайдах.

Решение на практике: разбор и пример кода

Выбранный нами подход для итоговой реализации парсит Bing напрямую из HTML:

  1. Отправляем HTTP‑запросы к https://www.bing.com/search.
  2. Подставляем User‑Agent.
  3. Разбираем HTML через BeautifulSoup + lxml и извлекаем заголовки, URL и сниппеты.

Благодаря этому подходу мы не регистрируемся в сервисах Microsoft и не зависим от сторонних платных API. Для выборки используем контейнер карточки выдачи li.b_algo — он часто встречается при обработке SERP Bing.

Рабочий пример кода (пагинация, задержки, опция прокси)


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"

# Пул юзер‑агентов
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:
    """Создает сессию с базовыми заголовками и необязательным прокси."""
    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: {'http': 'http://host:port', 'https': 'http://host:port'}
        s.proxies.update({"http": proxy, "https": proxy})
    return s


def _soup_with_fallback(html: str) -> BeautifulSoup:
    """Разбор HTML с «поблажливой» цепочкой парсеров: lxml -> html.parser -> html5lib (если установлен)."""
    for parser in ("lxml", "html.parser", "html5lib"):
        try:
            return BeautifulSoup(html, parser)
        except FeatureNotFound:
            continue
    # Если ни один не доступен, bs4 сгенерирует исключение; пропускаем его дальше
    return BeautifulSoup(html, "html.parser")


def parse_serp_html(html: str, start_pos: int) -> List[SerpItem]:
    """Извлекаем органические результаты из HTML выдачи Bing."""
    soup = _soup_with_fallback(html)
    items: List[SerpItem] = []

    # Органические блоки обычно выглядят как <li class="b_algo"> с h2>a и фрагментом под .b_caption p или первым <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]:
    """Загружает одну страницу выдачи и возвращает разобранные элементы."""
    params = {
        "q": query,
        "count": count,   # 10, 15, 20...
        "first": first,   # 1, 11, 21...
        "cc": cc,         # код страны для результатов
        "setlang": setlang,  # язык интерфейса/сниппетов
    }
    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]:
    """Листает страницы выдачи и возвращает агрегированный список результатов."""
    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))  # вежливая задержка
        first += count
    return all_items


def _normalize_cell(s: str) -> str:
    """Необязательно: сжимает внутренние пробелы, чтобы в простых просмотрщиках ячейки были в одну строку."""
    # Преобразует табы/переносы строки/множественные пробелы в один пробел
    return " ".join((s or "").split())


def save_csv(
    items: List[SerpItem],
    path: str,
    excel_friendly: bool = False,
    normalize: bool = False,
    delimiter: str = ",",
) -> int:
    """
Записывает результаты в CSV.

— excel_friendly=True -> записывает UTF‑8 с BOM (utf‑8‑sig), чтобы Excel автоматически распознал Unicode.

— normalize=True -> сжимает пробелы внутри строковых полей.

— delimiter -> измените, если ваш потребитель ожидает ';' и т. п.

Возвращает количество записанных строк (без заголовка).

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

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

    # Параметр newline='' нужен, чтобы модуль csv в Python корректно обрабатывал переводы строк на всех платформах
    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="Поисковой запрос")
    ap.add_argument("--pages", type=int, default=1, help="Количество страниц (x count)")
    ap.add_argument("--count", type=int, default=10, help="Результатов на страницу")
    ap.add_argument("--cc", default="UA", help="Код страны результатов (cc)")
    ap.add_argument("--setlang", default="uk", help="Язык интерфейса/сниппетов (setlang)")
    ap.add_argument("--proxy", help="Прокси, напр. http://user:pass@host:port")
    ap.add_argument("--csv", help="Путь к CSV (сохранить результаты)")
    ap.add_argument(
        "--excel-friendly",
        action="store_true",
        help="Добавить BOM (UTF‑8‑SIG), чтобы Excel корректно открыл файл",
    )
    ap.add_argument(
        "--normalize-cells",
        action="store_true",
        help="Убрать переносы строк и лишние пробелы в ячейках",
    )
    ap.add_argument(
        "--delimiter",
        default=",",
        help="Разделитель CSV (по умолчанию ','); например: ';'",
    )
    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())

Пример использования скрипта с дополнительными параметрами и прокси:


python bing_scraper.py -q "веб скрейпинг на python" --pages 3 --csv out.csv \
  --proxy "http://username:password@proxy:port"

Что делает скрипт:

  1. Отправляет GET‑запросы к Bing с управляемыми параметрами (q, count, first) и языково‑региональными настройками (cc, setlang).
  2. Подменяет User‑Agent и добавляет Accept‑Language для стабильных сниппетов.
  3. Разбирает HTML через BeautifulSoup("lxml"), ищет карточки li.b_algo и извлекает title, url, snippet. Селекторы .select() — стандартный и гибкий способ в BS4.
  4. Поддерживает прокси (опционально). Корректный формат задавания IP‑прокси в Requests — словарь протоколов → URL прокси.

Советы для стабильности:

  • Ставьте таймауты и делайте случайные паузы между запросами (Requests поддерживает timeout, сессии и собственные заголовки).
  • При необходимости добавьте повторы/бекофф и ротацию User‑Agent на каждом цикле.
  • Если разметка b_algo изменится, проверьте DOM в DevTools и обновите селекторы (например, #b_results h2 > a или альтернативные блоки SERP).

Где посмотреть детали по инструментам

Совет: если вам нужна инфраструктура прокси для более устойчивого сбора, рассматривайте решения такие как прокси для Bing, которые помогают масштабировать ресурсы в рамках ограничений платформы.

Как избежать капчи и сбоев при парсинге Bing

Базовые принципы, чтобы парсер не «падал» в первом цикле:

  • Добавляйте паузы (рандомизируйте интервалы между запросами).
  • Ротируйте User‑Agent (динамически или из собственного списка); корректную установку UA в requests мы используем в рабочем примере.
  • Используйте прокси/IP‑ротацию (с уважением к правилам сервисов) — это помогает масштабировать сбор данных в пределах ограничений платформы.
  • Ограничивайте общее количество запросов и анализируйте ответы на предмет CAPTCHA.
  • В сложных сценариях рассмотрите управляемые SERP‑API (Apify и др.) с встроенной антибот‑инфраструктурой.

Скрейпинг Bing: Вывод

Парсинг результатов поиска Bing уместен, когда нужно расширить исследование за пределы Google, собрать дополнительные домены‑доноры, отслеживать иные SERP‑фичи и получить независимую картину. Для стабильной и «официальной» интеграции Microsoft продвигает Grounding with Bing Search в Azure AI Agents; это безопаснее с точки зрения правил, но не предоставляет «сырой» json SERP. Если задача — снять структуру результатов, используйте парсинг поиска Bing напрямую через Requests/BS4 или Selenium либо специализированные SERP‑API. Выбирайте инструмент под цель: быстрый HTML‑парсинг Bing для прототипов, «агенты» — для LLM‑ответов, SERP‑API — для масштабируемого сбора.

Комментарии:

0 комментариев