0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PythonでBing検索をスクレイピングする方法

Last updated at Posted at 2025-10-31

この記事では、Python を使って Bing をスクレイピングする方法を解説します。キーワードや検索順位など、価値あるフィールドを取得する手順を示します。さっそく始めましょう! 🚀

更新日: 2025-10-31

主要ポイント

  • 高度な手法で Bing の検索を Python でスクレイピングし、SERP データ抽出や SEO 監視に活用する方法を解説します。
  • ブラウザのネットワークリクエストを傍受して Bing の API エンドポイントをリバースエンジニアリングし、JSON レスポンスを解析します。
  • 検索結果、関連キーワード、ランキング情報などの構造化データを抽出します。
  • ページネーション処理や検索パラメータ管理を実装して包括的な SERP 収集を行います。
  • 検出回避のためのプロキシローテーションとフィンガープリント管理を構成します(⚠️ ブロックや法的制限に注意)。
  • ScrapFly のような専用ツールを使うと、ブロッキング対策を組み込んだ自動化が容易になります(ツール名は参照用)。🔧
  • 信頼性を高めるためにデータ検証とエラーハンドリングを必ず実装してください。💡

最新の Bing スクレイパーコード

(元のリポジトリリンクは削除しました。必要であれば該当プロジェクトをブランド名や検索で参照してください。)

なぜ Bing をスクレイピングするのか

Bing はインデックス化の対象が広く、Google にインデックスされていないサイトを含むことがあります。したがって Bing をスクレイピングすることで、異なるデータソースや追加のインサイトにアクセスできます。

SEO の観点でも、Bing の検索結果を取得して競合の順位や使用キーワードを把握する用途は一般的です。さらに、Bing は Wikipedia のような人気サイトからの要約スニペットや AI による要約を SERP 上に表示することがあり、これらは元サイトを直接クロールする代わりに SERP から抽出できます。

プロジェクトのセットアップ

Bing をスクレイピングするために使う Python パッケージ

  • httpx:Bing 検索ページのリクエストと HTML 取得
  • playwright:動的に読み込まれるページ部分のスクレイピング
  • parsel:XPath / CSS セレクタでの HTML 解析
  • loguru:スクレイパーのログ監視
  • asyncio:非同期での実行によりスクレイピング速度を向上

asyncio は Python に同梱されているため、その他のパッケージをインストールします:

$ pip install httpx playwright parsel loguru

その後、Playwright のヘッドレスブラウザバイナリをインストールします:

$ playwright install

本ガイドは主に Bing の検索ページを対象としますが、示す概念は Google、DuckDuckGo、Kagi など他の検索サービスにも応用できます。

(補足: 実運用では robots.txt のポリシー、利用規約、地域ごとの法的要件を必ず確認してください。⚠️)

Bing 検索結果(SERP)のスクレイピング方法

まずは Bing の検索結果ページ(SERP)から順位を取得する基本的な流れを見ていきます。例えば "web scraping emails" のようなキーワードで検索すると、SERP は複数の検索結果要素を含んでいます。Bing の検索ページは HTML が動的かつクラス名が頻繁に変わるため、動的なクラス名に依存しない要素選択を心がけます。

def parse_serps(response: Response) -> List[Dict]:
    """parse SERPs from bing search pages"""
    selector = Selector(response.text)
    data = []
    
    if "first" not in response.context["url"]:
        position = 0
    else:
        position = int(response.context["url"].split("first=")[-1])
    
    for result in selector.xpath("//li[@class='b_algo']"):
        url = result.xpath(".//h2/a/@href").get()
        description = result.xpath("normalize-space(.//div/p)").extract_first()
        date = result.xpath(".//span[@class='news_dt']/text()").get()
        
        if data is not None and len(date) > 12:
            date_pattern = re.compile(r"\b\d{2}-\d{2}-\d{4}\b")
            date_pattern.findall(description)
            dates = date_pattern.findall(date)
            date = dates[0] if dates else None
        
        position += 1
        data.append({
            "position": position,
            "title": "".join(result.xpath(".//h2/a//text()").extract()),
            "url": url,
            "origin": result.xpath(".//div[@class='tptt']/text()").get(),
            "domain": url.split("https://")[-1].split("/")[0].replace("www.", "") if url else None,
            "description": description,
            "date": date,
        })
    
    return data

上のコードでは XPath セレクタを使い、順位(position)、タイトル、説明、リンク、ドメインなど SERP の基本要素を抽出しています。次に、このパース関数をリクエスト処理に組み込んで実際にデータを取得します。

import re
import asyncio
import json
from typing import List, Dict
from urllib.parse import urlencode
from httpx import AsyncClient, Response
from parsel import Selector
from loguru import logger as log

# initialize an async httpx client
client = AsyncClient(
    # enable http2
    http2=True,
    # add basic browser like headers to prevent being blocked
    headers={
        "Accept-Language": "en-US,en;q=0.9",  # get the search results in English
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "Accept-Encoding": "gzip, deflate, br",
    },
)

def parse_serps(response: Response) -> List[Dict]:
    """parse SERPs from bing search pages"""
    # rest of the function code

async def scrape_search(query: str):
    """scrape bing search pages"""
    url = f"https://www.bing.com/search?{urlencode({'q': query})}"
    log.info("scraping the first search page")
    response = await client.get(url)
    serp_data = parse_serps(response)
    log.success(f"scraped {len(serp_data)} search results from Bing search")
    return serp_data

実行のポイントを整理します。まず httpx の非同期クライアントを基本的なブラウザ風ヘッダで初期化してブロックされるリスクを下げています。Bing は複数の言語を返すため、Accept-Language ヘッダで英語を指定しています。scrape_search() 関数が最初の検索ページをリクエストし、先に定義した parse_serps() で HTML を解析しています。

上記のコードは最初の検索ページのみを取得します。次は複数ページをクロールする方法です。Bing の first パラメータを使ってページの開始インデックスを指定できます。たとえば最初のページが 0–9 のインデックスなら、次のページは first=10 で始まります。

# previous code remains the same

async def scrape_search(query: str, max_pages: int = None):
    """scrape bing search pages"""
    url = f"https://www.bing.com/search?{urlencode({'q': query})}"
    log.info("scraping the first search page")
    response = await client.get(url)
    serp_data = parse_serps(response)
    
    # new code starts from here
    log.info(f"scraping search pagination ({max_pages - 1} more pages)")
    total_results = (max_pages - 1) * 10  # each page contains 10 results
    other_pages = [
        client.get(url + f"&first={start}")
        for start in range(10, total_results + 10, 10)
    ]
    
    # scrape the remaining search pages concurrently
    for response in asyncio.as_completed(other_pages):
        response = await response
        data = parse_serps(response)
        serp_data.extend(data)
    
    log.success(f"scraped {len(serp_data)} search results from Bing search")
    return serp_data

ここでは first パラメータを用いて残りの検索ページをまとめてリスト化し、並列に取得しています。並列取得により高速に複数ページをクロールできますが、同時リクエスト数や間隔を調整してブロックやレート制限を避ける必要があります(後述のプロキシや待機戦略参照)。

サンプル出力

このサンプルでは SERP の基本データを正常に取得できています。次にキーワードデータの抽出方法を説明します。

Bing のキーワードデータをスクレイピングする方法

ユーザーが検索している語句やよくある質問は SEO のキーワードリサーチに不可欠です。Bing の検索ページには「関連クエリ」や FAQ のようなセクションがあり、ここからキーワードを取得できます。

まずは解析ロジックを定義します。前節と同様に XPath セレクタを使い、要素の属性に対して安定的にマッチングすることを目指します。

def parse_keywords(response: Response) -> Dict:
    """parse keyword data from bing search pages"""
    # rest of the function code

ここで parse_keywords 関数を定義し、関連クエリや FAQ を XPath で抽出します。通常、この種のキーワードデータは最初の検索ページに表示されるため、ページネーションは不要です。

import asyncio
import json
from typing import Dict
from urllib.parse import urlencode
from httpx import AsyncClient, Response
from parsel import Selector
from loguru import logger as log

# initialize an async httpx client
client = AsyncClient(
    # enable http2
    http2=True,
    # add basic browser like headers to prevent being blocked
    headers={
        "Accept-Language": "en-US,en;q=0.9",  # get the search results in English
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "accept-encoding": "gzip, deflate, br",
    },
)

def parse_keywords(response: Response) -> Dict:
    """parse keyword data from bing search pages"""
    # rest of the function code

async def scrape_keywords(query: str):
    """scrape bing search pages for keyword data"""
    url = f"https://www.bing.com/search?{urlencode({'q': query})}"
    log.info("scraping Bing search for keyword data")
    response = await client.get(url)
    keyword_data = parse_keywords(response)
    log.success(f"scraped {len(keyword_data)} keywords from Bing search")
    return keyword_data

実行すると、parse_keywords() が最初のページから関連クエリや FAQ を抜き出し、scrape_keywords() がそれを呼び出して結果を収集します。結果は JSON に保存可能です。

出力例

これで基本的な Bing スクレイパーは完成です。SERP、キーワード、リッチスニペットを検索ページの HTML から抽出できます。ただし、追加リクエストを行うとブロックされる可能性が高いため、対策を検討する必要があります。以下に実務で重要な追加事項と注意点をまとめます。🔧

実務で重要な追加事項(補足)

  • プロキシとローテーション

    • 複数の信頼できるプロキシを用意し、IP ローテーションを行うことで同一 IP に対するリクエスト集中を避けます。商用の回転プロキシや residential proxy が選択肢になります。
    • プロキシの応答速度や失敗率も監視対象にしてください。
  • ブラウザフィンガープリントとヘッダ管理

    • User-AgentAccept-LanguageAccept 等をランダム化またはそれらしい組み合わせにして、人間のブラウザに見えるようにします。
    • Playwright を用いる場合は、ヘッドレスモードとヘッドフルモードで挙動が異なる点に注意(サイト側の検知ロジックに依存)。
  • レート制御とバックオフ戦略

    • 過度な同時接続はブロックの原因になります。段階的なスロットリングと指数バックオフを実装してください。⚠️
    • エラーレスポンスや CAPTCHA 検出時は自動的に待機・再試行するロジックを設けます。
  • CAPTCHAs と検知回避

    • CAPTCHA が出現したら自動化では解決が難しいため、検知トリガーが発生する前に対策(リクエスト間隔・プロキシ・ヘッダ)で回避する方が現実的です。
  • データ検証と品質管理

    • 取得データは必ずスキーマ検証し、欠損値や誤抽出を検出するルールを入れてください。
    • タイトルや説明文から日付抽出を行う場合は正規表現や自然言語解析で誤抽出を減らします。
  • ロギングと監視

    • loguru などで成功/失敗率、レスポンスタイム、HTTP ステータス分布を記録し、異常検出アラートを設定します。📝

小さな実運用例(考慮すべきエッジケース)

  • 動的クラス名の変化
    • クラス名に頼らず、構造(h2 > a など)や意味的属性(aria-*)で選択する。
  • 地域・言語差異
    • 同じクエリでも地域やロケールによって結果が変わるため、必要に応じて mkt パラメータや Accept-Language を指定して地理的に分散して取得する。
  • ページ内の JavaScript 依存要素
    • FAQ やリッチスニペットが JS で動的に挿入される場合、Playwright によるレンダリングが必要になることがある。

(補足)運用前に必ず利用規約や各国の法規制を確認してください。法的に許可されない取得や大量のトラフィックで相手方に被害を与える行為は避けるべきです。

まとめ

この記事では、Python と主要パッケージを使った Bing のスクレイピング基礎を説明しました。主な流れは以下の通りです:

  • 非同期クライアントで検索ページを取得し、XPath/CSS で SERP をパースする
  • ページネーションを実装して複数ページを並列取得する
  • 関連キーワードやリッチスニペットも HTML から抽出可能
  • 実運用ではプロキシ、フィンガープリント、レート制御、エラーハンドリングが不可欠

最後に、運用に際してはサイトへの影響、法的要件、そしてブロッキング対策(プロキシ・ヘッダ・待機戦略)をしっかり設計してください。💡

(補足リソース)実運用でのブロック回避や大規模クロールに関するトピックとして、プロキシプロバイダの選定、CAPTCHA 対策、SRE 的な監視設計は別トピックで深掘りすると実務に役立ちます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?