Python - como validar um url em python? (Malformado ou não)

116

Recebi urldo usuário e devo responder com o HTML obtido.

Como posso verificar se o URL está malformado ou não?

Por exemplo :

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

Como podemos conseguir isso?

Yugal Jindle
fonte
1
Apenas tente lê-lo, se por exemplo httplib lançar uma exceção, você saberá que é inválido. Nem todos os urls bem formados são válidos !
carlpett
1
isso irá ajudá-lo: stackoverflow.com/questions/827557/…
DhruvPathak
10
url='http://google' não está malformado. Esquema + nome do host é sempre válido.
Viktor Joras

Respostas:

90

django url validação regex ( fonte ):

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
Cetver
fonte
uma curiosidade ... você adicionou o ftp? Ou tenho uma versão antiga do django?
Ruggero Turra
2
@ yugal-jindle sitedomain não é um url válido. museu é porque .museum é um domínio de nível superior (ICANN [1] os define), e não um sitedomain. [1] icann.org
glarrain de
1
Este parece não funcionar com URLs de estilo nome de usuário: [email protected]
Adam Baxter
2
Isso não funcionará para urls IPv6, que têm o formatohttp://[2001:0DB8::3]:8080/index.php?valid=true#result
cimnine
124

Na verdade, acho que essa é a melhor maneira.

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

Se você definir verify_existscomo True, ele realmente verificará se a URL existe;

edit: ah sim, esta questão é uma duplicata desta: Como posso verificar se existe uma URL com os validadores do Django?

Drekembe
fonte
46
Mas isso só funcionará no ambiente django, não de outra forma.
Yugal Jindle
19
verify_existsestá obsoleto. -1
g33kz0r
2
Adicione: from django.conf import settings settings.configure (DEBUG = False) e remova o verify_exists para mantê-lo funcionando com o django 1.5
Dukeatcoding
1
@YugalJindle Correto, mas retirá-lo do Django é quase trivial: D. Então, eu uso este método
swdev
7
Observe, com django> = 1.5 não há verify_existsmais. Além disso, em vez da valvariável, você pode chamá-la comoURLValidator()('http://www.google.com')
luckydonald
122

Use o pacote de validadores :

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

Instale-o a partir do PyPI com pip ( pip install validators).

Jabba
fonte
5
Ele lançará erros para urls de arquivo. Como "file: ///users/file.txt"
Devavrata
2
Falha para urls localhost validators.url("http://localhost:8080") ValidationFailure(func=url, args={'public': False, 'value': 'http://localhost:8080'})
Tom
5
@Lal Zada, antes de reivindicar algo assim, coloque algum esforço e verifique o código, regexp é muito bom, na verdade: validators.readthedocs.io/en/latest/_modules/validators/…
Drachenfels
1
O fn de validação do pacote tem muitas limitações arbitrárias, portanto, é um conselho terrível sugeri-lo como uma solução geral.
ivan_pozdeev
2
@ivan_pozdeev: se for terrível, sugira uma solução melhor
Jabba
62

Uma versão verdadeira ou falsa, com base na resposta @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'

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

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

Dá:

True
False
False
False
alemol
fonte
8
Eu não sabia que você poderia testar uma instrução if com uma lista de elementos não-None. Isso é útil. Também +1 para usar um módulo integrado
Marc Maxmeister
9
Isso permite tudo. Ele retorna Truepara a string fakeou mesmo para uma string em branco. Nunca haverá erros porque esses atributos estão sempre lá, e a lista sempre terá um valor booleano True porque contém esses atributos. Mesmo se todos os atributos forem Nenhum, a lista ainda não estará vazia. Você precisa de alguma validação dos atributos porque tudo passa da maneira que está agora.
zondo
3
Listas de objetos falsos avaliadas como Verdadeiras: print("I am true") if [False, None, 0, '', [], {}] else print("I am false.")imprime "Eu sou verdadeiro." quando eu o executo. [result.scheme, result.netloc, result.path]sempre avalia como True. print("I am True") if [] else print("I am False.")imprime "Eu sou falso." então listas vazias são falsas. O conteúdo da matriz precisa de avaliação com algo parecido com a allfunção.
dmmfll
3
Não sei por que você exigiria um caminho como esse. Você deve remover result.pathdo teste.
Jerinaw
1
Isso é bom o suficiente para mim, obrigado. Acabei de adicionar uma validação simples para scheme: if not all([result.scheme in ["file", "http", "https"], result.netloc, result.path]):
Alexander Fortin
20

Hoje utilizo o seguinte, com base na resposta do Padam:

$ python --version
Python 3.6.5

E é assim que parece:

from urllib.parse import urlparse

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

Basta usar is_url("http://www.asdf.com").

Espero que ajude!

jonaprieto
fonte
Ele falha caso o nome de domínio comece com um travessão, o que não é válido. tools.ietf.org/html/rfc952
Björn Lindqvist
1
Isso só é bom para dividir componentes no caso especial em que o URI NÃO esteja malformado. Como respondi anteriormente à outra resposta semelhante, isso valida URIs malformados, como https://https://https://www.foo.bar.
ingyhere
9

observação - o lepl não é mais compatível, desculpe (você pode usá-lo e acho que o código abaixo funciona, mas não receberá atualizações).

rfc 3696 http://www.faqs.org/rfcs/rfc3696.html define como fazer isso (para urls http e e-mail). Implementei suas recomendações em python usando lepl (uma biblioteca de analisador). consulte http://acooke.org/lepl/rfc3696.html

usar:

> easy_install lepl
...
> python
...
>>> from lepl.apps.rfc3696 import HttpUrl
>>> validator = HttpUrl()
>>> validator('google')
False
>>> validator('http://google')
False
>>> validator('http://google.com')
True
Andrew Cooke
fonte
2
Legal, mas e quanto ao FTP ou HTTPS?
Adam Parkin
6
você não bifurcou o código e os implementou? é código aberto.
Andrew Cooke
1
lepl foi descontinuado pelo autor acooke.org/lepl/discontinued.html EDIT: heh, acabei de perceber que você é o autor
Emmett Butler
1
observação: lepl.apps.rfc3696 não está funcionando em Python 3.7.4
Sheile
9

Aterrissei nesta página tentando descobrir uma maneira sensata de validar strings como urls "válidas". Eu compartilho aqui minha solução usando python3. Nenhuma biblioteca extra necessária.

Consulte https://docs.python.org/2/library/urlparse.html se estiver usando python2.

Consulte https://docs.python.org/3.0/library/urllib.parse.html se você estiver usando python3 como eu.

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 = '', fragmento = '')

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

A string 'dkakasdkjdjakdjadjfalskdjfalk' não tem esquema ou netloc.

' https://stackoverflow.com ' é provavelmente um url válido.

Aqui está uma função mais concisa:

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])
dmmfll
fonte
4

EDITAR

Conforme apontado por @Kwame, o código abaixo valida o url mesmo se o .comou .coetc não estiverem presentes.

também apontado por @Blaise, URLs como https://www.google é um URL válido e você precisa fazer uma verificação de DNS para verificar se resolve ou não, separadamente.

Isso é simples e funciona:

Portanto, min_attrcontém o conjunto básico de strings que precisam estar presentes para definir a validade de uma URL, ou seja, http://parte e google.comparte.

urlparse.schemelojas http://e

urlparse.netloc armazene o nome de domínio 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()retorna verdadeiro se todas as variáveis ​​dentro dele retornarem verdadeiras. Portanto, se result.schemee result.netlocestiver presente, ou seja, tiver algum valor, a URL é válida e, portanto, retorna True.

Padam Sethia
fonte
Oh, boa pegada .. Acho que tenho que pegar meu código de volta. O que você prefere, existem outras opções, exceto regex.
Padam Sethia
https://www.googleé um URL válido. Pode não resolver realmente, mas se você se preocupa com isso, você precisa fazer uma verificação de DNS.
Blaise,
engole exceções
ivan_pozdeev
2

Validar URL com urllibum regex semelhante ao Django

O regex de validação de URL do Django era realmente muito bom, mas eu precisava ajustá-lo um pouco para o meu caso de uso. Sinta-se à vontade para adaptá-lo ao seu!

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

Explicação

  • O código valida apenas schemee netlocparte de um determinado URL. (Para fazer isso corretamente, eu divido o URL urllib.parse.urlparse()nas duas partes de acordo com os termos regex correspondentes.)
  • A netlocparte para antes da primeira ocorrência de uma barra /, então os portnúmeros ainda fazem parte do netloc, por exemplo:

    https://www.google.com:80/search?q=python
    ^^^^^   ^^^^^^^^^^^^^^^^^
      |             |      
      |             +-- netloc (aka "domain" in my code)
      +-- scheme
  • Endereços IPv4 também são validados

Suporte IPv6

Se você quiser que o validador de URL também funcione com endereços IPv6, faça o seguinte:

  • Adicione da respostais_valid_ipv6(ip) de Markus Jarderot , que tem uma regex de validador IPv6 muito boa
  • Adicione and not is_valid_ipv6(domain)ao últimoif

Exemplos

Aqui estão alguns exemplos de regex para a parte netloc(aka domain) em ação:

Winklerrr
fonte
2

Todas as soluções acima reconhecem uma string como " http://www.google.com/path,www.yahoo.com/path " como válida. Esta solução sempre funciona como deveria

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)
Сергей Дорофий
fonte