爬虫的去重第一步是判断“这两个 URL 是不是同一个页面”。字符串不同不代表页面不同:大小写、默认端口、fragment、参数顺序、追踪参数都会让同一个页面有很多写法。这篇比较 yarl、w3lib 和自己写的规范化器,在 20 组用例和 8000 个 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,是否实际等价要靠观察响应内容(后面的指纹和近似去重)。