ブログに戻ります

Google サーチ JSON を分析用に CSV へエクスポート

Michael Lee
Michael Lee

Expert Network Defense Engineer

15-Sep-2026

TL;DR:

  • Google検索結果のCSVには、行の横にクエリコンテキストが必要です。 リクエストと観察時間を保持することで、各結果がエクスポート後も解釈可能になります。
  • CSVはJSONの投影です。 生のキャプチャを保持し、欠落およびnull値を区別し、変換されたセルの意味を記録します。
  • ヘッダーのみのファイルには実行記録が必要です。 保留中、失敗、マッピングされていない、または空のキャプチャは、さまざまな理由で有機的な行を生成できません。

スプレッドシートはすべてのタイトルを保持することができますが、それを生成した検索の意味を失うことがあります。クエリ、送信されたコンテキスト、および観察時間がなければ、行の比較や追跡が難しくなります。空のセルは別の曖昧さを生み出します:値が欠落していたのか、nullであったのか、それとも変換によって拒否されたのか?

Scrapeless Google Search APIは、上流に構造化された検索データを提供します。このガイドでは、ローカルのPythonエクスポータを使用して保存されたキャプチャからGoogle検索結果のCSVを作成します。CSV生成、スプレッドシートの処理、ファイルストレージは、例のプログラムの機能であり、APIがこのエクスポート形式を直接返すという主張ではありません。

前提条件とキャプチャエンベロープ

エクスポータにはPythonと、requesthttp_status、およびresponseを含む保存されたJSONキャプチャが必要です。収集者がそれらを記録するときにはrun_idrequested_at、およびreceived_atを含めてください。これらの外部フィールドは、APIのネイティブレスポンスラッパーではなく、収集アプリケーションに属します。

Google検索リクエストワークフローは、HTTP 200タスクデータとHTTP 201保留中の作業を区別します。何かをフラット化する前に、レスポンスとともにHTTPの結果を保持してください。保留中のレスポンスから欠落している有機フィールドを読み取り、空のリストに置き換えると、その区別が失われます。

ローカル変換にはAPIキーやサードパーティパッケージは必要ありません。アカウントキャプチャを生成することは、別の認証されたステップであり、この記事のために実行されていません。ローカルチェックは合成キャプチャを使用してデータ型、空の状態、Unicode、およびスプレッドシートに敏感なテキストをテストします。

JSON値モデルは、配列、オブジェクト、null、および直接フラットセルにマッピングされない文字列を保持します。後で分析者が省略されたモジュールや正確な元の値を回復できるように、エクスポート後も元のキャプチャを利用可能にしておいてください。

どの列がコンテキストと意味を運ぶかを決定する

実行識別子、リクエストおよび受信時間、利用可能な場合は正確なクエリ、シリアライズされたリクエストを各結果行に繰り返し記録します。完全なリクエストは、固定の便利な列を超えた設定を保持し、エクスポートの監査を容易にします。

Google検索パラメータには国と言語の設定が含まれていますが、フルURLモードはurl内に設定を持つことができます。したがって、空のq列は必ずしも提出されたクエリが欠落していることを示すわけではありません。request_jsonを調査してください;空の便利なフィールドから推測して有効なクエリを再構築しないでください。

有機的な配列順序と返された位置は別々の列です。ordinalはアイテムのゼロベースのソース順序を記録します。positionは正の整数である場合にのみ保持され、ブーリアンは明示的に拒否されます。エクスポータは配列順序からランキングを作成しません。

テキストフィールドと位置には、それぞれ補完的な状態列があります。missingnull、およびvalueは一般的なケースを説明します。invalidは使用できない位置を示し、unexpected_typeはシリアライズされたJSONとして保持される非文字列テキストフィールド値を示します。

CSVの引用をスプレッドシートの解釈から分離する

CSVライターは区切り文字と引用されたテキストを処理します;スプレッドシートがセルをどのように評価するかを決定しません。手動で文字列を結合するのではなく、カンマ、引用符、および埋め込まれた改行のためにPythonのCSVライターを使用してください。

プログラムはnewline=""およびutf-8-sigで出力を開始します。最初の設定ではCSVモジュールがレコード境界を管理できるようにします。UTF-8シグネチャはスプレッドシートがアクセント付きまたは非ラテンテキストを認識するのを助けることがありますが、宛先アプリケーションのインポート動作はまだ検査が必要です。
数式文字で始まる値は、スプレッドシートで開かれると式として解釈される場合があります。 CSVインジェクションガイダンス は、構文的に正しいCSVだけでは信頼できないテキストを無効化するには不十分である理由を説明しています。

このエクスポーターは、選択した数式の先頭文字に対してアポストロフィを前置きします。これには全角のバリアント、先頭のタブや改行が含まれます。ポリシーはリクエストコンテキストと結果テキストの両方に適用されます。数値の位置は別の検証ルールを使用します。

Scrapelessでスクレイピングを開始

Scrapelessでウェブスクレイピングと自動化ワークフローを強化しましょう!
今日サインアップして、$5の無料クレジットをゲットしましょう — クレジットカードは不要

今すぐScrapelessダッシュボードで無料クレジットを請求してください。

ローカルJSONからCSVエクスポーターを実行

プログラムをserp_csv.pyとして保存し、python3 serp_csv.py capture.json organic.csvを実行します。キャプチャを読み取り、organic.csvとそのコンパニオンorganic.csv.run.jsonを書き込みます。既存の出力ファイルは置き換えられますので、専用のエクスポートディレクトリを選択するか、複数のバージョンを保持する際にはユニークな名前を付けてください。

プログラムは、入力キャプチャを上書きする出力パスを拒否します。元のJSONを変更したり、APIリクエストを送信したり、保留中のタスクを取得したりすることはありません。

python Copy
import argparse
import csv
import json
from pathlib import Path

FIELDS = ["run_id", "requested_at", "received_at", "q", "request_json", "ordinal",
          "position", "position_state", "title", "title_state", "link", "link_state",
          "snippet", "snippet_state"]

def spreadsheet_text(value):
    text = "" if value is None else str(value)
    stripped = text.lstrip()
    if (stripped.startswith(("=", "+", "-", "@", "=", "+", "-", "@"))
            or text.startswith(("\t", "\r", "\n"))):
        return "'" + text
    return text

def field(row, name):
    if name not in row:
        return "", "missing"
    value = row[name]
    if value is None:
        return "", "null"
    if name == "position":
        return (value, "value") if type(value) is int and value > 0 else ("", "invalid")
    if isinstance(value, str):
        return spreadsheet_text(value), "value"
    return spreadsheet_text(json.dumps(value, ensure_ascii=False)), "unexpected_type"

def export(source, target):
    source, target = Path(source), Path(target)
    sidecar = target.with_suffix(target.suffix + ".run.json")
    if source.resolve() in (target.resolve(), sidecar.resolve()):
        raise ValueError("Output paths must differ from input")
    record = json.loads(source.read_text(encoding="utf-8"))
    request = record.get("request")
    if not isinstance(request, dict) or not isinstance(request.get("input"), dict):
        raise ValueError("Expected a capture record with request.input")
    payload = record.get("response")
    rows = payload.get("organic_results") if isinstance(payload, dict) else None
    status = record.get("http_status")
    if status == 201:
        state, rows = "pending", []
    elif status != 200:
        state, rows = ("transport_error" if status is None else "http_error"), []
    elif not isinstance(rows, list) or any(not isinstance(x, dict) for x in rows):
        state, rows = "unmapped", []
    else:
        state = "observed" if rows else "empty"
    context = {
        "run_id": spreadsheet_text(record.get("run_id")),
        "requested_at": spreadsheet_text(record.get("requested_at")),
        "received_at": spreadsheet_text(record.get("received_at")),
        "q": spreadsheet_text(request["input"].get("q")),
        "request_json": spreadsheet_text(json.dumps(request, ensure_ascii=False, sort_keys=True)),
    }
    with target.open("w", encoding="utf-8-sig", newline="") as handle:
        writer = csv.DictWriter(handle, fieldnames=FIELDS)
        writer.writeheader()
        for ordinal, item in enumerate(rows):
            output = dict(context, ordinal=ordinal)
            for name in ("position", "title", "link", "snippet"):
                output[name], output[name + "_state"] = field(item, name)
            writer.writerow(output)
    sidecar.write_text(json.dumps({"source": str(source), "run_id": record.get("run_id"),
        "state": state, "rows": len(rows), "request": request,
        "requested_at": record.get("requested_at"), "received_at": record.get("received_at"),
        "export_policy": "spreadsheet_text_prefix_v1; original values remain in source JSON"},
        ensure_ascii=False, indent=2), encoding="utf-8")
    print(f"Exported {len(rows)} organic rows; state={state}; metadata={sidecar}")
    return state, len(rows)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("source")
    parser.add_argument("target")
    args = parser.parse_args()
    export(args.source, args.target)

サイドカーは、コレクションの状態、予測行数、ソースパス、リクエスト、タイムスタンプ、およびエクスポートポリシーを記録します。ヘッダーのみのCSVは、そのレコードが一緒にある限り説明可能です。エクスポートを他の人に渡す際は、両方の出力ファイルを一緒に保管してください。

NULLと不正なオーガニックデータを検査

存在する空のオーガニック配列はstate=emptyを生成します。HTTP 201はpendingを生成し、欠落しているHTTPステータスはtransport_errorになり、もう1つの非200ステータスはこのアプリケーションの状態モデルでhttp_errorになります。これらの結果は、意味を共有せずにゼロの行数を共有することがあります。

オーガニックフィールドが存在しない、配列でない、または非オブジェクトアイテムを含む場合、全体のプロジェクションはunmappedになります。その実行では、プログラムはオーガニック行をエクスポートしません。これにより、残りの行を完全なプロジェクションとして説明しつつ、不正なアイテムを静かに落とすことを避けています。

オプションのフィールドに対しては、より狭く処理されます。欠落したスニペットは空のセルを残し、オーガニックアイテムを破棄することなく明示的なフィールド状態を持ちます。予期しない複雑な値はJSONテキストとして表現され、異常な値を引き続き検査できます。

これらはアプリケーションポリシーです。エクスポートの近くに文書化し、消費者が別のスキーマを必要とする場合は慎重に見直してください。フィールド状態列は、別のアナリストがいくつかの可視セルから変換を推測する必要がないため、有用です。

スプレッドシートの引き渡しを確認

出力をCSVパーサーで読み取り、物理的行ではなく論理レコードを比較します。引用されたスニペットは、新しい検索結果を作成せずに改行を含む場合があります。サイドカーのカウントが解析された行と一致し、各行がそのリクエストコンテキストを持っていることを確認してください。

チームが実際に使用するアプリケーションでUnicodeと数式に敏感な値を確認します。アポストロフィポリシーはエクスポートされた表現を意図的に変更し、一部のビューアで可視化される場合があります。すべてのインポート設定やスプレッドシートアプリケーションで普遍的な保証ではありません。

テストには保存と再オープンの動作を含めます。 スプレッドシートインポートの議論 は、エスケープ処理がアプリケーション特有のレビューに値する理由を示しています。関連する列はテキストとしてインポートし、正確なソース文字列が必要な場合は元のJSONを使用してください。

成功したローカルラウンドトリップは、このプロジェクションの一貫性を確認します。それは、上流の検索サンプルが完全であるとか、代表的であるとか、ランキングの結論に適していることを確立するものではなく、さらなる分析がなくてはなりません。

結論

リクエストコンテキストを結果と共にエクスポートし、空白が意味を隠す場所ではフィールド状態を保持し、生のJSONを保持します。CSVとラン記録は、次のアナリストが測定された空のスライスを利用できないコレクションと区別するために十分な情報を提供します。

A SERPコンテンツリサーチ ワークフローは、エクスポートされた観察結果を使用して、コンテンツブリーフを作成する前にレビュー可能な読みリストを構築できます。

次の検索観察を構築

Scrapeless Google Search APIを使用して、このワークフローの検索データを取得します。収集計画を立てる際はScrapelessの価格を確認し、構成の横にGoogle Searchパラメータを保持してください。

コミュニティでの実装についてはDiscordまたはTelegramで議論してください。

FAQ

Q: Google Search APIはこのCSVを直接作成しますか?

いいえ。示されたエクスポータは、保存されたJSONをローカルで変換します。ファイル生成とサイドカーのスキーマはPythonプログラムに属します。

Q: クエリにすでに列があるのにrequest_jsonを保持するのはなぜですか?

完全なリクエストは、クエリ列だけでは表現できないオプション設定や完全なURL入力を保持します。

Q: CSVの引用符は数式の解釈を妨げますか?

いいえ。区切り文字の処理とスプレッドシート評価は別です。文書化されたテキストポリシーを適用し、意図したインポートワークフローを確認してください。

Q: 空のCSVは検索結果がないことを意味しますか?

必ずしもそうではありません。存在する空の配列と保留中、失敗、またはマッピングされていないコレクションを区別するためにサイドカーの状態を検査してください。

Q: 元の値を復元できますか?

はい、保持された入力キャプチャから。CSVは投影であり、スプレッドシート使用のためにいくつかのテキスト表現を意図的に変換します。

Scrapelessでは、適用される法律、規制、およびWebサイトのプライバシーポリシーを厳密に遵守しながら、公開されているデータのみにアクセスします。 このブログのコンテンツは、デモンストレーションのみを目的としており、違法または侵害の活動は含まれません。 このブログまたはサードパーティのリンクからの情報の使用に対するすべての責任を保証せず、放棄します。 スクレイピング活動に従事する前に、法律顧問に相談し、ターゲットウェブサイトの利用規約を確認するか、必要な許可を取得してください。

最も人気のある記事

カタログ