Веб‑аналитика не ограничивается Google: Bing дает альтернативный срез поисковой выдачи, полезный для SEO‑исследований, сбора ссылок, мониторинга брендов, конкурентного анализа и контент‑ресерча. Python – отличный инструмент для такой автоматизации: богатая экосистема, понятный синтаксис и зрелые библиотеки для html‑парсинга и работы с json делают парсинг результатов поиска Bing удобным и быстрым.
Bing использует собственные принципы ранжирования и сигналы качества, поэтому результаты часто отличаются от Google. Это помогает находить дополнительные возможности в органическом поиске и длинном хвосте запросов (long‑tail). В руководствах для вебмастеров Bing подчеркивает релевантность, качество/доверие, вовлеченность пользователей, свежесть, геофакторы и скорость страницы — баланс сигналов иной, чем у Google. Поэтому некоторые страницы показываются выше именно в Bing.
Практические кейсы парсинга результатов поиска Bing:
Из «обычной» SERP реально извлечь:
Важно: верстка Bing периодически меняется, поэтому селекторы в коде ниже могут потребовать корректировок.
Установите базовые пакеты:
pip install requests beautifulsoup4 lxml fake-useragent selenium
Возьмем этот вариант за основу, чтобы показать алгоритм: выполняем 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")
Пояснения:
Публичный парсер Bing через Web Search API был прекращен 11 августа 2025 года. Microsoft официально рекомендует миграцию на Grounding with Bing Search в составе Azure AI Agents.
Что это означает на практике
Воспользуйтесь сторонними 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.
Когда в выдаче появляются карусели, интерактивные блоки или контент рендерится 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:
Благодаря этому подходу мы не регистрируемся в сервисах 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"
Что делает скрипт:
Советы для стабильности:
Где посмотреть детали по инструментам
Совет: если вам нужна инфраструктура прокси для более устойчивого сбора, рассматривайте решения такие как прокси для 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