【问题标题】:How to validate a url in Python? (Malformed or not)如何在 Python 中验证 url? (格式不正确)
【发布时间】:2011-11-01 21:49:27
【问题描述】:

我有来自用户的url,我必须用获取的 HTML 回复。

如何检查网址是否格式错误?

例如:

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

【问题讨论】:

标签: python url malformedurlexception


【解决方案1】:

使用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) 安装它from PyPI

【讨论】:

  • 它会抛出文件 url 错误。比如“file:///users/file.txt”
  • localhost url validators.url("http://localhost:8080") ValidationFailure(func=url, args={'public': False, 'value': 'http://localhost:8080'}) 失败
  • http://www.googlehttp://google.www有效。这只是检查http://a dot (.) between two words
  • 包的验证 fn 有许多任意限制,因此建议将其作为通用解决方案是一个糟糕的建议。
  • 输入[4]:validators.url("google.fndwjbveiuw")输出[4]:真
【解决方案2】:

其实我觉得这是最好的办法。

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 是否存在,否则它只会检查其格式是否正确。

编辑:啊,是的,这个问题与此重复:How can I check if a URL exists with Django’s validators?

【讨论】:

  • 但这仅适用于 django 环境,否则无效。
  • verify_exists 已弃用。 -1
  • 添加:从 django.conf 导入设置 settings.configure(DEBUG=False) 并删除 verify_exists 以使其与 django 1.5 一起使用
  • @YugalJindle 正确,但从 Django 中剥离它几乎是微不足道的:D。所以,我用这个方法
  • 注意,使用 django >= 1.5 不再有verify_exists。此外,您可以将其称为 URLValidator()('http://www.google.com'),而不是 val 变量
【解决方案3】:

django url 验证正则表达式 (source):

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

【讨论】:

【解决方案4】:

True 或 False 版本,基于 @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

【讨论】:

  • 我不知道您可以使用非 None 元素列表来测试 if 语句。这很有帮助。还 +1 使用内置模块
  • 这允许一切。它为字符串 fake 或什至为空字符串返回 True。永远不会有任何错误,因为这些属性始终存在,并且列表将始终具有布尔值 True,因为它包含这些属性。即使所有属性都为无,列表仍然是非空的。您需要对属性进行一些验证,因为一切都按照您现在拥有的方式进行。
  • 假对象列表评估为真:print("I am true") if [False, None, 0, '', [], {}] else print("I am false.") 打印“我是真的”。当我运行它时。 [result.scheme, result.netloc, result.path] 始终计算为 Trueprint("I am True") if [] else print("I am False.") 打印“我是假的”。所以空列表是错误的。数组的内容需要使用类似 all 的函数进行评估。
  • 不确定为什么需要这样的路径。您应该从测试中删除 result.path
  • 这对我来说已经足够了,谢谢。我刚刚为scheme添加了一个简单的验证:if not all([result.scheme in ["file", "http", "https"], result.netloc, result.path]):
【解决方案5】:

现在,我根据 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")

希望对你有帮助!

【讨论】:

  • 如果域名以破折号开头,则失败,这是无效的。 tools.ietf.org/html/rfc952
  • 只有在 URI 已知格式不正确的特殊情况下拆分组件才有用。正如我之前对其他类似答案的回答,这会验证格式错误的 URI,例如 https://https://https://www.foo.bar
【解决方案6】:

我登陆此页面试图找出一种将字符串验证为“有效”网址的合理方法。我在这里分享我使用 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])

【讨论】:

    【解决方案7】:

    注意 - 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
    

    【讨论】:

    • 不错,但是 FTP 或 HTTPS 呢?
    • 你还没有分叉代码并实现它们?它是开源的。
    • lepl 现在已被作者停止使用 acooke.org/lepl/discontinued.html 编辑:嘿,刚刚意识到你作者
    • 注意:lepl.apps.rfc3696 在 Python 3.7.4 中不起作用
    【解决方案8】:

    编辑

    正如@Kwame 所指出的,即使.com.co 等不存在,以下代码也会验证网址。

    @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

    【讨论】:

    • 哦,不错的收获.. 我想我必须收回我的代码。你喜欢什么,除了正则表达式还有其他选择吗?
    • https://www.google 是一个有效的 URL。它实际上可能无法解决,但如果您关心它,您需要进行 DNS 检查。
    • 吞下异常
    【解决方案9】:

    使用 urllib 和类似 Django 的正则表达式验证 URL

    Django URL 验证正则表达式实际上非常好,但我需要针对我的用例对其进行一些调整。 随意调整以适应您的需求!

    Python 3.7

    import re
    import urllib
    
    # Check https://regex101.com/r/A326u1/5 for reference
    DOMAIN_FORMAT = re.compile(
        r"(?:^(\w{1,255}):(.{1,255})@|^)" # http basic authentication [optional]
        r"(?:(?:(?=\S{0,253}(?:$|:))" # check full domain length to be less than or equal to 253 (starting after http basic auth, stopping before port)
        r"((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+" # check for at least one subdomain (maximum length per subdomain: 63 characters), dashes in between allowed
        r"(?:[a-z0-9]{1,63})))" # check for top level domain, no dashes allowed
        r"|localhost)" # accept also "localhost" only
        r"(:\d{1,5})?", # port [optional]
        re.IGNORECASE
    )
    SCHEME_FORMAT = re.compile(
        r"^(http|hxxp|ftp|fxp)s?$", # scheme: http(s) or ftp(s)
        re.IGNORECASE
    )
    
    def validate_url(url: str):
        url = url.strip()
    
        if not url:
            raise Exception("No URL specified")
    
        if len(url) > 2048:
            raise Exception("URL exceeds its maximum length of 2048 characters (given length={})".format(len(url)))
    
        result = urllib.parse.urlparse(url)
        scheme = result.scheme
        domain = result.netloc
    
        if not scheme:
            raise Exception("No URL scheme specified")
    
        if not re.fullmatch(SCHEME_FORMAT, scheme):
            raise Exception("URL scheme must either be http(s) or ftp(s) (given scheme={})".format(scheme))
    
        if not domain:
            raise Exception("No URL domain specified")
    
        if not re.fullmatch(DOMAIN_FORMAT, domain):
            raise Exception("URL domain malformed (domain={})".format(domain))
    
        return url
    

    说明

    • 代码仅验证给定 URL 的 schemenetloc 部分。 (为了正确执行此操作,我将带有 urllib.parse.urlparse() 的 URL 拆分为两个相应的部分,然后与相应的正则表达式匹配。)
    • netloc 部分在第一次出现斜线 / 之前停止,因此 port 数字仍然是 netloc 的一部分,例如:

      https://www.google.com:80/search?q=python
      ^^^^^   ^^^^^^^^^^^^^^^^^
        |             |      
        |             +-- netloc (aka "domain" in my code)
        +-- scheme
      
    • IPv4 地址也经过验证

    IPv6 支持

    如果您希望 URL 验证器也适用于 IPv6 地址,请执行以下操作:

    • Markus Jarderot's answer添加is_valid_ipv6(ip),它有一个非常好的IPv6验证器正则表达式
    • and not is_valid_ipv6(domain) 添加到最后一个if

    示例

    以下是netloc(又名domain)部分的正则表达式示例:

    【讨论】:

      【解决方案10】:

      上述所有解决方案都将“http://www.google.com/path,www.yahoo.com/path”之类的字符串识别为有效。这个解决方案总是能正常工作

      import re
      
      # URL-link validation
      ip_middle_octet = u"(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5]))"
      ip_last_octet = u"(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))"
      
      URL_PATTERN = re.compile(
                              u"^"
                              # protocol identifier
                              u"(?:(?:https?|ftp|rtsp|rtp|mmp)://)"
                              # user:pass authentication
                              u"(?:\S+(?::\S*)?@)?"
                              u"(?:"
                              u"(?P<private_ip>"
                              # IP address exclusion
                              # private & local networks
                              u"(?:localhost)|"
                              u"(?:(?:10|127)" + ip_middle_octet + u"{2}" + ip_last_octet + u")|"
                              u"(?:(?:169\.254|192\.168)" + ip_middle_octet + ip_last_octet + u")|"
                              u"(?:172\.(?:1[6-9]|2\d|3[0-1])" + ip_middle_octet + ip_last_octet + u"))"
                              u"|"
                              # IP address dotted notation octets
                              # excludes loopback network 0.0.0.0
                              # excludes reserved space >= 224.0.0.0
                              # excludes network & broadcast addresses
                              # (first & last IP address of each class)
                              u"(?P<public_ip>"
                              u"(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])"
                              u"" + ip_middle_octet + u"{2}"
                              u"" + ip_last_octet + u")"
                              u"|"
                              # host name
                              u"(?:(?:[a-z\u00a1-\uffff0-9_-]-?)*[a-z\u00a1-\uffff0-9_-]+)"
                              # domain name
                              u"(?:\.(?:[a-z\u00a1-\uffff0-9_-]-?)*[a-z\u00a1-\uffff0-9_-]+)*"
                              # TLD identifier
                              u"(?:\.(?:[a-z\u00a1-\uffff]{2,}))"
                              u")"
                              # port number
                              u"(?::\d{2,5})?"
                              # resource path
                              u"(?:/\S*)?"
                              # query string
                              u"(?:\?\S*)?"
                              u"$",
                              re.UNICODE | re.IGNORECASE
                             )
      def url_validate(url):   
          """ URL string validation
          """                                                                                                                                                      
          return re.compile(URL_PATTERN).match(url)
      

      【讨论】:

      • google.com/path,www.yahoo.com/path 有效。请参阅RFC 3986pathsegments 组成,pchars 可能是 sub-delims,其中之一是 ","
      • 是的,符号“,”包含在可接受的子分隔符列表中,但是我的示例中的行,即使在可怕的梦中,也不能是有效的 url =)
      【解决方案11】:

      这是一个正则表达式解决方案,因为最高投票的正则表达式不适用于顶级域等奇怪的情况。下面是一些测试用例。

      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))
      

      【讨论】:

      • 最后一行错误已修复:print(c, x.span()[1] - x.span()[0] == len(c) if (x := regex.match(c)) else False)
      • 感谢 Miguel,但我想警告其他不使用 Python 3.8+ 的人,因为 ":=" 对以前的版本无效。
      【解决方案12】:

      不直接相关,但通常需要确定某些令牌是否可以是 url,不一定 100% 正确形成(即省略 https 部分等等)。我已经阅读了这篇文章,但没有找到解决方案,所以为了完整起见,我在这里发布自己的解决方案。

      def get_domain_suffixes():
          import requests
          res=requests.get('https://publicsuffix.org/list/public_suffix_list.dat')
          lst=set()
          for line in res.text.split('\n'):
              if not line.startswith('//'):
                  domains=line.split('.')
                  cand=domains[-1]
                  if cand:
                      lst.add('.'+cand)
          return tuple(sorted(lst))
      
      domain_suffixes=get_domain_suffixes()
      
      def reminds_url(txt:str):
          """
          >>> reminds_url('yandex.ru.com/somepath')
          True
          
          """
          ltext=txt.lower().split('/')[0]
          return ltext.startswith(('http','www','ftp')) or ltext.endswith(domain_suffixes)
      

      【讨论】:

        【解决方案13】:

        基于 Dominic Tarro 答案的函数:

        import re
        def is_url(x):
            return bool(re.match(
                r"(https?|ftp)://" # protocol
                r"(\w+(\-\w+)*\.)?" # host (optional)
                r"((\w+(\-\w+)*)\.(\w+))" # domain
                r"(\.\w+)*" # top-level domain (optional, can have > 1)
                r"([\w\-\._\~/]*)*(?<!\.)" # path, params, anchors, etc. (optional)
            , x))
        

        【讨论】:

          猜你喜欢
          • 2019-12-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-05-11
          • 1970-01-01
          • 2012-09-02
          • 1970-01-01
          相关资源
          最近更新 更多