Phân tích URL là gì?
Phân tích URL là quá trình tách một Uniform Resource Locator thành các thành phần riêng lẻ: giao thức (scheme), hostname, port, pathname, tham số truy vấn và định danh fragment. Mọi URL đều tuân theo cấu trúc được định nghĩa bởi RFC 3986 và WHATWG URL Standard. Bộ phân tích URL đọc chuỗi thô, xác định từng đoạn dựa vào các ký tự phân cách (://, :, /, ?, #, &, =) và trả về chúng dưới dạng các trường riêng biệt, có thể truy cập độc lập.
Trình duyệt thực hiện phân tích URL mỗi khi bạn nhập địa chỉ hoặc nhấp vào một liên kết. Hàm khởi tạo URL trong JavaScript, mô-đun urllib.parse của Python và gói net/url của Go đều triển khai bộ phân tích tuân theo cùng các quy tắc cấu trúc. Phân tích URL là phép nghịch đảo của mã hóa URL: thay vì chuyển đổi ký tự để truyền tải an toàn, bạn tách một URL đã được hình thành thành các phần cấu thành nó.
Một URL điển hình như https://api.example.com:8080/v1/users?page=2&limit=10#section chứa sáu thành phần riêng biệt. Các ký tự phân cách — ://, :, /, ?, &, =, và # — là yếu tố làm cho việc phân tích trở nên xác định: mỗi ký tự báo hiệu một ranh giới và cho phép bộ phân tích trích xuất các trường mà không có sự mơ hồ.
Tại sao sử dụng công cụ phân tích URL trực tuyến?
Tách URL bằng mắt thường dễ xảy ra lỗi, đặc biệt khi chuỗi chứa các ký tự được mã hóa, nhiều tham số truy vấn hoặc các cổng không tiêu chuẩn. Công cụ này phân tích URL bằng cùng thuật toán tuân thủ WHATWG mà trình duyệt sử dụng và hiển thị từng thành phần trong một bảng rõ ràng, có thể sao chép.
Các trường hợp sử dụng công cụ phân tích URL
Tham chiếu thành phần URL
Bảng dưới đây hiển thị mọi thuộc tính được trả về bởi hàm khởi tạo URL trong JavaScript khi phân tích URL. Các thành phần tương tự tồn tại trong kết quả urlparse của Python, struct url.URL của Go và đầu ra parse_url của PHP, mặc dù tên thuộc tính khác nhau giữa các ngôn ngữ.
| Thuộc tính | Ví dụ | Mô tả |
|---|---|---|
| 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 Standard so với RFC 3986
Hai đặc tả định nghĩa cách URL nên được phân tích. Chúng đồng ý về cấu trúc cơ bản nhưng khác nhau ở các trường hợp ngoại lệ — và sự khác biệt đó thường là nguyên nhân khi trình duyệt xử lý URL khác với máy chủ của bạn.
Trong thực tế, hầu hết sự khác biệt xuất hiện khi phân tích URL có tên miền quốc tế (IDN), thiếu scheme hoặc ký tự bất thường. Bộ phân tích WHATWG tự động chuyển đổi hostname IDN sang Punycode, trong khi bộ phân tích RFC 3986 nghiêm ngặt có thể từ chối chúng. Nếu bạn dán URL vào công cụ này và thấy kết quả khác với code phía máy chủ tạo ra, thì sự khác biệt giữa WHATWG và RFC rất có thể là nguyên nhân.
Ví dụ code
Mọi ngôn ngữ lớn đều có bộ phân tích URL tích hợp sẵn. Các ví dụ dưới đây phân tích cùng một URL và trích xuất các thành phần của nó. Lưu ý sự khác biệt nhỏ về tên gọi giữa các ngôn ngữ: Python dùng scheme thay vì protocol, và Go dùng RawQuery thay vì 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"