跳至正文
中文
立即开始

Google 搜索结果采集:减少个性化与位置偏差

从工程角度解析搜索个性化算法、标准 UULE 位置编码,以及如何使用轮换住宅代理规模化采集减少偏差的 Google 搜索结果数据。

AC

Alex Chen

发布于 •更新于 •约 8 分钟

行业解决方案
文章目录(9 个章节)

企业 SEO 平台、排名跟踪 SaaS 服务商和竞争情报团队,都依赖准确且未受干扰的搜索结果页(SERP)数据。

但 Google RankBrain 和本地意图匹配等现代搜索算法,已不再为同一个搜索词提供统一排名。搜索引擎会根据 搜索历史 Cookie、客户端 IP 信誉和实际出口位置,动态调整自然结果、赞助广告和本地商家三联结果。

从 AWS、Google Cloud 或 DigitalOcean 等云托管服务器采集排名,可能产生严重偏差。搜索引擎识别到商业托管 ASN 后,会将请求转到 Google 的 sorry/index 验证码页面,或返回缺少本地自然结果特征的国家级兜底索引。

要提取可靠、减少偏差的排名,数据工程团队需要结合 无状态查询参数(gl、hl、pws=0、uule) 与 信誉良好的轮换住宅代理出口。


1. 多层个性化引擎

客户端查询 Google 时,搜索引擎会综合四个层面确定页面布局:

第一层:出口 IP 与实际地理位置
决定本地地图三联结果、地区商家列表和“附近”搜索倾向
                 ↓ 关联
第二层:HTTP 查询参数(gl、hl、pws=0、uule)
声明国家(gl=us)、界面语言(hl=en)与标准位置
                 ↓ 关联
第三层:Cookie 与历史状态
个性化点击历史、搜索历史、登录状态
                 ↓ 关联
第四层:客户端指纹与机器人评分
数据中心 ASN 易触发验证码;住宅 ISP 更可能获得正常 HTML

查询参数与 IP 不一致的陷阱

一些简单的采集流程只追加 Google 国家参数 &gl=us,却仍通过欧洲的数据中心服务器发出 HTTP 请求,以此模拟地区搜索结果。

Google 的边缘算法会发现这种不一致:

  • 声明的位置意图 gl=us 与实际 BGP 路由和客户端 TCP 往返时间(RTT)冲突。
  • 搜索引擎返回通用、去个性化结果,同时减少本地地图轮播和本地意图组件。
  • 请求速度升高后,托管 ASN 可能立即触发 HTTP 429 限速或自动 reCAPTCHA 验证。

2. 理解 Google UULE 参数编码

要提取精细到城区的搜索结果,例如比较曼哈顿、布鲁克林与皇后区的排名差异,程序化采集器需要通过 Google 的 UULE(&uule=) 参数指定标准位置。

标准 UULE 格式会将规范位置名称,例如 "New York,New York,United States",序列化为 Protocol Buffer 结构并进行 Base64 编码:

uule = "w+CAIQICI" + LengthKey(LocationString) + Base64(LocationString)

其中,LengthKey 使用包含 64 个字符的查找表(A-Z、a-z、0-9、-、_),将位置字符串的整数长度映射为确定的 ASCII 字符。

使用 Python 实现标准 UULE 编码

import base64

def generate_google_uule(canonical_name: str) -> str:
    """
    Encodes a standardized location name into Google's canonical UULE format.
    Example: 'New York,New York,United States' -> 'w+CAIQICI...'
    """
    lookup = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
    length = len(canonical_name)
    if length >= len(lookup):
        raise ValueError("Location string length exceeds UULE format capacity.")

    secret_key = lookup[length]
    encoded_name = base64.b64encode(canonical_name.encode("utf-8")).decode("utf-8")
    return f"w+CAIQICI{secret_key}{encoded_name}"

# Example canonical location
target_loc = "New York,New York,United States"
print("Generated UULE:", generate_google_uule(target_loc))
# Output: w+CAIQICINZXcgWW9yayxOZXcgWW9yayxVbml0ZWQgU3RhdGVz

UULE 同步规则:
UULE 参数需要与位于同一城市区域的住宅代理出口配合使用。如果从法兰克福出口提交曼哈顿 UULE,Google 的边缘延迟检测会识别位置差异,可能移除本地组件或提升安全验证等级。


3. 理解 google.com/sorry/index 验证入口与参数设置

自动化客户端触发 Google 威胁防御后,搜索引擎会通过 HTTP 302 将其重定向到 https://www.google.com/sorry/index?continue=...。

现代 Google 反机器人系统同时评估多种风险指标:

  1. 商业 ASN 特征: 注册在 AWS、Google Cloud、DigitalOcean 或 Hetzner 等云服务商下的出口 IP,请求速度容忍度极低。同一云 IP 连续查询两次,就可能触发 sorry/index。
  2. 缺少 pws=0 与残留 Cookie: 历史用户标识 Cookie 会让结果偏向个性化页面。
  3. TLS ClientHello 不一致: Google 边缘网关评估客户端密码套件偏好和扩展顺序。直接使用 Python 默认 OpenSSL 构建而不模拟浏览器,会引起边缘过滤器注意。
  4. 查询参数组合: 生产采集器应明确设置以下参数:
    • &pws=0:关闭基于历史用户 Cookie 的个性化搜索。
    • &gl=us:指定搜索国家。
    • &hl=en:将界面语言统一为英语。
    • &filter=0:避免 Google 省略重复或相似结果。
    • &num=100:在单次请求中获取前 100 个自然结果,减少往返请求。

4. 流量成本:原始 HTML 解析与无头浏览器

大规模搜索监控的一项关键架构决策,是选择 原始 HTML 解析 还是 无头浏览器自动化(Playwright/Puppeteer):

提取架构 每次查询响应体积 P50 执行延迟 10 万次查询的代理流量成本 可访问内容
通过 curl_cffi 获取原始 HTML 120–180 KB 250 毫秒 15 GB,约 $42.00 前 100 个自然链接、精选摘要、本地商家结果
无头 Chromium 完整渲染 2.5–4.0 MB 2,400 毫秒 320 GB,约 $896.00 完整 DOM 与客户端 JavaScript 跟踪
优化后的无头浏览器,拦截媒体 500–750 KB 1,200 毫秒 60 GB,约 $168.00 大量依赖 JavaScript 的交互功能

由于 Google 在初始服务端渲染 HTML 中包含前 100 个自然排名、“其他人还问了”轮播和本地商家结果,轻量 HTTP 客户端配合住宅代理,相比完整浏览器渲染可减少超过 85% 的数据成本。


5. 生产代码:高吞吐搜索结果提取管道

以下 Python 脚本使用 curl_cffi 模拟浏览器 TLS,并结合 FlashIP 轮换住宅网关,提取减少偏差的搜索排名、避免限速拦截:

import urllib.parse
from curl_cffi import requests

PROXY_HOST = "gate.flaship.net"
PROXY_PORT = "7000"
CUSTOMER_ID = "fl_live_9481a"
CUSTOMER_KEY = "SECRET_API_KEY"

def fetch_unbiased_serp(query: str, country="us", city=None, max_retries=3):
    """
    Queries Google Search with browser TLS impersonation through a FlashIP
    rotating residential proxy to harvest clean, unbiased search results.
    """
    # FlashIP per-request rotation: each outbound socket connects via a fresh IP
    user_parts = [f"user-{CUSTOMER_ID}", f"country-{country}"]
    if city:
        user_parts.append(f"city-{city}")

    username = "-".join(user_parts)
    proxy_url = f"http://{username}:{CUSTOMER_KEY}@{PROXY_HOST}:{PROXY_PORT}"
    proxies = {"http": proxy_url, "https": proxy_url}

    # Clean, unbiased SERP query parameters
    params = {
        "q": query,
        "hl": "en",           # Standardize interface language
        "gl": country,        # Country context
        "pws": "0",           # Explicitly disable personalized web search
        "filter": "0",        # Include similar / omitted results
        "num": "100",         # Fetch top 100 results per request
    }

    encoded_url = f"https://www.google.com/search?{urllib.parse.urlencode(params)}"

    headers = {
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
        "Accept-Language": "en-US,en;q=0.9",
        "Sec-Fetch-Dest": "document",
        "Sec-Fetch-Mode": "navigate",
        "Sec-Fetch-Site": "none",
        "Sec-Fetch-User": "?1",
    }

    for attempt in range(1, max_retries + 1):
        try:
            # Impersonate Chrome 124 TLS and HTTP/2 framing
            response = requests.get(
                encoded_url,
                impersonate="chrome124",
                proxies=proxies,
                headers=headers,
                timeout=15
            )

            if response.status_code == 200:
                # Audit payload to ensure no sorry/index redirect occurred
                if "sorry/index" in response.url or "recaptcha" in response.text.lower():
                    raise RuntimeError("Encountered Google reCAPTCHA verification.")

                return response.text
            elif response.status_code == 429:
                print(f"Rate limited (HTTP 429). Retrying on fresh proxy...")
        except Exception as err:
            print(f"Attempt {attempt} failed: {err}")

    raise RuntimeError(f"Failed to fetch SERP for '{query}' after {max_retries} retries.")

if __name__ == "__main__":
    test_query = "best enterprise residential proxies"
    html_content = fetch_unbiased_serp(test_query, country="us")
    print(f"Successfully scraped {len(html_content)} bytes of unbiased SERP HTML.")

6. 企业搜索采集管道检查清单

部署高流量排名跟踪管道前,应按以下清单检查基础设施:

  • 关闭个性化: 所有请求都包含 &pws=0,并使用不保留 Cookie 的无状态会话。
  • 同步 UULE 与出口 IP: 声明的 UULE 城市与实际代理出口相匹配。
  • 逐请求轮换: 排名采集采用逐请求 IP 轮换,避免粘性会话。
  • 直接提取 HTML: 从服务端渲染的原始 HTML 获取自然结果,减少无头浏览器带宽开销。
  • 请求间隔抖动: 在请求之间加入 200–600 毫秒随机延迟,避免可预测节奏被标记。

7. 使用 FlashIP 支持搜索情报研究

规模化获取减少偏差的搜索情报,需要广泛的住宅 IP 多样性和精细的地理控制:

  • FlashIP 住宅代理: 按流量计费,从 $2.80/GB 到企业大流量档位的 $1.00/GB。在数百万消费级 IP 间逐请求轮换,支持减少限速影响并提高搜索结果准确性。
  • FlashIP 静态 ISP 代理: 专用消费级住宅 IP,$7.99/月起,适用于固定搜索监控实例和自动化 API 测试。
  • FlashIP 数据中心代理: 高速服务器 IP,$4.99/月起,适用于未设防网页采集。

前往 价格页面 查看流量方案,或通过 Telegram 联系技术团队,讨论定制企业测试。

AC
Alex ChenFlashIP 基础设施负责人

FlashIP 分布式系统架构师,专注于高吞吐数据提取、反机器人协议逆向分析与边缘网络路由。

相关文章

查看全部文章