O parsing de URL (do inglês parse, «analisar») é a decomposição do endereço de uma página web em suas partes constituintes: o protocolo, o domínio, a porta, o caminho, os parâmetros de consulta e o fragmento. De uma string «bruta» como https://example.com/catalog?page=2 obtemos um objeto estruturado, em que cada campo pode ser consultado separadamente e, se necessário, modificado.
À primeira vista, a tarefa parece trivial: «fatiar uma string por caracteres». Na prática, as URLs têm uma especificação formal (RFC 3986) com uma infinidade de nuances: codificação de caracteres especiais, links relativos, partes opcionais do endereço, normalização. Por isso quase todas as linguagens incluem uma ferramenta padrão para parsear URLs, e escrever uma análise própria com expressões regulares praticamente nunca é necessário.
Do que se compõe uma URL
Vamos analisar um endereço o mais «completo» possível para ver todos os componentes:
https://user:pass@example.com:8080/catalog/items?page=2&sort=price#reviews
└─┬─┘ └──┬───┘ └────┬────┘└─┬─┘└─────┬──────┘ └──────┬──────┘ └──┬──┘
scheme userinfo host port path query fragment| Componente | Valor no exemplo | Função |
|---|---|---|
| scheme (esquema) | https |
Protocolo de acesso: http, https, ftp, mailto etc. |
| userinfo | user:pass |
Credenciais (pouco usado, sobretudo para FTP/basic auth) |
| host | example.com |
Nome de domínio ou endereço IP |
| port (porta) | 8080 |
Porta; quando não é indicada, vale a padrão do esquema (80 para http, 443 para https) |
| path (caminho) | /catalog/items |
Caminho até o recurso no servidor |
| query (consulta) | page=2&sort=price |
Parâmetros de consulta no formato «chave=valor» |
| fragment (âncora) | reviews |
Referência a uma seção específica dentro da página; não é enviado ao servidor |
São exatamente essas as partes que o parser extrai. Com elas já dá para trabalhar: obter o valor de um parâmetro GET específico, substituir o domínio, descartar o fragmento ou montar uma nova URL a partir dos componentes modificados.
Para que serve o parsing de URL
O parsing de URL aparece em praticamente qualquer aplicação que trabalhe com endereços web. Os cenários mais frequentes:
- Extração dos parâmetros de consulta. É preciso saber em que página do catálogo o usuário está (
?page=5), qual filtro ele escolheu ou qual tag UTM chegou junto. O parser converte a query string em um dicionário prático. - Validação e sanitização de links. Antes de salvar no banco uma URL enviada pelo usuário ou de usá-la em um redirecionamento, é importante conferir se o esquema é permitido (que não seja
javascript:, por exemplo) e se o domínio consta na lista de permissões. - Roteamento. Os frameworks web analisam o caminho e os parâmetros para determinar qual controller invocar.
- Análises e registro de logs. Para agrupar os acessos por domínio ou por caminho, primeiro é preciso decompor os endereços em partes.
- Web scraping e crawling. Aqui o parsing de URL é especialmente importante; falamos dele em detalhe mais abaixo.
O parsing de URL na construção de crawlers
Um crawler (robô de rastreamento) percorre um site passando de página em página através dos links. Para entender em que o crawling difere do scraping e do parsing propriamente dito, recomendamos ler antes o nosso material «Scraping, parsing e crawling: quais as diferenças?». Em resumo: o crawler cuida do percurso e da descoberta das páginas; o scraper, da extração dos dados; e o parsing, da análise do conteúdo obtido, endereços incluídos.
A principal «armadilha» ao percorrer um site está no fato de que os links do HTML quase sempre são relativos, não absolutos. Na marcação você encontrará:
<a href="/about">Quem somos</a>
<a href="../catalog/phones">Telefones</a>
<a href="page2.html">Próxima</a>
<a href="#reviews">Avaliações</a>O robô não pode seguir o link /about nem page2.html tal como estão: não são endereços completos. Para continuar o rastreamento, o link relativo precisa ser convertido em absoluto tomando como referência o endereço da página atual (a URL base). É disso, justamente, que o parsing de URL cuida:
| URL base da página | Link relativo | Resultado (URL absoluta) |
|---|---|---|
https://example.com/catalog/items/ |
/about |
https://example.com/about |
https://example.com/catalog/items/ |
../phones |
https://example.com/catalog/phones |
https://example.com/catalog/items/ |
page2.html |
https://example.com/catalog/items/page2.html |
https://example.com/catalog/items/ |
https://cdn.example.com/x |
https://cdn.example.com/x (já é absoluta) |
Essa operação chama-se resolução do link (URL resolution), e quase todas as linguagens oferecem uma função pronta para isso: urljoin em Python, o construtor new URL(href, base) em JavaScript, URI.resolve() em Java, ResolveReference() em Go.
Normalização e eliminação de duplicatas
Além de converter os links em absolutos, interessa ao crawler normalizar os endereços para não percorrer a mesma página várias vezes. Técnicas habituais:
- descartar o fragmento
#fragment: ele aponta para uma área dentro da página, não para um recurso à parte (/page#tope/pagesão a mesma página); - passar o esquema e o domínio para minúsculas;
- remover a porta padrão (
:443no https); - ordenar ou filtrar os parâmetros de consulta (por exemplo, remover as tags UTM).
Após a normalização, as URLs absolutas são guardadas no conjunto de endereços já visitados: assim o robô não entra em loops nem faz requisições a mais.
Exemplos de implementação de parsers de URL
A seguir, como decompor uma URL em partes e como resolver um link relativo com as ferramentas padrão das linguagens mais populares.
Python
Na biblioteca padrão, quem cuida de tudo é o módulo urllib.parse.
from urllib.parse import urlparse, urljoin, urldefrag, parse_qs
url = "https://example.com:8080/catalog/items?page=2&sort=price#reviews"
parsed = urlparse(url)
print(parsed.scheme) # https
print(parsed.hostname) # example.com
print(parsed.port) # 8080
print(parsed.path) # /catalog/items
print(parsed.query) # page=2&sort=price
print(parsed.fragment) # reviews
# Parâmetros de consulta -> dicionário
print(parse_qs(parsed.query)) # {'page': ['2'], 'sort': ['price']}
# Resolução de um link relativo (para o crawler)
base = "https://example.com/catalog/items/"
print(urljoin(base, "../about")) # https://example.com/catalog/about
print(urljoin(base, "/contacts")) # https://example.com/contacts
print(urljoin(base, "page2.html")) # https://example.com/catalog/items/page2.html
# Descartar o fragmento ao normalizar
print(urldefrag("https://example.com/page#reviews")[0]) # https://example.com/pageJavaScript (navegador e Node.js)
A classe integrada URL funciona igual no navegador e no Node.js. Ela também sabe resolver links relativos: para isso, o endereço base é passado como segundo argumento.
const url = new URL("https://example.com:8080/catalog/items?page=2&sort=price#reviews");
console.log(url.protocol); // "https:"
console.log(url.hostname); // "example.com"
console.log(url.port); // "8080"
console.log(url.pathname); // "/catalog/items"
console.log(url.search); // "?page=2&sort=price"
console.log(url.hash); // "#reviews"
// Parâmetros de consulta
console.log(url.searchParams.get("page")); // "2"
// Resolução de um link relativo
const base = "https://example.com/catalog/items/";
console.log(new URL("../about", base).href); // "https://example.com/catalog/about"
console.log(new URL("/contacts", base).href); // "https://example.com/contacts"PHP
A análise básica fica por conta de parse_url(), e parse_str() converte a query string em um array.
$url = "https://example.com:8080/catalog/items?page=2&sort=price#reviews";
$parts = parse_url($url);
echo $parts['scheme']; // https
echo $parts['host']; // example.com
echo $parts['port']; // 8080
echo $parts['path']; // /catalog/items
echo $parts['fragment']; // reviews
parse_str($parts['query'], $query);
print_r($query); // ['page' => '2', 'sort' => 'price']No PHP não existe um equivalente embutido do urljoin; por isso, para resolver links relativos nos crawlers costuma-se adotar uma biblioteca — por exemplo, league/uri ou os helpers do Guzzle (GuzzleHttp\Psr7\UriResolver).
Java
A biblioteca padrão inclui a classe java.net.URI para trabalhar com endereços. O método resolve() entrega diretamente a resolução do link relativo.
import java.net.URI;
URI uri = URI.create("https://example.com:8080/catalog/items?page=2&sort=price#reviews");
System.out.println(uri.getScheme()); // https
System.out.println(uri.getHost()); // example.com
System.out.println(uri.getPort()); // 8080
System.out.println(uri.getPath()); // /catalog/items
System.out.println(uri.getQuery()); // page=2&sort=price
System.out.println(uri.getFragment()); // reviews
// Resolução de um link relativo
URI base = URI.create("https://example.com/catalog/items/");
System.out.println(base.resolve("../about")); // https://example.com/catalog/about
System.out.println(base.resolve("/contacts")); // https://example.com/contactsGo
O pacote net/url da biblioteca padrão cobre tanto a análise quanto a resolução de links (ResolveReference).
package main
import (
"fmt"
"net/url"
)
func main() {
u, _ := url.Parse("https://example.com:8080/catalog/items?page=2&sort=price#reviews")
fmt.Println(u.Scheme) // https
fmt.Println(u.Hostname()) // example.com
fmt.Println(u.Port()) // 8080
fmt.Println(u.Path) // /catalog/items
fmt.Println(u.RawQuery) // page=2&sort=price
fmt.Println(u.Query().Get("page")) // 2
// Resolução de um link relativo
base, _ := url.Parse("https://example.com/catalog/items/")
ref, _ := url.Parse("../about")
fmt.Println(base.ResolveReference(ref)) // https://example.com/catalog/about
}Exemplo prático: coletando todos os links de uma página
Vamos juntar tudo no passo típico de um crawler: parsear o HTML, extrair todos os links e convertê-los em absolutos. Um exemplo em Python com requests e BeautifulSoup:
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urldefrag
def extract_links(page_url: str) -> set[str]:
response = requests.get(page_url, timeout=10)
soup = BeautifulSoup(response.text, "html.parser")
links = set()
for a in soup.find_all("a", href=True):
# 1. convertemos o link relativo em absoluto
absolute = urljoin(page_url, a["href"])
# 2. descartamos o fragmento para não gerar duplicatas
absolute, _ = urldefrag(absolute)
# 3. deixamos apenas os links http(s)
if absolute.startswith(("http://", "https://")):
links.add(absolute)
return links
for link in extract_links("https://example.com/catalog/"):
print(link)Aqui o parsing de URL cumpre duas funções-chave: urljoin transforma os href relativos em endereços completos que o robô poderá seguir depois, e urldefrag evita tratar /page e /page#top como páginas distintas.
Erros frequentes
- Parsear as URLs com expressões regulares. Pela abundância de casos-limite da especificação, uma análise caseira quase sempre sai incompleta. Use as bibliotecas padrão.
- Esquecer os links relativos. Sem a conversão para absoluto, o crawler «tropeça» já no primeiro link interno.
- Não normalizar os endereços. Sem descartar o fragmento, ordenar os parâmetros e unificar maiúsculas e minúsculas, o robô percorre as mesmas páginas repetidas vezes.
- Confiar no esquema sem conferir. Antes de um redirecionamento ou de inserir um link, certifique-se de que o esquema é seguro (
http/https); caso contrário, fica aberta a porta para um XSS viajavascript:.
Conclusão
O parsing de URL é uma operação básica, mas importante: converte um endereço em forma de string em uma estrutura com a qual é cômodo trabalhar. Nas aplicações comuns, serve para extrair parâmetros, validar e rotear; nos crawlers, torna-se crítico: sem converter os links relativos em absolutos e sem normalização, percorrer um site é simplesmente impossível. A boa notícia é que quase todas as linguagens incluem para isso uma ferramenta padrão e confiável, de modo que não é preciso reinventar a roda.