

# クエリと順位の証拠を保ちながらBing検索のエクスポートデータを統合する方法

検索結果の観測記録と対象アドレスを別々のテーブルにまとめることで、クエリ、順位、時刻の証拠を保てます。Actorは、ホスト環境で動くデータ収集プログラムです。Taskは、保存された入力設定です。canonicalUrlは、返された正規化済みの候補アドレスであり、確認済みのHTMLのcanonical URLでも、元のhrefでもありません。スニペットは検索結果に含まれるテキストであり、取得した記事本文ではありません。

Apify Google Search Scraperのビルド1.0.15を設定するには、保存済みのサンプルクエリのリストと検索エンジンのリストを、それぞれ目的の検索語句と`["bing"]`に丸ごと置き換えます。入力JSONは次のとおりです。`{"queries": ["Python asyncio documentation"], "engines": ["bing"], "maxResultsPerExecutedQuery": 2, "country": "US", "language": "en", "safeSearch": true, "includeSponsoredResults": false}`。公開ビルド1.0.15は複数の検索エンジンに対応していますが、ここで限定的な検証を通過しているのはBingのみです。Google、Brave、DuckDuckGoの処理経路は保留または対象外です。対照テストのOUTPUTにある実行サマリーには、次の件数が報告されています。`queriesRequested`: 1、`searchesCompiled`: 1、`executionsPlanned`: 1、`enginesSuccessful`: 1、`enginesFailed`: 0、`pagesAttempted`: 1、`pagesParsed`: 1、`resultsSaved`: 2、`engineStatusCounts`: {"bing": {"ok": 1}}、`engineResultCounts`: {"bing": 2}、`queryResultCounts`: {"Python asyncio documentation": 2}、`stopReason`: "result_caps_reached"。これらの実行サマリーの件数は、スクレイパーの実行全体の集計であり、対象ごとの行ではありません。この結果により、限定された範囲でBingのURL、順位、出典情報を扱えることが確認されました。検証を通過していない他の検索エンジン、他の市場、リンク先の取得は確認されていません。

## 1. 収集ルールを設定する

1. 保存済みのクエリと検索エンジンのリスト全体を、目的の検索語句1つと`["bing"]`に置き換えて、収集ルールを設定します。`maxResultsPerExecutedQuery: 2`、国はUS、言語は英語、`safeSearch: true`、`includeSponsoredResults: false`を指定します。公開されているページ数上限の入力項目については、指定しません。
2. Bingの検索結果の生データ行とOUTPUTのメタデータを保存します。標準の行フィールドには、`runId`、`country`、`language`、`safeSearch`、`engine`、`executedQuery`、`page`、`rankOnPage`、`rank`、`title`、`canonicalUrl`、`resultDomain`、`snippet`、`isSponsored`、`advertiserDomain`、`observedAt`、`schemaVersion`があります。実行と設定を完全に特定できる情報を保持します。
3. `runId` + `engine` + `executedQuery` + `page` + `rankOnPage`を使って観測キーを作成します。rankとobservedAtは証拠として保持します。完全に重複するエクスポート行を作業用テーブルから取り除き、重複の処理記録を残します。同じキーに異なるURL値がある場合は、黙って一方を選んだり、多数の行にある値を信頼したりせず、両方の候補を保留して元データを確認します。
4. 返された`canonicalUrl`から対象キーを作成します。これは正規化済みの候補アドレスであり、HTMLのcanonical URLであることの証拠でも、元のhrefでもありません。まず、返されたURL文字列をそのまま使います。ホスト名の大文字と小文字、フラグメント、トラッキングパラメーターを正規化する場合は、リンク先が変わらないと判断した理由を記録します。パスやクエリパラメーターが異なるものは別々に扱います。
5. 対象キー列を使って、観測記録を対象テーブルに関連付けます。同じURLが2つのクエリや2つの時点で見つかった場合は、複数の観測記録と、確認済みの対象1件を持つことになります。どのクエリに属するかは、実際の観測記録だけに基づいて決めます。
6. 作業用テーブルは、データを損なわないようにエクスポートします。クエリに含まれる引用符、カンマ、改行、Unicode文字を保持します。CSVのエクスポート機能やインポート機能ではnullと空の値の区別が失われる場合があるため、生のJSONまたはJSONLを主な参照データにします。大規模なエクスポートデータを統合する前に、小さなテストデータを使い、書き出して読み戻しても値が完全に一致することを確認します。
7. 生データ行、重複したエクスポート行、重複を除いた有効な観測記録、矛盾、重複を除いた対象キーは、それぞれ別の件数として報告します。重複を除いたURLやクエリとの対応関係から、総検索数やすべてのウェブサイトを推定することはできません。失敗した検索、結果が空だった検索、上限に達した検索も収集記録に残します。
8. 対象アドレスと観測記録の参照情報を、[別工程のリンク先確認](/ja/articles/bing-%E6%A4%9C%E7%B4%A2%E7%B5%90%E6%9E%9C%E3%81%8B%E3%82%89%E3%81%AE%E3%82%AF%E3%83%AD%E3%83%BC%E3%83%AB%E3%82%AD%E3%83%A5%E3%83%BC%E4%BD%9C%E6%88%90/)に渡します。リソースを統合しただけでは、ページにアクセスできること、関連性、情報源の信頼性は証明できません。対象範囲がBingであることを明示します。

## 架空の統合台帳とデータ確認

*注：以下の表とレコードは、学習用に独自に作成した架空のものです。*

次のようなテストを想定します。実行R1でクエリ「AI agents」を使うと、順位1にU1、順位2にU2が得られます。実行R1でクエリ「small business automation」を使うと、順位1にU2、順位2にU3が得られます。実行R2でクエリ「AI agents」を使うと、順位1にU1、順位2にU4が得られます。すべて、同じと明示された設定で得られたBingの結果であり、それぞれの観測時刻があります。6個の観測キーが、U1、U2、U3、U4という4個の対象キーに対応します。U1には2つの時点の観測記録があり、U2には2つのクエリの観測記録があります。どちらの繰り返しも、エクスポート行の重複ではありません。

7行目の生データは、R1 / クエリ「AI agents」 / ページ1 / 順位2のU2を完全に繰り返しています。元の生データの値をすべてそのままコピーした、重複したエクスポート行です。8行目は同じキーを持ちますが、値はU9です。キーR1+bing+AI agents+1+2では、U2とU9の両方が矛盾の対象になります。最初の値や多数の行にある値を信頼せず、両方の候補値を矛盾の管理台帳に保留します。生データ8行、重複したエクスポート1行、観測キー6個、矛盾のあるキー1個、矛盾のない観測記録5件、採用済みの対象4件を保持します。U9は採用済みの対象に含めません。

*独自に作成した架空の生データ8行の表。*

| RowId | RunId | Engine | ExecutedQuery | Page | RankOnPage | CanonicalUrl | ObservedAt |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 1 | R1 | bing | AI agents | 1 | 1 | U1 | 2026-10-13T06:20:15Z |
| 2 | R1 | bing | AI agents | 1 | 2 | U2 | 2026-10-13T06:20:15Z |
| 3 | R1 | bing | small business automation | 1 | 1 | U2 | 2026-10-13T06:21:00Z |
| 4 | R1 | bing | small business automation | 1 | 2 | U3 | 2026-10-13T06:21:00Z |
| 5 | R2 | bing | AI agents | 1 | 1 | U1 | 2026-10-13T07:00:15Z |
| 6 | R2 | bing | AI agents | 1 | 2 | U4 | 2026-10-13T07:00:15Z |
| 7 | R1 | bing | AI agents | 1 | 2 | U2 | 2026-10-13T06:20:15Z |
| 8 | R1 | bing | AI agents | 1 | 2 | U9 | 2026-10-13T06:20:15Z |

*独自に作成した架空のキー6個の状態表。*

| ObservationKey | CandidateCanonicalUrl | 状態 |
| --- | --- | --- |
| R1+bing+AI agents+1+1 | U1 | 矛盾なし |
| R1+bing+AI agents+1+2 | U2, U9 | 矛盾のため保留（両方の候補を保留。7行目はエクスポートの重複を示す記録） |
| R1+bing+small business automation+1+1 | U2 | 矛盾なし |
| R1+bing+small business automation+1+2 | U3 | 矛盾なし |
| R2+bing+AI agents+1+1 | U1 | 矛盾なし |
| R2+bing+AI agents+1+2 | U4 | 矛盾なし |

*独自に作成した架空の対象参照表（採用済みの対象4件）。*

| TargetKey | 矛盾のない観測記録の参照情報 | 状態 |
| --- | --- | --- |
| U1 | R1のAI agentsの順位1、R2のAI agentsの順位1 | 矛盾なし |
| U2 | R1のsmall business automationの順位1 | 矛盾なし（R1のAI agentsの順位2は矛盾のため保留） |
| U3 | R1のsmall business automationの順位2 | 矛盾なし |
| U4 | R2のAI agentsの順位2 | 矛盾なし |

*独自に作成した架空のCSVテストデータ。埋め込み改行、二重にした引用符、Unicode文字を含むcafé、カンマを含むクエリを使用：*

コード例

```text
runId,engine,executedQuery,page,rankOnPage,title,canonicalUrl
"R1","bing","garden, tray",1,1,"Café guide
""Best picks""","https://example.com/garden"
```

架空の統合台帳とデータ確認

*テストデータを読み取った値と読者による確認：* クエリフィールドは`garden, tray`として読み取られます。タイトルフィールドは`Café guide`に続いて実際の改行が入り、その後の`Best picks`を1組の引用符で囲んだ値として読み取られます。canonicalUrlは、Markdown構文を含まない通常の文字列`https://example.com/garden`として読み取られます。読者が書き出しと読み戻しを確認することで、カンマによる区切り、埋め込み改行、1組の引用符、Unicode文字が正しく保持されることを確かめます。Pythonでの解析、書き出し、読み込みを完了したという記述があっても、その対象はこの架空のテストデータ1件だけです。Actorのエクスポートデータや大規模なデータセットには一切当てはまりません。

## 次に行うこと

公式の[Google Search Scraper Actor](https://apify.com/chronometrica/google-search-scraper)と[Export Search Results to CSV Task](https://apify.com/chronometrica/google-search-scraper/examples/export-search-results-to-csv)を使ってデータ収集の設定を見直し、Bingの観測記録を生データとして取得します。同じURLを取り除く前に、記録を突き合わせて整合性を確認してください。

## 出典と参考資料

- [スクレイピング対象のURLを探す作業を自動化する](https://www.reddit.com/r/learnpython/comments/f96o07/automate_the_process_of_finding_urls_for_scraping/)