ua
English
Español
中國人
Tiếng Việt
Deutsch
Português
Français
भारतीय
Türkçe
한국인
Italiano
Gaeilge
اردو
Indonesia
Polski Веб-аналітика не обмежується 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 у вигляді Bing Web Search API від Microsoft було скасовано 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; це безпечніше з точки зору правил, але не надає «сирих» SERP у json. Якщо завдання — «зняти» структуру результатів, оберіть парсинг пошуку Bing напряму через Requests/BS4 або Selenium, або використайте спеціалізовані SERP-API. Обирайте інструмент під ціль: швидкий парсинг Bing HTML для прототипів, «агенти» — для LLM-відповідей, SERP-API — для масштабованого збору.
Коментарі: 0