什么是 URL 解析?
URL 解析是将统一资源定位符(URL)拆分为各个组成部分的过程,包括协议(scheme)、主机名、端口、路径、查询参数和片段标识符。每个 URL 都遵循 RFC 3986 和 WHATWG URL 标准所定义的结构。URL 解析器读取原始字符串,通过定界符(://、:、/、?、#、&、=)识别各段,并将其作为独立的可访问字段返回。
浏览器在每次输入地址或点击链接时都会执行 URL 解析。JavaScript 的 URL 构造函数、Python 的 urllib.parse 模块以及 Go 的 net/url 包都实现了遵循相同结构规则的解析器。解析 URL 是 URL 编码的逆过程:不是将字符转换为安全传输格式,而是将已构成的 URL 分解为各组成部分。
一个典型的 URL,如 https://api.example.com:8080/v1/users?page=2&limit=10#section,包含六个独立组成部分。定界符——://、:、/、?、&、= 和 #——使解析具有确定性:每个定界符标志着一个边界,使解析器能够无歧义地提取各字段。
为什么使用在线 URL 解析器?
手动拆分 URL 容易出错,尤其是当字符串中包含编码字符、多个查询参数或非标准端口时。本工具使用与浏览器相同的符合 WHATWG 标准的算法解析 URL,并以清晰、可复制的表格展示每个组成部分。
URL 解析器使用场景
URL 组成部分参考
下表展示了 JavaScript URL 构造函数解析 URL 时返回的所有属性。Python 的 urlparse 结果、Go 的 url.URL 结构体以及 PHP 的 parse_url 输出中也存在相同的组成部分,但各语言的属性名称有所不同。
| 属性 | 示例 | 说明 |
|---|---|---|
| protocol | https: | Scheme including the trailing colon |
| hostname | api.example.com | Domain name or IP address |
| port | 8080 | Port number (empty string if default) |
| pathname | /v1/users | Path starting with / |
| search | ?page=2&limit=10 | Query string including the leading ? |
| hash | #section | Fragment identifier including the leading # |
| origin | https://api.example.com:8080 | protocol + hostname + port |
| host | api.example.com:8080 | hostname + port |
| username | admin | Credentials before @ (rarely used in practice) |
| password | secret | Credentials before @ (avoid in production URLs) |
| href | (full URL) | The complete, serialized URL string |
WHATWG URL 标准与 RFC 3986
两个规范定义了 URL 的解析方式。它们在基本结构上达成一致,但在边界情况上存在分歧——这种分歧通常就是浏览器与服务器处理同一 URL 时产生差异的根源。
实际上,大多数差异出现在解析含国际化域名(IDN)、缺少 scheme 或特殊字符的 URL 时。WHATWG 解析器会自动将 IDN 主机名转换为 Punycode,而严格的 RFC 3986 解析器可能会拒绝此类输入。如果您将 URL 粘贴到本工具后看到与服务端代码不同的结果,最可能的原因就是 WHATWG 与 RFC 3986 之间的差异。
代码示例
各主流语言都内置了 URL 解析器。以下示例解析同一个 URL 并提取其组成部分。注意各语言命名上的细微差异:Python 使用 scheme 而非 protocol,Go 使用 RawQuery 而非 search。
const url = new URL('https://api.example.com:8080/v1/users?page=2&limit=10#section')
url.protocol // → "https:"
url.hostname // → "api.example.com"
url.port // → "8080"
url.pathname // → "/v1/users"
url.search // → "?page=2&limit=10"
url.hash // → "#section"
// Iterate over query parameters
for (const [key, value] of url.searchParams) {
console.log(`${key} = ${value}`)
}
// → "page = 2"
// → "limit = 10"
// Modify and re-serialize
url.searchParams.set('page', '3')
url.toString()
// → "https://api.example.com:8080/v1/users?page=3&limit=10#section"from urllib.parse import urlparse, parse_qs
result = urlparse('https://api.example.com:8080/v1/users?page=2&limit=10#section')
result.scheme # → 'https'
result.hostname # → 'api.example.com'
result.port # → 8080
result.path # → '/v1/users'
result.query # → 'page=2&limit=10'
result.fragment # → 'section'
# Parse query string into a dict
params = parse_qs(result.query)
params['page'] # → ['2']
params['limit'] # → ['10']
# Reconstruct with modifications
from urllib.parse import urlencode, urlunparse
new_query = urlencode({'page': '3', 'limit': '10'})
urlunparse(result._replace(query=new_query))
# → 'https://api.example.com:8080/v1/users?page=3&limit=10#section'package main
import (
"fmt"
"net/url"
)
func main() {
u, err := url.Parse("https://api.example.com:8080/v1/users?page=2&limit=10#section")
if err != nil {
panic(err)
}
fmt.Println(u.Scheme) // → "https"
fmt.Println(u.Hostname()) // → "api.example.com"
fmt.Println(u.Port()) // → "8080"
fmt.Println(u.Path) // → "/v1/users"
fmt.Println(u.RawQuery) // → "page=2&limit=10"
fmt.Println(u.Fragment) // → "section"
// Query params as map
q := u.Query()
fmt.Println(q.Get("page")) // → "2"
fmt.Println(q.Get("limit")) // → "10"
}<?php $url = 'https://api.example.com:8080/v1/users?page=2&limit=10#section'; $parts = parse_url($url); $parts['scheme']; // → "https" $parts['host']; // → "api.example.com" $parts['port']; // → 8080 $parts['path']; // → "/v1/users" $parts['query']; // → "page=2&limit=10" $parts['fragment']; // → "section" // Parse query string into an array parse_str($parts['query'], $params); $params['page']; // → "2" $params['limit']; // → "10"