如何在Python中验证URL(无论格式是否正确)?

223

我从用户那里得到了url并需要返回获取的HTML。

我如何检查URL是否格式不正确?

例如:

url = 'google' # Malformed
url = 'google.com' # Malformed
url = 'http://google.com' # Valid
url = 'http://google' # Malformed

2
尝试阅读它,如果例如httplib抛出异常,那么你就知道它是无效的。并非所有格式正确的URL都是有效的! - carlpett
24
url='http://google' 并不是格式错误的。模式 + 主机名组合始终是有效的。 - Viktor Joras
17个回答

225

使用 validators 包:

>>> import validators
>>> validators.url("http://google.com")
True
>>> validators.url("http://google")
ValidationFailure(func=url, args={'value': 'http://google', 'require_tld': True})
>>> if not validators.url("http://google"):
...     print "not valid"
... 
not valid
>>>

使用pip(pip install validators)从PyPI安装validators


8
文件 URL(例如“file:///users/file.txt”)会导致错误。 - Devavrata
4
仅适用于本地主机URLValidationFailure(func=url, args={'public': False, 'value': 'http://localhost:8080'}) - Tom
2
仅适用于 http://www.googlehttp://google.www 等网址。这只是检查 http:// 和两个单词之间的 点(.) 是否存在。 - Lal
13
该软件包的验证函数有很多任意限制,因此建议将其作为通用解决方案是不可取的。 - ivan_pozdeev
1
此软件包未得到积极维护。 - Mehdi Zare
显示剩余5条评论

154

实际上,我认为这是最好的方法。

from django.core.validators import URLValidator
from django.core.exceptions import ValidationError

val = URLValidator(verify_exists=False)
try:
    val('http://www.google.com')
except ValidationError, e:
    print e
如果将verify_exists设置为True,它实际上会验证URL是否存在,否则它只会检查URL的格式是否正确。
编辑:啊对了,这个问题是一个重复问题:如何使用Django的验证器检查URL是否存在?

67
但是这仅适用于Django环境,而非其他情况。 - Yugal Jindle
29
"verify_exists"已被弃用。-1 - user67416
2
添加:从django.conf导入设置 settings.configure(DEBUG=False),并删除verify_exists以使其与Django 1.5配合使用。 - Dukeatcoding
1
@YugalJindle 没错,但是从Django中剥离它几乎是微不足道的:D。所以我使用这种方法。 - swdev
9
注意,Django 1.5及以上版本中不再有verify_exists。此外,您可以使用以下方式调用URLValidator()来替代val变量:URLValidator()('http://www.google.com') - luckydonald
显示剩余3条评论

143

基于 @DMfll 的回答,这是一个真假版本:

try:
    # python2
    from urlparse import urlparse
except:
    # python3
    from urllib.parse import urlparse

a = 'http://www.cwi.nl:80/%7Eguido/Python.html'
b = '/data/Python.html'
c = 532
d = u'dkakasdkjdjakdjadjfalskdjfalk'
e = 'https://stackoverflow.com'

def uri_validator(x):
    try:
        result = urlparse(x)
        return all([result.scheme, result.netloc])
    except:
        return False

print(uri_validator(a))
print(uri_validator(b))
print(uri_validator(c))
print(uri_validator(d))
print(uri_validator(e))

给出:

True
False
False
False
True

12
我不知道你可以用非None元素的列表来测试if语句,这很有帮助。此外,使用内置模块加1。 - Marc Maxmeister
19
这允许一切。它对于字符串“fake”甚至空字符串返回“True”。由于这些属性始终存在,列表始终具有布尔值True,因此永远不会出现任何错误。即使所有属性均为None,该列表仍将是非空的。您需要对属性进行一些验证,因为当前的方式允许一切通过。 - zondo
3
假对象的列表会被评估为True:当运行 print("I am true") if [False, None, 0, '', [], {}] else print("I am false.") 时,会打印出"I am true."。 [result.scheme, result.netloc, result.path] 的值始终为 True 。当运行 print("I am True") if [] else print("I am False.") 时,会打印出"I am false.",因此空列表为False。需要使用类似于all函数的东西来评估数组的内容。 - dmmfll
3
不确定为什么你需要这样的路径。你应该从测试中移除 result.path - Jerinaw
3
这对我来说已经足够好了,谢谢。我只是为scheme添加了一个简单的验证:if not all([result.scheme in ["file", "http", "https"], result.netloc, result.path]): - Alexander Fortin
显示剩余10条评论

134

django url 验证正则表达式 (源代码):

import re
regex = re.compile(
        r'^(?:http|ftp)s?://' # http:// or https://
        r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' #domain...
        r'localhost|' #localhost...
        r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
        r'(?::\d+)?' # optional port
        r'(?:/?|[/?]\S+)$', re.IGNORECASE)

print(re.match(regex, "http://www.example.com") is not None) # True
print(re.match(regex, "example.com") is not None)            # False

2
好奇问一下...您添加了ftp吗?还是我的Django版本太旧了? - Ruggero Turra
2
@yugal-jindle http://www.sitedomain 不是一个有效的网址。http://www.museum/ 是因为 ".museum" 是顶级域名(由ICANN[1]定义),而不是站点域名。[1] http://www.icann.org/ - glarrain
1
这个似乎不支持以 http://username:password@example.com 格式的URL。 - Adam Baxter
1
@cowlinator https://github.com/django/django/blob/stable/1.3.x/django/core/validators.py#L45 - cetver
5
这种方法不适用于IPv6网址,其形式为http://[2001:0DB8::3]:8080/index.php?valid=true#result - cimnine
显示剩余7条评论

36

现在,我使用以下内容,基于Padam的答案:

$ python --version
Python 3.6.5

这就是它的样子:

from urllib.parse import urlparse

def is_url(url):
  try:
    result = urlparse(url)
    return all([result.scheme, result.netloc])
  except ValueError:
    return False

只需使用is_url("http://www.asdf.com")

希望能帮到您!


如果域名以破折号开头,这是无效的,它将失败。https://tools.ietf.org/html/rfc952 - Björn Lindqvist
5
仅在已知URI未损坏的特殊情况下,拆分组件才是有效的。就像我之前回答其他类似问题时所说的那样,这可以验证损坏的URI,例如 https://https://https://www.foo.bar - ingyhere
截至Python 3.7.6,我已经使用"https://-wee.com"测试了这个逻辑,并且它可以正常工作。 - Jesuisme

20

我来到这个页面是为了找出一种合理的方法来验证字符串是否为“有效”的URL。下面是我使用Python3分享的解决方案。不需要额外的库。

如果您正在使用Python2,请参见https://docs.python.org/2/library/urlparse.html

如果您像我一样正在使用Python3,请参见https://docs.python.org/3.0/library/urllib.parse.html

import urllib
from pprint import pprint

invalid_url = 'dkakasdkjdjakdjadjfalskdjfalk'
valid_url = 'https://stackoverflow.com'
tokens = [urllib.parse.urlparse(url) for url in (invalid_url, valid_url)]

for token in tokens:
    pprint(token)

min_attributes = ('scheme', 'netloc')  # add attrs to your liking
for token in tokens:
    if not all([getattr(token, attr) for attr in min_attributes]):
        error = "'{url}' string has no scheme or netloc.".format(url=token.geturl())
        print(error)
    else:
        print("'{url}' is probably a valid url.".format(url=token.geturl()))

ParseResult(scheme='', netloc='', path='dkakasdkjdjakdjadjfalskdjfalk', params='', query='', fragment='')

ParseResult(scheme='https', netloc='stackoverflow.com', path='', params='', query='', fragment='')

'dkakasdkjdjakdjadjfalskdjfalk'字符串没有方案或netloc。

'https://stackoverflow.com'可能是一个有效的URL。

这里有一个更简洁的函数:

from urllib.parse import urlparse

min_attributes = ('scheme', 'netloc')


def is_valid(url, qualifying=min_attributes):
    tokens = urlparse(url)
    return all([getattr(tokens, qualifying_attr)
                for qualifying_attr in qualifying])

10

注意- 对不起,lepl不再受支持(您可以使用它,并且我认为下面的代码可以工作,但是它将不会得到更新)。

rfc 3696 http://www.faqs.org/rfcs/rfc3696.html定义了如何实现这一点(用于http url和电子邮件)。我使用lepl(一个解析器库)在 Python 中实现了其建议。请参见http://acooke.org/lepl/rfc3696.html

使用方法:

> easy_install lepl
...
> python
...
>>> from lepl.apps.rfc3696 import HttpUrl
>>> validator = HttpUrl()
>>> validator('google')
False
>>> validator('http://google')
False
>>> validator('http://google.com')
True

2
整洁,但FTP或HTTPS呢? - Adam Parkin
6
你还没有fork这段代码并进行实现吗?它是开源的。 - andrew cooke
1
lepl现已被作者停止更新。http://www.acooke.org/lepl/discontinued.html 编辑:嘿,我刚意识到你就是作者。 - Emmett Butler
1
注意:lepl.apps.rfc3696在Python 3.7.4中无法正常工作。 - Sheile

6

编辑

正如@Kwame所指出的,即使没有.com.co等,下面的代码也可以验证url。

@Blaise还指出,像https://www.google这样的URL是一个有效的URL,你需要单独进行DNS检查以检查它是否解析。

以下代码简单易懂且有效:

min_attr包含了定义URL有效性所需的基本字符串集合,即http://部分和google.com部分。

urlparse.scheme存储http://,而

urlparse.netloc存储域名google.com

from urlparse import urlparse
def url_check(url):

    min_attr = ('scheme' , 'netloc')
    try:
        result = urlparse(url)
        if all([result.scheme, result.netloc]):
            return True
        else:
            return False
    except:
        return False

all() 如果其中所有变量都为true,则返回true。 因此,如果 result.schemeresult.netloc 存在,即具有某些值,则该URL是有效的,因此返回 True


哦,好发现...我想我得把我的代码拿回来了。 你更喜欢什么,除了正则表达式还有其他选项吗? - Padam Sethia
https://www.google 是一个有效的URL。它可能实际上无法解析,但如果您关心这一点,您需要进行DNS检查。 - Blaise
吞噬异常 - ivan_pozdeev

5
这里有一个正则表达式的解决方案,因为最受欢迎的正则表达式对于顶级域名等奇怪情况不起作用。以下是一些测试案例。
regex = re.compile(
    r"(\w+://)?"                # protocol                      (optional)
    r"(\w+\.)?"                 # host                          (optional)
    r"(([\w-]+)\.(\w+))"        # domain
    r"(\.\w+)*"                 # top-level domain              (optional, can have > 1)
    r"([\w\-\._\~/]*)*(?<!\.)"  # path, params, anchors, etc.   (optional)
)

cases = [
    "http://www.google.com",
    "https://www.google.com",
    "http://google.com",
    "https://google.com",
    "www.google.com",
    "google.com",
    "http://www.google.com/~as_db3.2123/134-1a",
    "https://www.google.com/~as_db3.2123/134-1a",
    "http://google.com/~as_db3.2123/134-1a",
    "https://google.com/~as_db3.2123/134-1a",
    "www.google.com/~as_db3.2123/134-1a",
    "google.com/~as_db3.2123/134-1a",
    # .co.uk top level
    "http://www.google.co.uk",
    "https://www.google.co.uk",
    "http://google.co.uk",
    "https://google.co.uk",
    "www.google.co.uk",
    "google.co.uk",
    "http://www.google.co.uk/~as_db3.2123/134-1a",
    "https://www.google.co.uk/~as_db3.2123/134-1a",
    "http://google.co.uk/~as_db3.2123/134-1a",
    "https://google.co.uk/~as_db3.2123/134-1a",
    "www.google.co.uk/~as_db3.2123/134-1a",
    "google.co.uk/~as_db3.2123/134-1a",
    "https://...",
    "https://..",
    "https://.",
    "https://.google.com",
    "https://..google.com",
    "https://...google.com",
    "https://.google..com",
    "https://.google...com"
    "https://...google..com",
    "https://...google...com",
    ".google.com",
    ".google.co."
    "https://google.co."
]
for c in cases:
    print(c, regex.match(c).span()[1] - regex.match(c).span()[0] == len(c))

编辑:根据nickh的建议,将域名添加了连字符。


1
最后一行的错误已经修复:print(c, x.span()[1] - x.span()[0] == len(c) if (x := regex.match(c)) else False) - pmiguelpinto
谢谢Miguel,但我想警告那些不使用Python 3.8+的人,因为":="对于早期版本无效。 - Başar Söker
它不匹配带有连字符的域名,例如 https://api-example.com 考虑使用 (\w+://)?(\w+.)?(([\w-]+).(\w+))(.\w+)([\w-._~/])*(?<!.) - nickh
它也不匹配一个单词,例如“fred”。它会显示错误AttributeError: 'NoneType' object has no attribute 'span' - colin0117
@Táwros 这是一个问题,不过它并不是一个边缘情况 - 它是一个无效的URL - 所以正则表达式解决方案应该拒绝它。 - colin0117
显示剩余2条评论

3

可以使用Pydantic来实现。我不太熟悉它的限制,但这是一个选项,没有人建议过。

我看到许多人在之前的答案中对ftp和文件URL提出了疑问,因此建议查阅文档,因为Pydantic有许多用于验证的类型,如FileUrl、AnyUrl甚至数据库URL类型。

以下是一个简单的使用示例:

from requests import get, HTTPError, ConnectionError
from pydantic import BaseModel, AnyHttpUrl, ValidationError
    
class MyConfModel(BaseModel):
    URI: AnyHttpUrl

try:
    myAddress = MyConfModel(URI = "http://myurl.com/")
    req = get(myAddress.URI, verify=False)
    print(myAddress.URI)

except(ValidationError):
    print('Invalid destination')

Pydantic还会引发异常(pydantic.ValidationError),可用于处理错误。

我已使用以下模式进行了测试:


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