获取URL的部分(正则表达式)

164

给定以下URL(单行):
http://test.example.com/dir/subdir/file.html

如何使用正则表达式提取以下部分:

  1. 子域名(test)
  2. 域名(example.com)
  3. 没有文件的路径(/dir/subdir/)
  4. 文件名(file.html)
  5. 包含文件的路径(/dir/subdir/file.html)
  6. 不包括路径的URL (http://test.example.com)
  7. (添加任何你认为有用的内容)

即使我输入以下URL,正则表达式也应该能够正确工作:

http://example.example.com/example/example/example.html

1
这不是一个直接的答案,但大多数网络库都有一个完成此任务的函数。该函数通常被称为类似于“CrackUrl”的东西。如果存在这样的函数,请使用它,它几乎可以保证比任何手工编写的代码更可靠和更高效。 - Konrad Rudolph
9
请解释一下为什么需要使用正则表达式来完成这项任务。如果这是作业的话,请说明,因为这是你的限制条件。否则,使用与特定语言相关的更好解决方案比使用正则表达式更好。 - Andy Lester
1
第一个和最后一个样本的链接已经失效。 - the Tin Man
在这里,您可以找到如何提取方案、域、TLD、端口和查询路径的方法:https://dev59.com/WWkw5IYBdhLWcg3wn7-O#31952097 - Paolo Rovelli
30个回答

170

一个正则表达式,可以解析并分离完整的URL,包括查询参数和锚点,例如:

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\-]+)?$

正则表达式位置:

url:RegExp['$&'],

protocol:RegExp.$2,

host:RegExp.$3,

path:RegExp.$4,

file:RegExp.$6,

query:RegExp.$7,

hash:RegExp.$8

你可以很容易地进一步解析主机(使用“.”作为分隔符)。

我会像这样使用:

/*
    ^(.*:)//([A-Za-z0-9\-\.]+)(:[0-9]+)?(.*)$
*/
proto $1
host $2
port $3
the-rest $4

进一步解析“剩下的部分”,尽可能具体。用一个正则表达式完成这个任务,嗯,有点疯狂。


4
截至2010年10月20日,链接http://codesnippets.joyent.com/posts/show/523无法使用。 - W3Max
19
问题出在这部分:(.*)?,因为Kleene星号已经可以接受0个或多个匹配项,所以?(0个或1个)会让它变得混乱。我通过将(.*)?更改为(.+)?来修正它。你也可以直接删除? - rossipedia
3
你好 Dve,我对提取类似 http://www.example.com:8080/.... 样式的 URL 中的 example.com 进行了进一步改进。以下是正则表达式: ^((http[s]?|ftp):\/\/)?\/?([^\/\.]+\.)*?([^\/\.]+\.[^:\/\s\.]{2,3}(\.[^:\/\s\.]{2,3})?(:\d+)?)($|\/)([^#?\s]+)?(.*?)?(#[\w\-]+)?$ - mnacos
4
并且证明没有正则表达式是完美的,这里有一个立即更正的:^((http[s]?|ftp):\/\/)?\/?([^\/\.]+\.)*?([^\/\.]+\.[^:\/\s\.]{2,3}(\.[^:\/\s\.]{2,3})?)(:\d+)?($|\/)([^#?\s]+)?(.*?)?(#[\w\-]+)?$ - mnacos
5
我将这个正则表达式修改了一下,以便识别 URL 的所有部分(改进版) - Python 代码^((?P<scheme>[^:/?#]+):(?=//))?(//)?(((?P<login>[^:]+)(?::(?P<password>[^@]+)?)?@)?(?P<host>[^@/?#:]*)(?::(?P<port>\d+)?)?)?(?P<path>[^?#]*)(\?(?P<query>[^#]*))?(#(?P<fragment>.*))? 代码你可以在 pythex.org 上看到这段代码的运行情况。 - arannasousa
显示剩余11条评论

113

我有点晚来参加这个派对,但我很惊讶没有人提到统一资源标识符规范中使用正则表达式解析URI的部分。该正则表达式由Berners-Lee等人编写,如下所示:

^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
 12            3  4          5       6  7        8 9

The numbers in the second line above are only to assist readability; they indicate the reference points for each subexpression (i.e., each paired parenthesis). We refer to the value matched for subexpression as $. For example, matching the above expression to

http://www.ics.uci.edu/pub/ietf/uri/#Related

results in the following subexpression matches:

$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

就我个人而言,我发现在JavaScript中必须转义斜杠:

^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?


5
好的回答!从RFC中选择内容肯定不会是做错事。 - frankster
1
这不解析查询参数。 - Rémy DAVID
4
在我看来,这是最好的选择。特别是解决了其他选项存在的两个问题:1:正确处理其他协议,如 ftp://mailto://2:正确处理 usernamepassword。 这些可选字段与主机名和端口号一样,由冒号分隔,这将使大多数其他正则表达式出现错误。 @RémyDAVID 浏览器的位置对象也不能正常解析查询字符串。如果需要解析查询字符串,请查看我的微型库 uqs - Stijn de Witt
3
这个答案应该得到更多的点赞,因为它涵盖了几乎所有协议。 - Tianzhen Lin
4
当使用隐含HTTP协议的用户名/密码(我承认是一种奇怪且技术上无效的语法)时,例如user:pass@example.com,它会中断。RFC3986规定:“包含冒号字符(例如“this:that”)的路径段不能用作相对路径引用的第一个段,因为它会被误认为是一个协议名称。这样的段必须在前面加上点段(例如“./this:that”)来组成相对路径引用”。 - Matt Chambers
这不会像http://www.ics.uci.edu:9000/pub/ietf/uri/#Related那样将域名与端口分开。 - Fernando Correia

88

我意识到我来晚了,但是有一种简单的方法让浏览器为您解析URL而无需使用正则表达式:

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
*/

12
鉴于原问题被标记为“不依赖于特定编程语言”,那么这段话的语言是什么? - MarkHu
1
请注意,此解决方案需要协议前缀的存在,例如 http://,以正确显示协议、主机和主机名属性。否则,URL 的开头直到第一个斜杠将进入协议属性。 - Oleksii Aza
1
我相信这个虽然简单,但比正则表达式解析慢得多。 - demisx
1
如果我们这样做,你也可以使用 var url = new URL(someUrl) - gman
@gman:不幸的是,在IE11和Edge中,URL()构造函数未实现。 - Artif3x
显示剩余3条评论

35

我发现最高票答案(hometoast的答案)对我来说并不完美。存在两个问题:

  1. 它无法处理端口号。
  2. 哈希部分已经破碎。

以下是修改后的版本:

^((http[s]?|ftp):\/)?\/?([^:\/\s]+)(:([^\/]*))?((\/\w+)*\/)([\w\-\.]+[^#?\s]+)(\?([^#]*))?(#(.*))?$

零件的位置如下:

int SCHEMA = 2, DOMAIN = 3, PORT = 5, PATH = 6, FILE = 8, QUERYSTRING = 9, HASH = 12

匿名用户发布的编辑内容:

function getFileName(path) {
    return path.match(/^((http[s]?|ftp):\/)?\/?([^:\/\s]+)(:([^\/]*))?((\/[\w\/-]+)*\/)([\w\-\.]+[^#?\s]+)(\?([^#]*))?(#(.*))?$/i)[8];
}

2
请注意,如果URL在域名后面没有路径(例如http://www.example.com),或者路径只有一个字符(例如http://www.example.com/a),则此方法无法正常工作。 - Fernando Correia

12

我需要一个正则表达式来匹配所有的URL,我写了这个:

/(?:([^\:]*)\:\/\/)?(?:([^\:\@]*)(?:\:([^\@]*))?\@)?(?:([^\/\:]*)\.(?=[^\.\/\:]*\.[^\.\/\:]*))?([^\.\/\:]*)(?:\.([^\/\.\:]*))?(?:\:([0-9]*))?(\/[^\?#]*(?=.*?\/)\/)?([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?/

它匹配所有的URL,任何协议,甚至是像以下这样的URL:

ftp://user:pass@www.cs.server.com:8080/dir1/dir2/file.php?param1=value1#hashtag

在JavaScript中,结果如下:

["ftp", "user", "pass", "www.cs", "server", "com", "8080", "/dir1/dir2/", "file.php", "param1=value1", "hashtag"]

像这样的网址

mailto://admin@www.cs.server.com

看起来像这样:

["mailto", "admin", undefined, "www.cs", "server", "com", undefined, undefined, undefined, undefined, undefined] 

4
如果您想匹配整个域名/IP地址(没有点分隔),请使用以下正则表达式:/(?:([^\:]*)\:\/\/)?(?:([^\:\@]*)(?:\:([^\@]*))?\@)?(?:([^\/\:]*))?(?:\:([0-9]*))?\/(\/[^\?#]*(?=.*?\/)\/)?([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?/ - lepe

11
我试图用JavaScript解决这个问题,应该这样处理:
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');

因为(至少在Chrome中)它被解析为:

{
  "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"
}

然而,这不是跨浏览器的(https://developer.mozilla.org/en-US/docs/Web/API/URL),因此我将其拼凑在一起,以从上面提取相同的部分:
^(?:(?:(([^:\/#\?]+:)?(?:(?:\/\/)(?:(?:(?:([^:@\/#\?]+)(?:\:([^:@\/#\?]*))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((?:\/?(?:[^\/\?#]+\/+)*)(?:[^\?#]*)))?(\?[^#]+)?)(#.*)?

这个正则表达式的功劳归功于https://gist.github.com/rpflorence,他发布了这个jsperf http://jsperf.com/url-parsing(最初在这里发现:https://gist.github.com/jlong/2428561#comment-310066),他想出了最初基于的正则表达式。

这些部分按照以下顺序排列:

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
];

还有一个小型库可以封装它并提供查询参数:

https://github.com/sadams/lite-url(也可在bower上获得)

如果您有改进意见,请创建一个拉取请求,附带更多测试,我将感激地接受并合并。


这很棒,但是真的需要一个像这样的版本,可以提取子域而不是重复的主机名。因此,如果我有例如http://test1.dev.mydomain.com/,它将提取出test1.dev. - user692942
这个非常好用。我一直在寻找一种从URL中提取不寻常的身份验证参数的方法,而这个工具运行得非常完美。 - Aaron M

8
提出一种更易读的解决方案(使用Python编写,但适用于任何正则表达式):
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')

打印:

{
'host': 'example.example.com', 
'user': None, 
'path': '/example/example/example.html', 
'query': None, 
'password': None, 
'port': None, 
'schema': 'http'
}

6

子域名和域名很难区分,因为子域名可以有多个部分,顶级域名也可以如此。例如: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对正则表达式不是很友好)


3
非常有用 - 我增加了一个额外的(http(s?)://[^/]+/)以便也可以获取https。 - Mojowen

5
这个改进版本应该能像解析器一样可靠地工作。
   // 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'
   }

5
尝试以下操作:
^((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+)*)?

它支持HTTP / FTP、子域名、文件夹、文件等。

我是从谷歌快速搜索中发现它的:

链接


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接