bigywater.tech
爬虫工程 · 36

URL 规范化:大小写、默认端口、片段、参数顺序

爬虫的去重第一步是判断“这两个 URL 是不是同一个页面”。字符串不同不代表页面不同:大小写、默认端口、fragment、参数顺序、追踪参数都会让同一个页面有很多写法。这篇比较 yarl、w3lib 和自己写的规范化器,在 20 组用例和 8000 个 URL 的去重上到底能合并多少。

8000 → 1000
1000 个页面的 8 种写法:不规范化与加上语义规范化之后的“不同 URL”数
15 / 20
自写语法 + 语义规范化合并成功的组数(w3lib 10,yarl 8)
:80
w3lib 2.5.0 的 canonicalize_url 保留了默认端口

用例对比

实测 · 本机执行输出
  用例                                         原样字符串       urlsplit→geturl              yarl.URL           w3lib 2.5.0            自写:只做语法规范化            自写:再加语义规范化
  scheme 与主机大小写                              ·                     ·                     ✓                     ✓                     ✓                     ✓
  路径大小写(路径通常区分)                              ·                     ·                     ·                     ·                     ·                     ·
  默认端口 :80                                   ·                     ·                     ✓                     ·                     ✓                     ✓
  默认端口 :443                                  ·                     ·                     ✓                     ·                     ✓                     ✓
  非默认端口 :8080                                ·                     ·                     ·                     ·                     ·                     ·
  http 与 https                               ·                     ·                     ·                     ·                     ·                     ·
  fragment                                   ·                     ·                     ·                     ✓                     ✓                     ✓
  空路径与 /                                     ·                     ·                     ·                     ✓                     ✓                     ✓
  结尾斜杠                                       ·                     ·                     ·                     ·                     ·                     ·
  点段 /./ 与 /../                              ·                     ·                     ✓                     ✓                     ✓                     ✓
  重复斜杠                                       ·                     ·                     ·                     ·                     ·                     ✓
  百分号编码大小写 %2f / %2F                         ·                     ·                     ✓                     ✓                     ✓                     ✓
  不必要的编码 %7E 与 ~                             ·                     ·                     ✓                     ✓                     ✓                     ✓
  参数顺序                                       ·                     ·                     ·                     ✓                     ·                     ✓
  空的 ?                                       ·                     ✓                     ✓                     ✓                     ✓                     ✓
  utm 追踪参数                                   ·                     ·                     ·                     ·                     ·                     ✓
  主机末尾的点                                     ·                     ·                     ·                     ·                     ✓                     ✓
  中文域名 与 punycode                            ·                     ·                     ✓                     ✓                     ✓                     ✓
  查询串里空格 + 与 %20                             ·                     ·                     ·                     ✓                     ·                     ✓
  用户信息                                       ·                     ·                     ·                     ·                     ·                     ·
  合并成功的组数                                  0                     1                     8                    10                    11                    15

20 组用例里,原样字符串一组都合并不了;yarl 合并 8 组(大小写、默认端口、点段、百分号编码、punycode,但不动参数顺序和 fragment);w3lib 的 canonicalize_url 合并 10 组(丢 fragment、参数排序、补空路径,但保留了 :80);我自己写的语法规范化合并 11 组;再加语义规范化合并 15 组。

规范化的结果长什么样

实测 · 本机执行输出
  输入 HTTP://Example.COM:80/a/./b/../c?b=2&a=1#frag
      yarl.URL          → http://example.com/a/c?b=2&a=1#frag
      w3lib 2.5.0       → http://example.com:80/a/c?a=1&b=2
      自写:只做语法规范化        → http://example.com/a/c?b=2&a=1
      自写:再加语义规范化        → http://example.com/a/c?a=1&b=2
  输入 http://a.test/a//b/%7Euser?utm_source=x&id=1
      yarl.URL          → http://a.test/a//b/~user?utm_source=x&id=1
      w3lib 2.5.0       → http://a.test/a//b/~user?id=1&utm_source=x
      自写:只做语法规范化        → http://a.test/a//b/~user?utm_source=x&id=1
      自写:再加语义规范化        → http://a.test/a/b/~user?id=1
  输入 http://中文.test/x
      yarl.URL          → http://xn--fiq228c.test/x
      w3lib 2.5.0       → http://xn--fiq228c.test/x
      自写:只做语法规范化        → http://xn--fiq228c.test/x
      自写:再加语义规范化        → http://xn--fiq228c.test/x

同一个输入经不同规范化器得到的字符串并不相同,所以不能混用:换一个库,历史去重集合里的 key 就对不上了。中文域名转成 punycode(xn--fiq228c.test)这一点三个库一致。

去重数量:语法与语义规范化

实测 · 本机执行输出
  原样字符串                 :8000 个 URL → 8000 个不同的
  urlsplit→geturl       :8000 个 URL → 8000 个不同的
  yarl.URL              :8000 个 URL → 6000 个不同的
  w3lib 2.5.0           :8000 个 URL → 5000 个不同的
  自写:只做语法规范化            :8000 个 URL → 5000 个不同的
  自写:再加语义规范化            :8000 个 URL → 1000 个不同的

前者(scheme 和主机小写、去默认端口、去点段、统一百分号编码、去 fragment、主机末尾的点、punycode)在 URL 的语法层面是等价的;后者(参数排序、去 utm 与 sessionid、合并重复斜杠、丢空查询)只在你确认站点这样处理时才成立,比如有的站点 // 与 / 是两个页面。8000 个 URL 经语法规范化剩 5000 个,再加语义规范化才剩下 1000 个。

路径大小写、非默认端口、http 与 https、结尾斜杠、用户信息:没有哪个规范化器把它们合并,也不该合并——这些是不同的 URL,是否实际等价要靠观察响应内容(后面的指纹和近似去重)。

分步演示

URL 规范化
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / requests 2.34.2 / aiohttp 3.14.3 / lxml 6.1.1(系统 Python);所有请求只发给本机自己起的测试服务器(127.0.0.1,端口 9960 以上,不访问任何真实网站);服务器可以按实验需要制造延迟、断连、压缩、重定向等行为,并记录收到的原始请求;计时用 time.perf_counter,数字每次运行会有小幅抖动。规范化器:yarl 1.24.5(aiohttp 依赖,str(URL(…)))、w3lib 2.5.0(pip 装进实验专用目录,canonicalize_url)、标准库 urllib.parse.urlsplit,以及我自己写的两个函数(norm_safe、norm_loose,代码在实验脚本里);用例是 20 组手写的 URL 对,去重实验是 1000 个页面 × 8 种写法(sessionid 用固定种子 5 的随机数),纯字符串计算,不发请求
    • 没有验证:RFC 3986 对规范化的规定(这里按我的理解区分“语法”和“语义”,没有逐条对照标准);站点是否真的把参数顺序、双斜杠当成同一个页面;大小写混用的路径在 Windows 或 macOS 后端上的等价性;不同库在更多边界输入(IPv6、非 ASCII 路径、非法字符)上的差异;规范化的性能
    京ICP备2026067502号-1京公网安备11010502063487号