Cách thu thập kết quả tìm kiếm Bing bằng Python

Bình luận: 0

Phân tích web không chỉ giới hạn ở Google. Bing cung cấp một góc nhìn khác về SERP, hữu ích cho nghiên cứu SEO, tìm kiếm liên kết, theo dõi thương hiệu, phân tích đối thủ và nghiên cứu nội dung. Python là công cụ lý tưởng cho dạng tự động hóa này: hệ sinh thái trưởng thành, cú pháp đơn giản, và các thư viện mạnh mẽ để phân tích HTML và xử lý JSON giúp bạn thu thập kết quả tìm kiếm Bing nhanh hơn và thuận tiện hơn.

Tại sao nên tập trung vào Bing thay vì Google?

Bing sử dụng bộ hướng dẫn xếp hạng và tín hiệu chất lượng riêng, vì vậy kết quả thường khác với Google. Điều này hữu ích để khám phá thêm cơ hội trong tìm kiếm tự nhiên và các truy vấn dài. Trong khuyến nghị cho webmaster, Bing nhấn mạnh mức độ liên quan, chất lượng/độ tin cậy, mức độ tương tác của người dùng, độ mới, yếu tố địa lý và tốc độ tải trang — một sự cân bằng tín hiệu khác so với Google. Đó là lý do tại sao một số trang xếp hạng cao hơn trên Bing.

Các trường hợp sử dụng thực tế khi thu thập kết quả tìm kiếm Bing:

  • Mở rộng danh sách liên kết xây dựng backlink — công cụ này đôi khi đưa lên top những trang không xuất hiện trong top 10 của Google.
  • Theo dõi PAA (“People also ask”) và các thành phần SERP phổ quát của Bing (video, carousel) để điều chỉnh chiến lược nội dung.

Bạn có thể trích xuất dữ liệu gì từ tìm kiếm Bing?

Từ một SERP “cổ điển”, bạn có thể trích xuất đáng tin cậy:

  • Tiêu đề;
  • URL (liên kết tài liệu);
  • Đoạn mô tả (snippet);
  • Vị trí trong kết quả (chỉ số thứ tự);
  • Một số kết quả phổ quát: “Related/People also ask”, kết quả hình ảnh/video nhúng (khi được hiển thị trực tiếp trong SERP chính).

Quan trọng: cấu trúc markup của Bing thay đổi theo thời gian, vì vậy các bộ chọn trong mã bên dưới có thể cần điều chỉnh.

Các yếu tố pháp lý và đạo đức khi thu thập dữ liệu tìm kiếm Bing

  • Tuân thủ Điều khoản sử dụng của Microsoft: để truy cập “chính thức” dữ liệu web, Microsoft hiện cung cấp Grounding with Bing Search như một phần của Azure AI Agents. API tìm kiếm Bing công khai đã bị ngừng hoàn toàn vào ngày 11/08/2025.
  • Grounding with Bing Search có điều khoản và giới hạn riêng: được sử dụng thông qua các agent Azure và kết quả được trả về trong phản hồi của agent thay vì “raw” JSON SERP.
  • Tôn trọng robots.txt và tránh làm quá tải máy chủ — tuân thủ robots là nguyên tắc đạo đức cơ bản khi scraping.

Thiết lập môi trường Python để thu thập dữ liệu

Cài đặt các thành phần cơ bản:

pip install requests beautifulsoup4 lxml fake-useragent selenium
  • requests — HTTP client (cho phép bạn đặt các header như User-Agent);
  • beautifulsoup4 + lxml — phân tích cú pháp HTML;
  • fake-useragent — tạo UA ngẫu nhiên (hoặc tự xây dựng danh sách của bạn);
  • selenium — render các khối nội dung động khi cần thiết.

Phương pháp 1 – Thu thập dữ liệu Bing bằng Requests và BeautifulSoup

Chúng tôi sẽ sử dụng phần này làm cơ sở để minh họa luồng xử lý: gửi yêu cầu GET, đặt User-Agent, phân tích các thẻ kết quả và thu thập tiêu đề, URL, đoạn mô tả và vị trí.

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

Giải thích:

  • Sử dụng các tham số count/first để phân trang.
  • Các bộ chọn li.b_algo h2 a và .b_caption p là cơ bản; bố cục có thể thay đổi (hãy kiểm tra trong DevTools).
  • Thêm proxy khi cần thiết và điều chỉnh thời gian tạm dừng giữa các yêu cầu.
  • Chúng tôi sẽ cải thiện ví dụ này một chút ở bên dưới, vì đây là cách hiệu quả nhất trong điều kiện hiện tại.

Phương pháp 2 — Thu thập kết quả tìm kiếm Bing qua API (tình trạng năm 2025)

API thu thập dữ liệu Bing công khai của Microsoft đã bị ngừng vào tháng 8 năm 2025. Microsoft khuyến nghị chuyển sang Grounding with Bing Search trong Azure AI Agents.

Điều này có nghĩa gì trong thực tế

  • Endpoint REST cổ điển với dữ liệu SERP dạng JSON “thô” không còn khả dụng cho phần lớn nhà phát triển.
  • Grounding with Bing Search được kết nối như một công cụ trong một agent Azure; agent có thể “tra cứu” web và trả về câu trả lời tổng hợp. Dịch vụ có TOU và các đặc điểm riêng: nó không được thiết kế để thu thập hàng loạt SERP thô.

Giải pháp thay thế cho SERP thô dạng JSON

Sử dụng API/ nền tảng SERP của bên thứ ba (ví dụ: Apify Bing Search Scraper) để nhận kết quả có cấu trúc: tiêu đề, URL, đoạn mô tả, vị trí, v.v.

Ví dụ yêu cầu tối thiểu với 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']

Tài liệu của Apify hỗ trợ kết quả tự nhiên, PAA, truy vấn liên quan và nhiều nội dung khác. Hãy đảm bảo rằng trường hợp sử dụng của bạn tuân thủ quy định của nền tảng và luật pháp tại khu vực bạn sinh sống.

Mẹo: Nếu bạn làm việc trong hệ thống Azure AI Agents và chỉ cần các tham chiếu đã được kiểm chứng cho LLM (thay vì JSON thô), hãy đọc hướng dẫn về Grounding with Bing Search.

Phương pháp 3 – Phân tích nội dung động bằng Selenium

Khi SERP bao gồm các carousel, khối tương tác hoặc nội dung được render bằng JavaScript, hãy chuyển sang 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"))

Tham khảo tài liệu chính thức của Selenium để cài đặt driver và xem các ví dụ về WebDriverWait.

Giải pháp thực tiễn: Chiến lược phân tích và mã ví dụ

Trong phần triển khai cuối cùng, chúng ta sẽ thực hiện việc thu thập dữ liệu Bing trực tiếp từ HTML:

  1. Gửi các yêu cầu HTTP tới https://www.bing.com/search.
  2. Thiết lập User-Agent.
  3. Phân tích HTML bằng BeautifulSoup + lxml để trích xuất tiêu đề, URL và đoạn mô tả.

Cách này giúp bạn không cần tài khoản Microsoft và không bị phụ thuộc vào API trả phí của bên thứ ba. Để chọn kết quả, chúng ta sử dụng thẻ chứa kết quả li.b_algo, vốn thường được Bing dùng cho các khối kết quả tự nhiên.

Ví dụ hoạt động (phân trang, độ trễ, proxy tùy chọn)

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

Ví dụ sử dụng với tham số bổ sung và proxy:

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

Script thực hiện những gì:

  1. Gửi các yêu cầu GET đến Bing với tham số kiểm soát (q, count, first) và thiết lập ngôn ngữ/khu vực (cc, setlang).
  2. Ghi đè User-Agent và thêm Accept-Language để có snippet ổn định hơn.
  3. Phân tích HTML bằng BeautifulSoup(..., "lxml"), xác định các thẻ kết quả li.b_algo và trích xuất tiêu đề, URL và đoạn mô tả. Bộ chọn CSS .select() trong BS4 là cách linh hoạt và tiêu chuẩn.
  4. Hỗ trợ proxy tùy chọn. Với Requests, định dạng proxy đúng là ánh xạ protocol→URL.

Mẹo tăng độ ổn định:

  • Thêm độ trễ (ngẫu nhiên hóa thời gian giữa các yêu cầu).
  • Luân phiên User-Agent (tự động hoặc từ danh sách của bạn). Requests chỉ cách đặt header đúng — chúng tôi sử dụng cách đó trong ví dụ.
  • Sử dụng hạ tầng proxy/luân phiên IP khi cần để mở rộng trong giới hạn của nền tảng.
  • Giữ tổng số lượng yêu cầu ở mức hợp lý và kiểm tra phản hồi xem có CAPTCHA hay không.
  • Với các tình huống phức tạp, hãy cân nhắc các SERP API được quản lý (Apify, v.v.) có tích hợp chống bot.

Tài liệu tham khảo công cụ

Mẹo: Nếu bạn cần hạ tầng proxy để thu thập dữ liệu ổn định hơn, hãy xem các proxy tốt nhất cho Bing.

Cách tránh bị chặn khi thu thập dữ liệu Bing

Những nguyên tắc chính để đảm bảo scraper của bạn không “chết” ngay vòng đầu tiên:

  • Thêm độ trễ (ngẫu nhiên hóa thời gian giữa các yêu cầu).
  • Luân phiên User-Agent (tự động hoặc từ danh sách của bạn); cách thiết lập header đúng được mô tả trong tài liệu — và chúng tôi áp dụng đúng trong ví dụ.
  • Sử dụng proxy hoặc luân phiên IP (tôn trọng điều khoản sử dụng của dịch vụ).
  • Giới hạn tổng số yêu cầu và theo dõi phản hồi để nhận biết CAPTCHA.
  • Với tác vụ phức tạp, xem xét SERP API có tích hợp chống bot (Apify, v.v.).

Kết luận

Thu thập dữ liệu Bing hữu ích khi bạn muốn mở rộng nghiên cứu ngoài Google, thu thập thêm domain nguồn, theo dõi các thành phần SERP thay thế và có cái nhìn độc lập về toàn cảnh. Đối với việc tích hợp ổn định và “chính thức”, Microsoft khuyến nghị Grounding with Bing Search trong Azure AI Agents; đây là cách an toàn hơn về mặt điều khoản dịch vụ nhưng không trả lại JSON SERP thô. Nếu nhiệm vụ của bạn là trích xuất dữ liệu có cấu trúc, hãy chọn phân tích HTML trực tiếp bằng Requests/BS4 hoặc Selenium, hoặc sử dụng SERP API chuyên dụng. Chọn công cụ phù hợp: phân tích HTML nhanh cho nguyên mẫu, agent cho câu trả lời dựa trên LLM, và SERP API cho thu thập quy mô lớn.

Bình luận:

0 Bình luận