从页面里提取出的链接大多是相对的,爬虫要把它们变成绝对 URL 才能继续抓。看起来 urljoin 一行就行,可是浏览器遵循的 WHATWG 标准和 Python 的 RFC 风格有细节差别,
结果相同的 25 个,不同的 11 个
./、../、绝对路径、协议相对 //、只有 ? 或 #、目录结尾的斜杠、点段等 25 种用例,urljoin 与 WHATWG 的结果一致。
空字符串:相对链接 ''
Python:http://a.test/dir/sub/page.html?x=1#top
Node :http://a.test/dir/sub/page.html?x=1
前后有空格:相对链接 ' x.html '
Python:http://a.test/dir/sub/x.html
Node :http://a.test/dir/sub/x.html
反斜杠:相对链接 '..\\x.html'
Python:http://a.test/dir/sub/..\x.html
Node :http://a.test/dir/x.html
反斜杠开头的绝对路径:相对链接 '\\x.html'
Python:http://a.test/dir/sub/\x.html
Node :http://a.test/x.html
不同 scheme 的 scheme:foo:相对链接 'https:foo'(base http://a.test/dir/)
Python:https:foo
Node :https://foo/
三个斜杠 http:///x:相对链接 'http:///x'
Python:http://a.test/x
Node :http://x/
大写 scheme:相对链接 'HTTP://C.test/Z'
Python:http://C.test/Z
Node :http://c.test/Z
端口:相对链接 'http://a.test:80/x'
Python:http://a.test:80/x
Node :http://a.test/x
中文路径:相对链接 '/路径/页面.html'
Python:http://a.test/路径/页面.html
Node :http://a.test/%E8%B7%AF%E5%BE%84/%E9%A1%B5%E9%9D%A2.html
中文域名绝对链接:相对链接 'http://中文.test/x'
Python:http://中文.test/x
Node :http://xn--fiq228c.test/x
查询串里有空格:相对链接 '/s?q=a b'
Python:http://a.test/s?q=a b
Node :http://a.test/s?q=a%20b11 个不同的结果集中在:空字符串是否保留 fragment、前后空格、反斜杠、scheme 后没有斜杠的 https:foo、主机大小写、默认端口、中文路径/域名/空格的编码。浏览器会修正这些写法,urljoin 不会,所以同一个链接,你的爬虫和浏览器访问的 URL 字符串可能不同。去重前最好统一一遍(上一篇)。
urljoin(base, 'javascript:void(0)') = 'javascript:void(0)';scheme = 'javascript' urljoin(base, 'mailto:a@b.test') = 'mailto:a@b.test';scheme = 'mailto' urljoin(base, 'data:text/plain,hi') = 'data:text/plain,hi';scheme = 'data' urljoin(base, 'tel:+123') = 'tel:+123';scheme = 'tel' urljoin(base, 'ftp://f.test/x') = 'ftp://f.test/x';scheme = 'ftp' urljoin(base, 'file:///etc/passwd') = 'file:///etc/passwd';scheme = 'file'
urljoin 对 javascript:、mailto:、data:、tel:、ftp:、file: 一律原样返回。把它们交给下载器之前必须检查 scheme。
页面 URL = http://a.test/dir/page.html
没有 <base>:['http://a.test/dir/x.html', 'http://a.test/root.html', 'http://a.test/up.html']
<base href="http://cdn.test/assets/">:['http://cdn.test/assets/x.html', 'http://cdn.test/root.html', 'http://cdn.test/up.html']
同样的页面,make_links_absolute(url, resolve_base_href=False)(文档字符串说"忽略 <base>"):['http://cdn.test/assets/x.html', 'http://cdn.test/root.html', 'http://cdn.test/up.html']
→ 实测 <base> 仍然生效。lxml 6.1.1 源码里的原因:
make_links_absolute:if resolve_base_href:
make_links_absolute:self.rewrite_links(link_repl)
rewrite_links:def rewrite_links(self, link_repl_func, resolve_base_href=True,
rewrite_links:elif resolve_base_href:
rewrite_links:self.resolve_base_href()
<base href="/other/"> (相对的 base):['http://a.test/other/x.html', 'http://a.test/root.html', 'http://a.test/up.html']
两个 <base>:['http://two.test/x']
请求 /old 被重定向到 /new/dir/page.html;相对链接 next.html:用请求时的 URL 当 base → <站点>/next.html;用 r.url(最终 URL)当 base → <站点>/new/dir/next.html页面里的 <base href> 会改变所有相对链接的基准;两个 <base> 时 lxml 取最后一个。lxml 的 make_links_absolute(resolve_base_href=False) 在 6.1.1 里并没有忽略 <base>(源码里 rewrite_links 的默认参数让它仍然生效)。被重定向的页面,基准应该是最终 URL(r.url)而不是请求时的 URL。