Obtendo partes de uma URL (Regex)
-
09-06-2019 - |
Pergunta
Dado o URL (linha única):
http://test.example.com/dir/subdir/file.html
Como posso extrair as seguintes partes usando expressões regulares:
- O subdomínio (teste)
- O domínio (exemplo.com)
- O caminho sem o arquivo (/dir/subdir/)
- O arquivo (arquivo.html)
- O caminho com o arquivo (/dir/subdir/file.html)
- O URL sem o caminho (http://teste.exemplo.com)
- (adicione qualquer outro que você acha que seria útil)
A regex deve funcionar corretamente mesmo se eu inserir o seguinte URL:
http://example.example.com/example/example/example.html
Solução
Um único regex para analisar e quebrar um URL completo, incluindo parâmetros de consulta e âncoras, por exemplo
https://www.google.com/dir/1/2/search.html?arg=0-a&arg1=1-b&arg3-c#hash
^((http[s]?|ftp):\/)?\/?([^:\/\s]+)((\/\w+)*\/)([\w\-\.]+[^#?\s]+)(.*)?(#[\w\-]+)?$
Posições RexEx:
url:RegExp['$&'],
protocolo:RegExp.$2,
anfitrião:RegExp.$3,
caminho:RegExp.$4,
arquivo:RegExp.$6,
consulta:RegExp.$7,
hash:RegExp.$8
você poderia então analisar ainda mais o host (delimitado por '.') com bastante facilidade.
O que EU faria é usar algo assim:
/*
^(.*:)//([A-Za-z0-9\-\.]+)(:[0-9]+)?(.*)$
*/
proto $1
host $2
port $3
the-rest $4
a análise adicional de 'o resto' será o mais específica possível.Fazer isso em um regex é, bem, um pouco louco.
Outras dicas
Sei que estou atrasado para a festa, mas há uma maneira simples de permitir que o navegador analise uma URL para você sem uma regex:
var a = document.createElement('a');
a.href = 'http://www.example.com:123/foo/bar.html?fox=trot#foo';
['href','protocol','host','hostname','port','pathname','search','hash'].forEach(function(k) {
console.log(k+':', a[k]);
});
/*//Output:
href: http://www.example.com:123/foo/bar.html?fox=trot#foo
protocol: http:
host: www.example.com:123
hostname: www.example.com
port: 123
pathname: /foo/bar.html
search: ?fox=trot
hash: #foo
*/
Estou alguns anos atrasado para a festa, mas estou surpreso que ninguém tenha mencionado que a especificação Uniform Resource Identifier tem um seção sobre análise de URIs com uma expressão regular.A expressão regular, escrita por Berners-Lee, et al., é:
^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? 12 3 4 5 6 7 8 9
Os números na segunda linha acima servem apenas para auxiliar na legibilidade;Eles indicam os pontos de referência para cada subexpressão (ou seja, cada parêntese emparelhada).Nós nos referimos ao valor correspondente à subexpressão como $.Por exemplo, combinando a expressão acima com
http://www.ics.uci.edu/pub/ietf/uri/#Related
resulta nas seguintes correspondências de subexpressão:
$1 = http: $2 = http $3 = //www.ics.uci.edu $4 = www.ics.uci.edu $5 = /pub/ietf/uri/ $6 = <undefined> $7 = <undefined> $8 = #Related $9 = Related
Para constar, descobri que precisava escapar das barras em JavaScript:
^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
Descobri que a resposta mais votada (resposta do hometoast) não funciona perfeitamente para mim.Dois problemas:
- Ele não pode lidar com o número da porta.
- A parte hash está quebrada.
A seguir está uma versão modificada:
^((http[s]?|ftp):\/)?\/?([^:\/\s]+)(:([^\/]*))?((\/\w+)*\/)([\w\-\.]+[^#?\s]+)(\?([^#]*))?(#(.*))?$
A posição das peças é a seguinte:
int SCHEMA = 2, DOMAIN = 3, PORT = 5, PATH = 6, FILE = 8, QUERYSTRING = 9, HASH = 12
Edição postada por um usuário anônimo:
function getFileName(path) {
return path.match(/^((http[s]?|ftp):\/)?\/?([^:\/\s]+)(:([^\/]*))?((\/[\w\/-]+)*\/)([\w\-\.]+[^#?\s]+)(\?([^#]*))?(#(.*))?$/i)[8];
}
Eu precisava de uma expressão regular para corresponder a todos os URLs e criei esta:
/(?:([^\:]*)\:\/\/)?(?:([^\:\@]*)(?:\:([^\@]*))?\@)?(?:([^\/\:]*)\.(?=[^\.\/\:]*\.[^\.\/\:]*))?([^\.\/\:]*)(?:\.([^\/\.\:]*))?(?:\:([0-9]*))?(\/[^\?#]*(?=.*?\/)\/)?([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?/
Corresponde a todos os URLs, qualquer protocolo, até mesmo URLs como
ftp://user:pass@www.cs.server.com:8080/dir1/dir2/file.php?param1=value1#hashtag
O resultado (em JavaScript) é assim:
["ftp", "user", "pass", "www.cs", "server", "com", "8080", "/dir1/dir2/", "file.php", "param1=value1", "hashtag"]
Um URL como
mailto://admin@www.cs.server.com
se parece com isso:
["mailto", "admin", undefined, "www.cs", "server", "com", undefined, undefined, undefined, undefined, undefined]
Eu estava tentando resolver isso em javascript, que deveria ser tratado por:
var url = new URL('http://a:b@example.com:890/path/wah@t/foo.js?foo=bar&bingobang=&king=kong@kong.com#foobar/bing/bo@ng?bang');
já que (no Chrome, pelo menos) ele analisa:
{
"hash": "#foobar/bing/bo@ng?bang",
"search": "?foo=bar&bingobang=&king=kong@kong.com",
"pathname": "/path/wah@t/foo.js",
"port": "890",
"hostname": "example.com",
"host": "example.com:890",
"password": "b",
"username": "a",
"protocol": "http:",
"origin": "http://example.com:890",
"href": "http://a:b@example.com:890/path/wah@t/foo.js?foo=bar&bingobang=&king=kong@kong.com#foobar/bing/bo@ng?bang"
}
No entanto, isso não é compatível com vários navegadores (https://developer.mozilla.org/en-US/docs/Web/API/URL), então juntei isso para extrair as mesmas peças acima:
^(?:(?:(([^:\/#\?]+:)?(?:(?:\/\/)(?:(?:(?:([^:@\/#\?]+)(?:\:([^:@\/#\?]*))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((?:\/?(?:[^\/\?#]+\/+)*)(?:[^\?#]*)))?(\?[^#]+)?)(#.*)?
O crédito por este regex vai para https://gist.github.com/rpflorence quem postou esse jsperf http://jsperf.com/url-parsing (originalmente encontrado aqui: https://gist.github.com/jlong/2428561#comment-310066) que criou a regex na qual ela foi originalmente baseada.
As peças estão nesta ordem:
var keys = [
"href", // http://user:pass@host.com:81/directory/file.ext?query=1#anchor
"origin", // http://user:pass@host.com:81
"protocol", // http:
"username", // user
"password", // pass
"host", // host.com:81
"hostname", // host.com
"port", // 81
"pathname", // /directory/file.ext
"search", // ?query=1
"hash" // #anchor
];
Há também uma pequena biblioteca que o envolve e fornece parâmetros de consulta:
https://github.com/sadams/lite-url (também disponível no Bower)
Caso tenha alguma melhoria, por favor crie um pull request com mais testes e eu aceitarei e mesclarei com agradecimento.
Proponha uma solução muito mais legível (em Python, mas que se aplica a qualquer regex):
def url_path_to_dict(path):
pattern = (r'^'
r'((?P<schema>.+?)://)?'
r'((?P<user>.+?)(:(?P<password>.*?))?@)?'
r'(?P<host>.*?)'
r'(:(?P<port>\d+?))?'
r'(?P<path>/.*?)?'
r'(?P<query>[?].*?)?'
r'$'
)
regex = re.compile(pattern)
m = regex.match(path)
d = m.groupdict() if m is not None else None
return d
def main():
print url_path_to_dict('http://example.example.com/example/example/example.html')
Impressões:
{
'host': 'example.example.com',
'user': None,
'path': '/example/example/example.html',
'query': None,
'password': None,
'port': None,
'schema': 'http'
}
subdomínio e domínio são difíceis porque o subdomínio pode ter várias partes, assim como o domínio de nível superior, http://sub1.sub2.domain.co.uk/
the path without the file : http://[^/]+/((?:[^/]+/)*(?:[^/]+$)?)
the file : http://[^/]+/(?:[^/]+/)*((?:[^/.]+\.)+[^/.]+)$
the path with the file : http://[^/]+/(.*)
the URL without the path : (http://[^/]+/)
(Markdown não é muito amigável para regexes)
Esta versão melhorada deve funcionar de forma tão confiável quanto um analisador.
// Applies to URI, not just URL or URN:
// http://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Relationship_to_URL_and_URN
//
// http://labs.apache.org/webarch/uri/rfc/rfc3986.html#regexp
//
// (?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?
//
// http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax
//
// $@ matches the entire uri
// $1 matches scheme (ftp, http, mailto, mshelp, ymsgr, etc)
// $2 matches authority (host, user:pwd@host, etc)
// $3 matches path
// $4 matches query (http GET REST api, etc)
// $5 matches fragment (html anchor, etc)
//
// Match specific schemes, non-optional authority, disallow white-space so can delimit in text, and allow 'www.' w/o scheme
// Note the schemes must match ^[^\s|:/?#]+(?:\|[^\s|:/?#]+)*$
//
// (?:()(www\.[^\s/?#]+\.[^\s/?#]+)|(schemes)://([^\s/?#]*))([^\s?#]*)(?:\?([^\s#]*))?(#(\S*))?
//
// Validate the authority with an orthogonal RegExp, so the RegExp above won’t fail to match any valid urls.
function uriRegExp( flags, schemes/* = null*/, noSubMatches/* = false*/ )
{
if( !schemes )
schemes = '[^\\s:\/?#]+'
else if( !RegExp( /^[^\s|:\/?#]+(?:\|[^\s|:\/?#]+)*$/ ).test( schemes ) )
throw TypeError( 'expected URI schemes' )
return noSubMatches ? new RegExp( '(?:www\\.[^\\s/?#]+\\.[^\\s/?#]+|' + schemes + '://[^\\s/?#]*)[^\\s?#]*(?:\\?[^\\s#]*)?(?:#\\S*)?', flags ) :
new RegExp( '(?:()(www\\.[^\\s/?#]+\\.[^\\s/?#]+)|(' + schemes + ')://([^\\s/?#]*))([^\\s?#]*)(?:\\?([^\\s#]*))?(?:#(\\S*))?', flags )
}
// http://en.wikipedia.org/wiki/URI_scheme#Official_IANA-registered_schemes
function uriSchemesRegExp()
{
return 'about|callto|ftp|gtalk|http|https|irc|ircs|javascript|mailto|mshelp|sftp|ssh|steam|tel|view-source|ymsgr'
}
Experimente o seguinte:
^((ht|f)tp(s?)\:\/\/|~/|/)?([\w]+:\w+@)?([a-zA-Z]{1}([\w\-]+\.)+([\w]{2,5}))(:[\d]{1,5})?((/?\w+/)+|/?)(\w+\.[\w]{3,4})?((\?\w+=\w+)?(&\w+=\w+)*)?
Suporta HTTP/FTP, subdomínios, pastas, arquivos etc.
Encontrei em uma rápida pesquisa no Google:
http://geekswithblogs.net/casualjim/archive/2005/12/01/61722.aspx
/^((?P<scheme>https?|ftp):\/)?\/?((?P<username>.*?)(:(?P<password>.*?)|)@)?(?P<hostname>[^:\/\s]+)(?P<port>:([^\/]*))?(?P<path>(\/\w+)*\/)(?P<filename>[-\w.]+[^#?\s]*)?(?P<query>\?([^#]*))?(?P<fragment>#(.*))?$/
Da minha resposta em um pergunta semelhante.Funciona melhor do que alguns dos outros mencionados porque tinham alguns bugs (como não suportar nome de usuário/senha, não suportar nomes de arquivos de um único caractere, identificadores de fragmentos quebrados).
Você pode obter todos os http/https, host, porta, caminho e também consultas usando o objeto Uri no .NET.apenas a tarefa difícil é dividir o host em subdomínio, nome de domínio e TLD.
Não existe um padrão para fazer isso e não pode simplesmente usar a análise de string ou RegEx para produzir o resultado correto.A princípio, estou usando a função RegEx, mas nem todos os URLs podem analisar o subdomínio corretamente.A maneira prática é usar uma lista de TLDs.Depois que um TLD para um URL é definido, a parte esquerda é o domínio e o restante é o subdomínio.
No entanto, a lista precisa ser mantida, pois novos TLDs são possíveis.O momento atual que conheço é publicsuffix.org manter a lista mais recente e você pode usar ferramentas de analisador de nome de domínio do código do Google para analisar a lista de sufixos públicos e obter o subdomínio, domínio e TLD facilmente usando o objeto DomainName:nomedodomínio.SubDomínio, nomedodomínio.Domínio e nomeDodomínio.TLD.
Estas respostas também são úteis:Obtenha o subdomínio de um URL
CaLLMeLaNN
Aqui está um que é completo e não depende de nenhum protocolo.
function getServerURL(url) {
var m = url.match("(^(?:(?:.*?)?//)?[^/?#;]*)");
console.log(m[1]) // Remove this
return m[1];
}
getServerURL("http://dev.test.se")
getServerURL("http://dev.test.se/")
getServerURL("//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js")
getServerURL("//")
getServerURL("www.dev.test.se/sdas/dsads")
getServerURL("www.dev.test.se/")
getServerURL("www.dev.test.se?abc=32")
getServerURL("www.dev.test.se#abc")
getServerURL("//dev.test.se?sads")
getServerURL("http://www.dev.test.se#321")
getServerURL("http://localhost:8080/sads")
getServerURL("https://localhost:8080?sdsa")
Impressões
http://dev.test.se
http://dev.test.se
//ajax.googleapis.com
//
www.dev.test.se
www.dev.test.se
www.dev.test.se
www.dev.test.se
//dev.test.se
http://www.dev.test.se
http://localhost:8080
https://localhost:8080
Nenhuma das opções acima funcionou para mim.Aqui está o que acabei usando:
/^(?:((?:https?|s?ftp):)\/\/)([^:\/\s]+)(?::(\d*))?(?:\/([^\s?#]+)?([?][^?#]*)?(#.*)?)?/
Gosto da regex que foi publicada em "Javascript:As partes boas".Não é muito curto e nem muito complexo.Esta página no github também contém o código JavaScript que a utiliza.Mas pode ser adaptado para qualquer idioma.https://gist.github.com/voodooGQ/4057330
Java oferece uma classe de URL que fará isso. Consultar objetos de URL.
Por outro lado, o PHP oferece parse_url().
Eu recomendaria não usar regex.Uma chamada de API como WinHttpCrackUrl() é menos propenso a erros.
http://msdn.microsoft.com/en-us/library/aa384092%28VS.85%29.aspx
Tentei alguns deles que não atendiam às minhas necessidades, especialmente os mais votados, que não encontraram um URL sem caminho (http://exemplo.com/)
também a falta de nomes de grupos o tornou inutilizável no ansible (ou talvez minhas habilidades em jinja2 estejam faltando).
então esta é a minha versão ligeiramente modificada, sendo a fonte a versão mais votada aqui:
^((?P<protocol>http[s]?|ftp):\/)?\/?(?P<host>[^:\/\s]+)(?P<path>((\/\w+)*\/)([\w\-\.]+[^#?\s]+))*(.*)?(#[\w\-]+)?$
Usando http://www.fileformat.info/tool/regex.htm o regex do hometoast funciona muito bem.
Mas o negócio é o seguinte: quero usar diferentes padrões de regex em diferentes situações em meu programa.
Por exemplo, eu tenho esse URL e uma enumeração que lista todos os URLs suportados em meu programa.Cada objeto na enumeração possui um método getRegexPattern que retorna o padrão regex que será então usado para comparar com uma URL.Se o padrão regex específico retornar verdadeiro, sei que esse URL é compatível com meu programa.Portanto, cada enumeração tem seu próprio regex dependendo de onde deve procurar dentro da URL.
A sugestão do Hometoast é ótima, mas no meu caso acho que não ajudaria (a menos que eu copie e cole o mesmo regex em todas as enumerações).
Por isso queria que a resposta desse o regex para cada situação separadamente.Embora +1 para hometoast.;)
Eu sei que você está afirmando ser independente de linguagem, mas você pode nos dizer o que está usando apenas para sabermos quais recursos de regex você possui?
Se você tiver recursos para não capturar correspondências, poderá modificar a expressão do hometoast para que as subexpressões que você não está interessado em capturar sejam configuradas assim:
(?:SOMESTUFF)
Você ainda teria que copiar e colar (e modificar ligeiramente) o Regex em vários lugares, mas isso faz sentido - você não está apenas verificando se a subexpressão existe, mas sim se ela existe como parte de um URL.Usar o modificador de não captura para subexpressões pode fornecer o que você precisa e nada mais, o que, se estou lendo corretamente, é o que você deseja.
Assim como uma pequena nota, a expressão de hometoast não precisa colocar colchetes em torno do 's' para 'https', já que ele só tem um caractere lá.Os quantificadores quantificam o caractere (ou classe de caracteres ou subexpressão) que os precede diretamente.Então:
https?
corresponderia perfeitamente a 'http' ou 'https'.
regexp para obter o caminho da URL sem o arquivo.
url = 'http://domínio/dir1/dir2/somefile'url.scan (/^(http: // [^/]+) ((?:/[^/]+)+(? =/))?/? (?: [^/]+)? $ /i).to_s
Pode ser útil adicionar um caminho relativo a este URL.
String s = "https://www.thomas-bayer.com/axis2/services/BLZService?wsdl";
String regex = "(^http.?://)(.*?)([/\\?]{1,})(.*)";
System.out.println("1: " + s.replaceAll(regex, "$1"));
System.out.println("2: " + s.replaceAll(regex, "$2"));
System.out.println("3: " + s.replaceAll(regex, "$3"));
System.out.println("4: " + s.replaceAll(regex, "$4"));
Fornecerá a seguinte saída:
1:https://
2:www.thomas-bayer.com
3: /
4:eixo2/serviços/BLZService?wsdl
Se você alterar o URL para
Sequência s = "https://www.thomas-bayer.com?wsdl=qwerwer&ttt=888";a saída será a seguinte:
1:https://
2:www.thomas-bayer.com
3: ?
4:wsdl=qwerwer&ttt=888
aproveitar..
Yosi Lev
O regex para fazer a análise completa é horrível.Incluí referências anteriores nomeadas para legibilidade e dividi cada parte em linhas separadas, mas ainda fica assim:
^(?:(?P<protocol>\w+(?=:\/\/))(?::\/\/))?
(?:(?P<host>(?:(?:&(?:amp|apos|gt|lt|nbsp|quot|bull|hellip|[lr][ds]quo|[mn]dash|permil|\#[1-9][0-9]{1,3}|[A-Za-z][0-9A-Za-z]+);)|[^\/?#:]+)(?::(?P<port>[0-9]+))?)\/)?
(?:(?P<path>(?:(?:&(?:amp|apos|gt|lt|nbsp|quot|bull|hellip|[lr][ds]quo|[mn]dash|permil|\#[1-9][0-9]{1,3}|[A-Za-z][0-9A-Za-z]+);)|[^?#])+)\/)?
(?P<file>(?:(?:&(?:amp|apos|gt|lt|nbsp|quot|bull|hellip|[lr][ds]quo|[mn]dash|permil|\#[1-9][0-9]{1,3}|[A-Za-z][0-9A-Za-z]+);)|[^?#])+)
(?:\?(?P<querystring>(?:(?:&(?:amp|apos|gt|lt|nbsp|quot|bull|hellip|[lr][ds]quo|[mn]dash|permil|\#[1-9][0-9]{1,3}|[A-Za-z][0-9A-Za-z]+);)|[^#])+))?
(?:#(?P<fragment>.*))?$
O que exige que seja tão detalhado é que, exceto o protocolo ou a porta, qualquer uma das partes pode conter entidades HTML, o que torna o delineamento do fragmento bastante complicado.Portanto, nos últimos casos - host, caminho, arquivo, string de consulta e fragmento, permitimos qualquer entidade html ou qualquer caractere que não seja um ?
ou #
.A regex para uma entidade HTML é assim:
$htmlentity = "&(?:amp|apos|gt|lt|nbsp|quot|bull|hellip|[lr][ds]quo|[mn]dash|permil|\#[1-9][0-9]{1,3}|[A-Za-z][0-9A-Za-z]+);"
Quando isso é extraído (usei uma sintaxe de bigode para representá-lo), fica um pouco mais legível:
^(?:(?P<protocol>(?:ht|f)tps?|\w+(?=:\/\/))(?::\/\/))?
(?:(?P<host>(?:{{htmlentity}}|[^\/?#:])+(?::(?P<port>[0-9]+))?)\/)?
(?:(?P<path>(?:{{htmlentity}}|[^?#])+)\/)?
(?P<file>(?:{{htmlentity}}|[^?#])+)
(?:\?(?P<querystring>(?:{{htmlentity}};|[^#])+))?
(?:#(?P<fragment>.*))?$
Em JavaScript, é claro, você não pode usar referências anteriores nomeadas, então a regex se torna
^(?:(\w+(?=:\/\/))(?::\/\/))?(?:((?:(?:&(?:amp|apos|gt|lt|nbsp|quot|bull|hellip|[lr][ds]quo|[mn]dash|permil|\#[1-9][0-9]{1,3}|[A-Za-z][0-9A-Za-z]+);)|[^\/?#:]+)(?::([0-9]+))?)\/)?(?:((?:(?:&(?:amp|apos|gt|lt|nbsp|quot|bull|hellip|[lr][ds]quo|[mn]dash|permil|\#[1-9][0-9]{1,3}|[A-Za-z][0-9A-Za-z]+);)|[^?#])+)\/)?((?:(?:&(?:amp|apos|gt|lt|nbsp|quot|bull|hellip|[lr][ds]quo|[mn]dash|permil|\#[1-9][0-9]{1,3}|[A-Za-z][0-9A-Za-z]+);)|[^?#])+)(?:\?((?:(?:&(?:amp|apos|gt|lt|nbsp|quot|bull|hellip|[lr][ds]quo|[mn]dash|permil|\#[1-9][0-9]{1,3}|[A-Za-z][0-9A-Za-z]+);)|[^#])+))?(?:#(.*))?$
e em cada partida o protocolo é \1
, o anfitrião é \2
, o porto é \3
, o caminho \4
, o arquivo \5
, a cadeia de consulta \6
, e o fragmento \7
.
//USING REGEX
/**
* Parse URL to get information
*
* @param url the URL string to parse
* @return parsed the URL parsed or null
*/
var UrlParser = function (url) {
"use strict";
var regx = /^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,
matches = regx.exec(url),
parser = null;
if (null !== matches) {
parser = {
href : matches[0],
withoutHash : matches[1],
url : matches[2],
origin : matches[3],
protocol : matches[4],
protocolseparator : matches[5],
credhost : matches[6],
cred : matches[7],
user : matches[8],
pass : matches[9],
host : matches[10],
hostname : matches[11],
port : matches[12],
pathname : matches[13],
segment1 : matches[14],
segment2 : matches[15],
search : matches[16],
hash : matches[17]
};
}
return parser;
};
var parsedURL=UrlParser(url);
console.log(parsedURL);