从零玩转搜索引擎 API: 多引擎整合实战

01 前言

  • 开发痛点:做全球化数据采集、SEO监测、竞品分析的小伙伴应该深有体会。项目如果需要同时接入Google、Bing、Yandex等多个搜索引擎,传统开发模式简直折磨人------不同引擎要单独找服务商、单独写对接代码、单独适配差异化返回格式,不仅开发周期长,后续迭代维护还要同时维护多套逻辑,冗余工作量直接翻倍。
  • 优解方案:今天给大家安利Dataify统一搜索SERP API。直白点说:单一接口+统一账号+一套通用代码,直接打通四大主流搜索引擎,彻底告别重复造轮子,把开发效率拉满。

Dataify - AIhttps://www.dataify.com/?utm_source=yanyj&utm_term=01

02 核心能力

  • 核心亮点:平台直接封装Google、Bing、Yandex、DuckDuckGo四大搜索引擎底层能力,标准化REST接口开箱即用。我们开发者不用纠结采集策略、反爬对抗、网页源码解析这些底层琐事,专注业务逻辑就行。
  • 引擎全覆盖&适配场景
  • Google搜索:实时捕获平台数据,洞察定价、监控竞品、精准收集用户评论。
  • Bing搜索:微软必应搜索结果 - 替代搜索引擎数据。
  • Yandex搜索:轻松获取本地化、精准且实时的结构化数据,服务范围涵盖自然搜索、广告、图片与地图等。
  • DuckDuckGo搜索:规模、稳定可靠地获取精准实时数据,满足市场监测与 SEO 等需求。

03 接入前置准备

整套接入流程超级简单,没有复杂资质审核,总共三步,新手也能一键上手:

  • 步骤1:注册Dataify账号:全程傻瓜式操作,无需实名认证,一分钟就能完成注册,零门槛入门。
  • 步骤2:获取专属API密钥:账号登录后,API Key直接展示在页面右上角,无需额外申请,复制保存即可,这是后续调用接口的核心凭证。
  • 步骤3:配置开发环境:仅Python开发环境即可,整个项目只依赖requests库,打开终端输入一行安装命令,直接搞定环境配置。

04 快速接入

官方控制台已经内置全引擎现成代码模板,不用自己从零编写。大家直接进入官方商店,选中需要调用的搜索引擎API,切换至对应编程语言版本,一键复制到本地,或者直接在线试运行,快速验证接口可用性。

05 返回数据

  • 四种标准化返回格式 :支持 JSON / JSON+HTML / HTML / Light JSON 四种输出模式。比较好的一点:四大搜索引擎返回结构一样,不用针对不同引擎写多套解析脚本,一套代码通吃全部数据源。
  • 通用字段封装:平台已封装标题、摘要、链接、发布时间、来源站点等通用核心字段,抹平各引擎字段命名差异,降低数据解析难度。
  • 通用解析脚本:文档内附带适配全引擎的结果解析代码,直接复用就能批量提取有效数据,省去自主解析数据的冗余步骤。

06 单/多引擎调用方案

6.1 单引擎调用

适合只需要单一搜索引擎数据的场景,代码精简、逻辑直白、延迟更低。我在代码模板里加了详细功能注释,直接复制修改关键词、地域参数就能直接跑,修改13行为你的api token复用即可。

6.1.1 谷歌搜索引擎API
python 复制代码
"""
Google 搜索 - 独立调用示例
用法:改 keyword,直接跑,结果保存为 JSON 文件
"""

import requests
import json
import os
from datetime import datetime

# ── 配置 ──────────────────────────────────────────────
URL = "https://scraperapi.dataify.com/request"
TOKEN = "Bearer 改为你的api token"

headers = {
    "Authorization": TOKEN,
    "Content-Type": "application/x-www-form-urlencoded",
}

# ── 改这里 ────────────────────────────────────────────
KEYWORD = "SEO优化工具"   # ← 改关键词
# ─────────────────────────────────────────────────────────

data = {
    # ── 必填 ──
    "engine": "google",
    "q":      KEYWORD,
    "json":   "1",            # 1=JSON  2=JSON+HTML  3=HTML  4=Light JSON

    # ── 本地化(按需取消注释)──
    # "gl": "us",              # 国家:us/cn/jp/uk/de/fr
    # "hl": "en",              # 语言:en/zh-cn/zh-tw/ja/ko/de/fr
    # "cr": "countryUS",       # 限定国家,多个用 I 连接
    # "lr": "lang_en",        # 限定语言,多个用 | 连接

    # ── 分页 ──
    # "start": "0",           # 0=第1页  10=第2页  20=第3页

    # ────
    # "safe":  "active",      # 安全搜索:active=开  off=关
    # "nfpr":  "0",          # 排除自动更正:1=排除  0=保留
    # "filter": "1",          # 相似结果过滤:1=启用  0=关闭
    # "tbs":   "",            # 搜索(日期/文件类型等)

    # ── 平台 ──
    "device":    "desktop",   # desktop / tablet / mobile
    # "render_js": "false",  # JS 渲染:true=开(慢) false=关
    # "no_cache": "false",   # 不用缓存:true=每次重抓
}

# ── 发送请求 ──────────────────────────────────────────
resp = requests.post(URL, headers=headers, data=data, timeout=30)
result = resp.json() if resp.status_code == 200 else {"error": resp.text}

# ── 保存 JSON 到文件夹 ───────────────────────────────
folder = "result_google"
os.makedirs(folder, exist_ok=True)

filepath = os.path.join(folder, f"result_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json")
with open(filepath, "w", encoding="utf-8") as f:
    json.dump(result, f, ensure_ascii=False, indent=2)

print(f"✅ 结果已保存:{filepath}")

使用后 会自动生成文件夹和时间戳json文件 方便使用

6.1.2 Bing搜索引擎API
python 复制代码
"""
Bing 搜索 - 独立调用示例
用法:改 keyword,直接跑,结果保存为 JSON 文件
"""

import requests
import json
import os
from datetime import datetime

# ── 配置 ────────────────────────────────────────────
URL = "https://scraperapi.dataify.com/request"
TOKEN = "Bearer 改为你的api token"

headers = {
    "Authorization": TOKEN,
    "Content-Type": "application/x-www-form-urlencoded",
}

# ── 改这里 ──────────────────────────────────────────
KEYWORD = "SEO"   # ← 改关键词
# ────────────────────────────────────────────────────────

data = {
    # ── 必填 ──
    "engine": "bing_news",
    "q":      KEYWORD,
    "json":   "1",            # 1=JSON  2=JSON+HTML  3=HTML  4=Light JSON

    # ── 本地化(按需取消注释)──
    # "mkt": "en-US",      # 市场/语言:zh-CN/en-US/en-GB/ja-JP/ko-KR
    # "cc":  "US",         # 国家:CN/US/JP/GB/DE/FR/KR

    # ── 分页 ──
    # "first": "1",         # 起始偏移
    # "count": "10",        # 每页条数(建议值)

    # ──  ──
    # "safeSearch": "Moderate",  # Off / Moderate(默认) / Strict
    # "qft":  "",          # 新闻过滤/排序

    # ── 平台 ──
    # "no_cache": "false",
}

# ── 发送请求 ──────────────────────────────────────
resp = requests.post(URL, headers=headers, data=data, timeout=30)
result = resp.json() if resp.status_code == 200 else {"error": resp.text}

# Dataify API 有时将整个 JSON 对象包在一个字符串里返回,需二次解析
if isinstance(result, str):
    result = json.loads(result)

print(json.dumps(result, ensure_ascii=False, indent=2))

# ── 保存 JSON 到文件夹 ───────────────────────────
folder = "result_bing"
os.makedirs(folder, exist_ok=True)

filepath = os.path.join(folder, "result_" + datetime.now().strftime("%Y%m%d_%H%M%S") + ".json")
with open(filepath, "w", encoding="utf-8") as f:
    json.dump(result, f, ensure_ascii=False, indent=2)

print("✅ 结果已保存:" + filepath)

这里有个要注意的点 避免json二次转义 不然会出现\

6.1.3 Yandex搜索引擎API
python 复制代码
"""
Yandex 搜索 - 独立调用示例
用法:改 keyword,直接跑,结果保存为 JSON 文件
"""

import requests
import json
import os
from datetime import datetime

# ── 配置 ─────────────────────────────────────────
URL  = "https://scraperapi.dataify.com/request"
TOKEN = "Bearer 改为你的api token"

headers = {"Authorization": TOKEN, "Content-Type": "application/x-www-form-urlencoded"}

# ── 改这里 ───────────────────────────────────────
KEYWORD = "SEO优化工具"   # ← 改关键词
# ────────────────────────────────────────────────────

data = {
    # ── 必填 ──
    "engine": "yandex",
    "text":   KEYWORD,               # Yandex 用 text(不是 q)
    "json":   "1",                   # 1=JSON  2=JSON+HTML  3=HTML  4=Light

    # ── 本地化(按需取消注释)──
    "yandex_domain": "yandex.com",  # yandex.com / yandex.ru / ya.ru 等
    "lang":            "en",               # en/ru/zh-cn/zh-tw/ja/ko/de/fr/es
    # "lr":   "",                         # 地区 ID(数字)

    # ── 分页 ──
    "p": "0",                             # 0=第1页  1=第2页

    # ──  ──
    "family_mode":    "1",               # 0=关  1=中等(默认)  2=严格
    "fix_typo":      "TRUE",             # TRUE=自动纠错  FALSE=关闭
    "groups_on_page": "20",              # 每页群组数:10/20/30/50

    # ── 平台 ──
    # "no_cache": "false",
}

# ── 发送请求 ──────────────────────────────────────
resp = requests.post(URL, headers=headers, data=data, timeout=30)
result = resp.json() if resp.status_code == 200 else {"error": resp.text}

if isinstance(result, str):
    result = json.loads(result)

print(result)

# ── 保存 JSON 到文件夹 ─────────────────────────
folder = "result_yandex"
os.makedirs(folder, exist_ok=True)

filepath = os.path.join(folder, "result_" + datetime.now().strftime("%Y%m%d_%H%M%S") + ".json")
with open(filepath, "w", encoding="utf-8") as f:
    json.dump(result, f, ensure_ascii=False, indent=2)

print("✅ 结果已保存:" + filepath)
6.1.4 DuckDuckGo搜索引擎API
python 复制代码
"""
DuckDuckGo 搜索 - 独立调用示例
用法:改 keyword,直接跑,结果保存为 JSON 文件
"""

import requests
import json
import os
from datetime import datetime

# ── 配置 ──────────────────────────────────────
URL  = "https://scraperapi.dataify.com/request"
TOKEN = "Bearer 改为你的api token"

headers = {"Authorization": TOKEN, "Content-Type": "application/x-www-form-urlencoded"}

# ── 改这里 ─────────────────────────────────
KEYWORD = "SEO优化工具"   # ← 改关键词
# ──────────────────────────────────────────────

data = {
    # ── 必填 ──
    "engine": "duckduckgo",
    "q":      KEYWORD,
    "json":   "1",             # 1=JSON  2=JSON+HTML  3=HTML  4=Light

    # ── 本地化(按需取消注释)──
    # "kl": "us-en",       # 地区:us-en / uk-en / fr-fr / de-de / zh-cn

    # ── ──
    "search_assist": "FALSE",  # TRUE=开AI辅助  FALSE=关(和 m 不能同时用)
    "safe":           "-1",   # 1=严格  -1=中等(默认)  -2=关闭
    # "df": "w",              # 日期过滤:d=一天  w=一周  m=一月  y=一年

    # ── 分页 ──
    "start": "0",             # 0=第1页(≤35条)  35=第2页(≤50条)
    "m":    "50",             # 结果数(1-50,和 search_assist 不能同时用)

    # ── 平台 ──
    # "no_cache": "false",
}

# ── 发送请求 ──────────────────────────────────
resp = requests.post(URL, headers=headers, data=data, timeout=30)
result = resp.json() if resp.status_code == 200 else {"error": resp.text}

# Dataify API 有时将整个 JSON 对象包在一个字符串里返回,需二次解析
if isinstance(result, str):
    result = json.loads(result)

# ── 保存 JSON 到文件夹 ─────────────────────
folder = "result_duckduckgo"
os.makedirs(folder, exist_ok=True)

filepath = os.path.join(folder, "result_" + datetime.now().strftime("%Y%m%d_%H%M%S") + ".json")
with open(filepath, "w", encoding="utf-8") as f:
    json.dump(result, f, ensure_ascii=False, indent=2)

print("✅ 结果已保存:" + filepath)

6.2 多引擎混合调用

适合全球化调研、多维度数据对比、批量采集场景。通过循环遍历逻辑,批量调用四大引擎,内置参数自适应逻辑,自动适配各引擎专属请求参数,附带完整可直接运行的全套源码,记得33行修改为你的api token。

python 复制代码
"""
动态搜索引擎 API 批量调用工具
========================================
功能:支持多引擎选择、关键词数组遍历、JSON 结果保存

执行逻辑:
  - 先选关键词 → 再选引擎 → 确认后执行
  - 循环次数 = 所选关键词的数量
  - 每个关键词依次被选中的引擎搜索
  - 例如:5 个关键词 × 4 个引擎 = 20 次请求
  - 每次请求同时写入「原始版」和「精简版」两个 JSON 文件

注意:精简版会自动去除 base64 图片等大量无用字段,
      让结果文件更小、更易读。
"""

# ──────────────────────────────────────────────────────
# 标准库 & 第三方库导入
# ──────────────────────────────────────────────────────
import requests   # HTTP 请求库(需 pip install requests)
import time       # 用于请求间隔等待
import json       # JSON 序列化 / 反序列化
import os         # 文件路径判断
from datetime import datetime  # 时间戳生成

# ══════════════════════════════════════════════════════
#  全局配置区(修改这里来定制你的搜索任务)
# ══════════════════════════════════════════════════════

# API 接入点和鉴权 Token
API_URL    = "https://scraperapi.dataify.com/request"
AUTH_TOKEN = "Bearer 换成你的api token"

# ── 搜索引擎字典 ──────────────────────────────────────
# 格式:{ 用户输入编号: { "name": 显示名称, "engine": API 参数值 } }
ENGINES = {
    "1": {"name": "Google",    "engine": "google"},
    "2": {"name": "Bing",      "engine": "bing_news"},
    "3": {"name": "Yandex",    "engine": "yandex"},
    "4": {"name": "DuckDuckGo","engine": "duckduckgo"},
}

# ── 关键词配置 ──────────────────────────────────────
# 支持两种写法:
#   字符串: KEYWORDS = "Python"          → 只搜这一个词
#   列表:   KEYWORDS = ["科技", "AI", ...] → 让用户选择范围,逐个搜索
# ──────────────────────────────────────────────────
KEYWORDS = [
    "科技",
    "引擎",
]

# ── 各引擎的默认请求参数 ─────────────────────────────
# ══════════════════════════════════════════════════════
#  DEFAULT_PARAMS  ------ 各引擎完整参数字典
#
#  设计原则:
#    ✅ 激活行(无 # 前缀)= 实际生效的参数
#    💤 注释行(# "key": ...)= 备用开关,取消注释即可启用
#
#  参数来源:google_search.py / bing_search.py /
#            yandex_search.py / duckduckgo_search.py
# ══════════════════════════════════════════════════════
DEFAULT_PARAMS = {

    # ──────────────────────────────────────────────────
    # Google 引擎参数
    # ──────────────────────────────────────────────────
    "google": {
        # ---------- 【基础参数】 ----------
        "json": "1",            # 输出格式:1=JSON, 2=JSON+HTML, 3=HTML, 4=Light JSON

        # ---------- 【本地化参数】 ----------
        "google_domain": "google.com",  # Google 域名
        # 常用域名:google.com / google.co.jp / google.de / google.fr
        #           google.co.uk / google.com.cn / google.com.au
        # "gl": "us",           # 国家/地区代码(us/cn/jp/uk/de/fr)
        # "hl": "en",           # 语言代码(en/zh-cn/zh-tw/ja/ko/de/fr)
        # "cr": "countryUS",    # 多国家限制,格式:country{大写代码},用 I 分隔
        # "lr": "lang_en",      # 多语言限制,格式:lang_{代码},用 | 分隔

        # ---------- 【地理位置参数】 ----------
        # "location": "United States",  # 搜索发起地理位置(不能与 uule/lat/lon 同时用)
        # "uule": "",           # Google 编码的精确位置(不能与 location/lat/lon 同时用)

        # ---------- 【分页参数】 ----------
        # "start": "0",         # 起始偏移量:0=第一页, 10=第二页, 20=第三页

        # ---------- 【过滤器】 ----------
        # "tbs": "",            # 搜索(专利/日期范围/文件类型等)
        # "safe": "active",     # 安全搜索:active=开启, off=关闭
        # "nfpr": "0",          # 排除自动更正结果:1=排除, 0=包含(默认)
        # "filter": "1",        # 类似/省略结果过滤:1=启用(默认), 0=不使用

        # ---------- 【Dataify 平台参数】 ----------
        "device": "desktop",    # 设备类型:desktop / tablet / mobile
        # "render_js": "false", # JS渲染:true=开启(耗时增加), false=关闭(默认)
        # "no_cache": "false",  # 不使用缓存:true=每次重新抓取, false=使用缓存(默认)
        # "ai_overview": "false", # Google AI Overview:true=开启, false=关闭(默认)
    },

    # ──────────────────────────────────────────────────
    # Bing News 引擎参数
    # ──────────────────────────────────────────────────
    "bing_news": {
        # ---------- 【基础参数】 ----------
        "json": "1",            # 输出格式:1=JSON, 2=JSON+HTML, 3=HTML, 4=Light JSON
        # engine 会在 build_request_data 中自动注入,此处无需写

        # ---------- 【地理位置参数】 ----------
        # "mkt": "en-US",       # 市场/语言代码(zh-CN / zh-HK / en-US / en-GB / ja-JP / ko-KR)
        # "cc": "US",           # 国家/地区代码(US / CN / JP / GB / DE / FR / KR / RU / BR)

        # ---------- 【分页参数】 ----------
        # "first": "1",         # 起始偏移量:1=第一条, 10=从第十条开始
        # "count": "10",        # 每页结果数(建议值,实际返回数可能不同)

        # ---------- 【参数】 ----------
        # "qft": "",            # 新闻排序/过滤(参考 Bing API 文档)
        # "safeSearch": "Moderate",  # 安全搜索:Off / Moderate(默认)/ Strict

        # ---------- 【Dataify 平台参数】 ----------
        # "no_cache": "false",  # 不使用缓存:true=每次重新抓取, false=使用缓存(默认)
    },

    # ──────────────────────────────────────────────────
    # Yandex 引擎参数
    # ──────────────────────────────────────────────────
    "yandex": {
        # ---------- 【基础参数】 ----------
        "json": "1",            # 输出格式:1=JSON, 2=JSON+HTML, 3=HTML, 4=Light JSON
        # 注意:Yandex 关键词参数名为 "text",由 build_request_data 自动注入

        # ---------- 【本地化参数】 ----------
        "yandex_domain": "yandex.com",  # Yandex 域名
        # 可选:yandex.com / yandex.ru / ya.ru / yandex.by / yandex.kz
        #       yandex.uz / yandex.com.tr / yandex.az / yandex.com.ge
        #       yandex.com.am / yandex.co.il / yandex.md / yandex.tm
        #       yandex.tj / yandex.eu
        "lang": "en",           # 语言:en / ru / zh-cn / zh-tw / ja / ko / de / fr / es
        # "lr": "",             # 地区 ID(数字,参考 Yandex 地区 ID 列表)

        # ---------- 【分页参数】 ----------
        "p": "0",               # 页码(从 0 开始):0=第一页, 1=第二页

        # ---------- 【参数】 ----------
        "family_mode": "1",     # 家庭模式:0=关闭, 1=中等(默认), 2=严格
        "fix_typo": "TRUE",     # 自动纠错:TRUE=启用(默认), FALSE=不使用
        "groups_on_page": "20", # 每页群组数(默认 20,可设 10/30/50)

        # ---------- 【Dataify 平台参数】 ----------
        # "no_cache": "false",  # 不使用缓存:true=每次重新抓取, false=使用缓存(默认)
    },

    # ──────────────────────────────────────────────────
    # DuckDuckGo 引擎参数
    # ──────────────────────────────────────────────────
    "duckduckgo": {
        # ---------- 【基础参数】 ----------
        "json": "1",            # 输出格式:1=JSON, 2=JSON+HTML, 3=HTML, 4=Light JSON

        # ---------- 【本地化参数】 ----------
        # "kl": "us-en",        # 地区代码(us-en / uk-en / fr-fr / de-de / zh-cn)

        # ---------- 【参数】 ----------
        "search_assist": "FALSE",  # AI 搜索辅助:true=启用, FALSE=不使用(默认)
                                   # 注意:search_assist 与 m 参数不能同时使用!

        # ---------- 【过滤器】 ----------
        "safe": "-1",           # 安全搜索:1=严格, -1=中等(默认), -2=关闭
        # "df": "w",            # 日期过滤:d=过去一天, w=一周, m=一月, y=一年
        #                       # 自定义范围:开始日期..结束日期(如 "2024-01-01..2024-06-30")

        # ---------- 【分页参数】 ----------
        "start": "0",           # 起始偏移量:0=第一页(≤35条), 35=第二页(≤50条)
        "m": "50",              # 结果数(1-50)
                                # 注意:m 与 search_assist 不能同时使用!
                                # 不带偏移量时返回 35 条,带偏移量时50 条

        # ---------- 【Dataify 平台参数】 ----------
        # "no_cache": "false",  # 不使用缓存:true=每次重新抓取, false=使用缓存(默认)
    },
}

# ── 精简 JSON 时需要「整个字段」剔除的 key 列表 ─────
# 这些字段要么是 base64 大图、要么是无实际意义的元信息
CLEAN_REMOVE_KEYS = {
    # 图片相关(base64 数据极大)
    "image", "original", "source_logo", "favicon", "icon",
    # 无用的 Google 元数据字段
    "about_page_link", "about_this_result", "redirect_link",
    # 分页链接(通常不需要批量保存)
    "pages",
}

# ══════════════════════════════════════════════════════
#  工具函数
# ══════════════════════════════════════════════════════

def print_banner():
    """打印程序欢迎横幅"""
    print("=" * 60)
    print("       搜索引擎 API 批量调用工具")
    print("=" * 60)

# ── 精简 JSON 相关 ────────────────────────────────────

def clean_value(value):
    """
    递归清理一个 JSON 值:
      - 如果是 dict,删除 CLEAN_REMOVE_KEYS 中指定的键,
        并对其余值继续递归清理
      - 如果是 list,对每个元素递归清理
      - 其他类型直接返回
    """
    if isinstance(value, dict):
        cleaned = {}
        for k, v in value.items():
            if k in CLEAN_REMOVE_KEYS:
                continue          # 跳过需要删除的字段
            cleaned[k] = clean_value(v)
        return cleaned

    elif isinstance(value, list):
        return [clean_value(item) for item in value]

    else:
        return value  # 字符串、数字、布尔等直接返回

def make_clean_record(raw_record):
    """
    将一条「原始 result_record」生成对应的「精简版记录」。

    原始 result 可能是:
      1. dict(正常 JSON 响应)
      2. str(某些引擎返回的 JSON 字符串,需先 parse)
      3. 其他(报错信息等)

    精简逻辑:
      - 先尝试将 str 类型的 result 解析为 dict
      - 再对整个 result 执行 clean_value 递归剔除
    """
    # 深拷贝一份,避免修改原始数据
    import copy
    record = copy.deepcopy(raw_record)

    raw_result = record.get("result")

    # 如果 result 是字符串(Bing / DuckDuckGo 有时会这样),先 parse 一次
    if isinstance(raw_result, str):
        try:
            raw_result = json.loads(raw_result)
        except (json.JSONDecodeError, TypeError):
            pass  # parse 失败就保留原始字符串

    # 递归清理
    record["result"] = clean_value(raw_result)
    return record

# ── 关键词选择 ────────────────────────────────────────

def select_keyword_range(keywords):
    """
    【步骤 1】让用户选择要搜索的关键词范围。

    支持三种输入方式:
      1. 单个数字:如 "3"   → 只搜第 3 个关键词
      2. 范围格式:如 "3,7" → 搜第 3 到第 7 个(共 5 个)
      3. all       → 搜全部关键词

    返回:选中的关键词列表(list of str)
    """
    # ── 单字符串模式:直接返回,不需要选择 ──────────
    if isinstance(keywords, str):
        print(f"\n✓ 关键词: {keywords}(单字符串模式)")
        return [keywords]

    # ── 列表模式 ──────────────────────────────────────
    n = len(keywords)
    print("\n【步骤 1】选择关键词")
    print("-" * 60)
    print(f"可用关键词(共 {n} 个):")
    for i, kw in enumerate(keywords):
        print(f"  {i + 1:>2}. {kw}")
    print("-" * 60)
    print("输入说明:")
    print("  单个数字     示例: 3       → 只用第 3 个")
    print("  范围(逗号)   示例: 3,7     → 第 3 到第 7 个(共 5 个)")
    print("  all          回车           → 使用全部关键词")
    print("-" * 60)

    user_input = input("请选择关键词范围(直接回车 = all): ").strip()

    # 回车或输入 all → 全选
    if user_input == "" or user_input.lower() == "all":
        print(f"✓ 已选全部 {n} 个关键词")
        return list(keywords)

    # 范围格式:x,y
    if "," in user_input:
        parts = user_input.split(",", 1)  # 拆一次逗号
        try:
            start = int(parts[0].strip())
            end   = int(parts[1].strip())
            # 验证合法性
            if not (1 <= start <= end <= n):
                print(f"⚠ 范围 {start},{end} 超出 1~{n},已改为全选")
                return list(keywords)
            selected = keywords[start - 1 : end]  # 转换为 0 索引
            print(f"✓ 已选第 {start} 到第 {end} 个(共 {len(selected)} 个)")
            return selected
        except ValueError:
            print("⚠ 格式有误,已改为全选")
            return list(keywords)

    # 单个数字
    try:
        idx = int(user_input)
        if 1 <= idx <= n:
            print(f"✓ 已选第 {idx} 个: {keywords[idx - 1]}")
            return [keywords[idx - 1]]
        else:
            print(f"⚠ 超出范围(1~{n}),已改为全选")
            return list(keywords)
    except ValueError:
        print("⚠ 无法识别,已改为全选")
        return list(keywords)

# ── 引擎选择 ──────────────────────────────────────────

def select_engines():
    """
    【步骤 2】让用户选择要使用的搜索引擎。

    支持两种输入方式:
      1. 数字组合:如 "12" 或 "1 2" → 选 Google + Bing
      2. 0 或 all 或直接回车           → 全选

    设计重点:允许用户用空格/逗号/粘连数字等多种格式输入,
              统一提取其中的有效数字字符。
    """
    print("\n【步骤 2】选择搜索引擎")
    print("-" * 60)
    for key, val in ENGINES.items():
        print(f"  {key}. {val['name']}  ({val['engine']})")
    print("-" * 60)
    print("输入说明:")
    print("  单个:   1        → 只用 Google")
    print("  多个:   12  或  1 2  或  1,2   → Google + Bing")
    print("  全选:   0  /  all  /  直接回车")
    print("-" * 60)

    user_input = input("请选择引擎: ").strip()

    # 全选触发条件
    if user_input in ("", "0", "all"):
        selected = list(ENGINES.keys())
        print(f"✓ 已选全部引擎: {[ENGINES[k]['name'] for k in selected]}")
        return selected

    # 提取所有出现过的合法数字(去重、保序)
    seen = set()
    selected = []
    for ch in user_input:
        if ch in ENGINES and ch not in seen:
            seen.add(ch)
            selected.append(ch)

    if not selected:
        # 没有识别到任何有效引擎 → 全选作为 fallback
        selected = list(ENGINES.keys())
        print(f"⚠ 未识别到有效引擎,已改为全选")
    else:
        print(f"✓ 已选引擎: {[ENGINES[k]['name'] for k in selected]}")

    return selected

# ── 请求构建与发送 ────────────────────────────────────

def build_request_data(engine_type, keyword):
    """
    构建发往 API 的 POST 表单数据。

    - 以该引擎的 DEFAULT_PARAMS 为基础
    - 追加 engine 类型字段
    - 追加关键词(Yandex 使用 "text" 参数,其余用 "q")

    返回:dict,将被 requests 以 form-urlencoded 格式发送
    """
    data = DEFAULT_PARAMS.get(engine_type, {}).copy()
    data["engine"] = engine_type

    # Yandex API 的关键词参数名与其他引擎不同
    if engine_type == "yandex":
        data["text"] = keyword
    else:
        data["q"] = keyword

    return data

def send_request(data, attempt_num, total_attempts):
    """
    发送一次 API 请求并返回解析后的 JSON 结果。

    参数:
      data           - 请求表单数据(dict)
      attempt_num    - 当前是第几次请求(用于打印进度)
      total_attempts - 总请求次数

    返回:
      成功 → dict(解析后的 JSON)
      失败 → dict 包含 "error" 键

    异常处理:超时、网络错误、非 JSON 响应会被捕获
    """
    headers = {
        "Authorization": AUTH_TOKEN,
        "Content-Type": "application/x-www-form-urlencoded",
    }

    try:
        print(f"\n  📤 发送请求 [{attempt_num}/{total_attempts}]...")
        response = requests.post(
            API_URL,
            headers=headers,
            data=data,
            timeout=30   # 30 秒超时
        )

        if response.status_code == 200:
            print(f"  ✅ 请求成功 (HTTP 200)")
            try:
                result = response.json()
                # Dataify API 有时将整个 JSON 对象包在一个字符串里返回
                # resp.json() 得到 str 而非 dict,print 看着正常
                # 但 json.dump(str) 会触发二次编码,导致保存文件里出现反斜杠
                if isinstance(result, str):
                    result = json.loads(result)
                return result
            except json.JSONDecodeError:
                # 极少数情况:API 返回了非 JSON 文本
                print(f"  ⚠ 响应内容不是 JSON 格式,已保存原始文本")
                return {"raw_text": response.text}
        else:
            print(f"  ❌ 请求失败 (HTTP {response.status_code})")
            return {"error": f"HTTP {response.status_code}", "text": response.text}

    except requests.exceptions.Timeout:
        print(f"  ⏰ 请求超时(超过 30 秒)")
        return {"error": "Timeout"}
    except requests.exceptions.RequestException as e:
        print(f"  ❌ 网络异常: {str(e)}")
        return {"error": str(e)}

# ── JSON 文件保存 ─────────────────────────────────────

def append_to_json_file(record, filename):
    """
    将一条记录追加到指定 JSON 文件(数组格式)。

    文件结构:JSON 数组 [ {...}, {...}, ... ]
    如果文件不存在,先创建空数组文件,再追加。

    返回:True(成功)/ False(失败)
    """
    try:
        # 文件不存在时初始化为空数组
        if not os.path.exists(filename):
            with open(filename, "w", encoding="utf-8") as f:
                json.dump([], f, ensure_ascii=False, indent=2)

        # 读取已有内容
        with open(filename, "r", encoding="utf-8") as f:
            try:
                data = json.load(f)
            except json.JSONDecodeError:
                data = []  # 文件损坏时重置

        # 追加新记录并写回
        data.append(record)
        with open(filename, "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=2)

        return True

    except Exception as e:
        print(f"  ⚠ 文件写入失败 [{filename}]: {str(e)}")
        return False

# ══════════════════════════════════════════════════════
#  主程序入口
# ══════════════════════════════════════════════════════

def main():
    """
    主函数:协调整个搜索任务的执行流程

    流程:
      1. 打印欢迎横幅
      2. 用户选择关键词范围
      3. 用户选择搜索引擎
      4. 显示执行计划,等待确认
      5. 逐条发送请求:每条结果追加写入「原始版」和「精简版」两个文件
      6. 打印汇总信息
    """
    print_banner()

    # ── 步骤 1:选择关键词 ────────────────────────────
    selected_keywords = select_keyword_range(KEYWORDS)

    # ── 步骤 2:选择引擎 ──────────────────────────────
    selected_engines = select_engines()

    # ── 计划摘要 ──────────────────────────────────────
    total_requests = len(selected_engines) * len(selected_keywords)
    print(f"\n{'=' * 60}")
    print(f"📊 执行计划")
    print(f"   关键词: {len(selected_keywords)} 个")
    for i, kw in enumerate(selected_keywords):
        print(f"      [{i+1}] {kw}")
    print(f"   引擎:   {[ENGINES[k]['name'] for k in selected_engines]}")
    print(f"   总请求: {total_requests}  = {len(selected_keywords)} 关键词 × {len(selected_engines)} 引擎")
    print(f"{'=' * 60}")

    # ── 确认开始 ──────────────────────────────────────
    confirm = input("\n是否开始执行?(y/n,回车默认 y): ").strip().lower()
    if confirm == "n":
        print("已取消")
        return

    # ── 生成带时间戳的文件名 ──────────────────────────
    # 原始文件:保存完整 API 响应(含 base64 图片等)
    # 精简文件:自动剔除 base64 图片及噪音字段,体积更小
    timestamp    = datetime.now().strftime("%Y%m%d_%H%M%S")
    raw_file     = f"search_results_{timestamp}.json"
    clean_file   = f"search_results_{timestamp}_clean.json"

    print(f"\n💾 原始文件: {raw_file}")
    print(f"💾 精简文件: {clean_file}")

    # ── 开始执行请求循环 ──────────────────────────────
    print(f"\n{'=' * 60}")
    print("🚀 开始执行...")
    print(f"{'=' * 60}")

    request_counter = 0

    # 外层循环:按关键词逐个搜索
    for kw_idx, keyword in enumerate(selected_keywords):
        print(f"\n{'#' * 60}")
        print(f"# 关键词 [{kw_idx + 1}/{len(selected_keywords)}]: {keyword}")
        print(f"{'#' * 60}")

        # 内层循环:每个关键词依次被所有引擎搜索
        for eng_idx, engine_key in enumerate(selected_engines):
            request_counter += 1
            engine_info = ENGINES[engine_key]
            engine_name = engine_info["name"]
            engine_type = engine_info["engine"]

            print(f"\n[{request_counter}/{total_requests}] "
                  f"引擎 {eng_idx + 1}/{len(selected_engines)}: {engine_name}")

            # 构建请求参数并发送
            data   = build_request_data(engine_type, keyword)
            result = send_request(data, request_counter, total_requests)

            # ── 组装「原始记录」 ──────────────────────
            # 保存了所有字段,包括 base64 图片
            raw_record = {
                "request_index": request_counter,   # 请求序号
                "keyword":        keyword,            # 搜索关键词
                "engine":         engine_name,        # 引擎名称
                "engine_type":    engine_type,        # 引擎 API 类型
                "timestamp":      datetime.now().isoformat(),
                "result":         result              # 完整 API 响应
            }

            # ── 组装「精简记录」 ──────────────────────
            # 剔除所有 base64 图片字段及噪音元数据
            clean_record = make_clean_record(raw_record)

            # 追加写入两个 JSON 文件
            if append_to_json_file(raw_record,   raw_file):
                print(f"  💾 原始版已保存")
            if append_to_json_file(clean_record, clean_file):
                print(f"  ✨ 精简版已保存")

            # 请求间隔(最后一条不需要等待)
            # if request_counter < total_requests:
            #     print(f"  ⏳ 等待 1 秒...")
            #     time.sleep(1)

    # ── 执行完成汇总 ──────────────────────────────────
    print(f"\n{'=' * 60}")
    print(f"✅ 执行完成!")
    print(f"   共发送:   {request_counter} 个请求")
    print(f"   原始文件: {raw_file}")
    print(f"   精简文件: {clean_file}  ← 推荐查看这个,体积更小")
    print(f"{'=' * 60}")

# ── 程序入口守卫 ──────────────────────────────────────
# 只有直接运行本文件时才执行 main()
# 被 import 引入时不会自动执行
if __name__ == "__main__":
    main()

这里我做了自定义搜索和引擎选择 json也分为了原版和精简版

原始文件:保存完整 API 响应(含 base64 图片等):

精简文件:自动剔除 base64 图片及噪音字段,体积更小:

07 高阶玩法

默认参数能满足基础搜索需求,但想要适配精细化业务场景,必须学会自定义筛选参数。我帮大家梳理了比较实用的本地化、时间过滤两类高阶参数,四大引擎全部兼容,取消代码内注释即可直接启用。

7.1 语言&地域过滤

同一个关键词,不同地区、不同语言环境下,搜索结果天差地别。举个简单例子:检索"SEO优化工具",美版结果清一色海外工具,中文版则以国内站长工具为主。实现该效果的核心就是地域、语言参数。

常用配置速查:

|------------|-----------------------|--------------|---------------|----------------|
| 你想要的效果 | Google | Bing | Yandex | DuckDuckGo |
| 搜中文内容 | gl: "cn", hl: "zh-cn" | mkt: "zh-CN" | lang: "zh-cn" | kl: "zh-cn" |
| 搜英文内容 | gl: "us", hl: "en" | mkt: "en-US" | lang: "en" | kl: "us-en" |
| 搜日文内容 | gl: "jp", hl: "ja" | mkt: "ja-JP" | lang: "ja" | kl: "jp-jp" |

避坑重点 :各引擎参数命名不统一,千万别记混!Google用 gl(地区)/hl(语言),Bing用 mkt市场参数,Yandex用 lang语言参数,DuckDuckGo用 kl地域参数。我已经在代码模板里按引擎分类整理好,无需死记硬背。

7.2 时间范围过滤

做行业热点追踪、短期舆情监控时,我们往往只需要近期更新的内容,时间过滤参数就能解决冗余数据问题。

简单科普:DuckDuckGo依靠 df 参数限定时间范围,Google通过 tbs 参数实现过滤,支持按天/周/月/自定义时间段筛选。

|------------------------|---------|
| | 含义 |
| d | 过去一天 |
| w | 过去一周 |
| m | 过去一月 |
| y | 过去一年 |
| 2024-01-01..2024-06-30 | 自定义日期范围 |

7.3 功能验证

  • ✅ 语言过滤:配置 hl: "zh-cn" 后,Google优先返回中文标题与摘要内容
  • ✅ 地区过滤:配置 mkt: "zh-CN" 后,Bing优先展示中国地区本地化内容
  • ✅ 时间过滤:配置 df: "w" 后,DuckDuckGo仅展示一周内更新的搜索结果
  • ✅ 安全搜索:四大引擎均内置独立安全风险参数,可自主调节

08 适配人群

  • 中小型开发团队:不想投入人力维护多套采集程序、对抗反爬机制,低成本快速接入成熟搜索能力。
  • 数据运营/SEO从业者:需要多引擎横向对比数据,支撑市场调研、关键词布局、竞品分析工作。
  • 全球化业务团队:业务覆盖多国地区,需要适配不同国家本地化搜索引擎,统一管理数据源。

09 后续接入指引

  • 官方文档地址:简介 - Dataify 官方完整文档链接,包含冷门参数、限额说明、计费规则等详细内容,可供深度查阅。
  • 简单接入:注册账号→复制API Key→复用本文配套代码,5分钟即可完成接口请求调试。
相关推荐
ihuyigui1 小时前
国际商超零售短信接口
大数据·前端·后端·架构·零售
Spider_Man1 小时前
Claude Code Hooks:给 AI 助手装上"安全带"
前端·ai编程·claude
lichenyang4531 小时前
HarmonyOS 6.0 ArkUI 循环渲染:ForEach、LazyForEach 和 Repeat 到底怎么选?
前端
Captaincc2 小时前
置身钉内:一个 AI 办公产品的理想、失焦与组织困境
前端·程序员
步步为营DotNet2 小时前
Blazor 在边缘计算客户端应用中的创新实践与深度解析
人工智能·microsoft·边缘计算
零陵上将军_xdr2 小时前
后端转全栈学习-Day6-JavaScript 基础-4
前端·javascript·学习
爱装代码的小瓶子2 小时前
3. 设计buffer模块
linux·服务器·开发语言·c++·php
流浪0012 小时前
Linux系统篇(四):一文吃透 Linux 虚拟地址空间:从页表映射到内核结构体全链路拆解
linux·运维·服务器
Jacob程序员2 小时前
WebSSH技术实现全解析
linux·运维·服务器·websocket