Python: ¿cómo validar una URL en Python? (Malformado o no)

116

Lo tengo urldel usuario y tengo que responder con el HTML obtenido.

¿Cómo puedo comprobar si la URL está mal formada o no?

Por ejemplo :

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

¿Cómo podemos lograrlo?

Yugal Jindle
fuente
1
Intente leerlo, si por ejemplo httplib lanza una excepción, entonces sabrá que no era válido. ¡No todas las URL bien formadas son válidas !
carlpett
1
esto te ayudará: stackoverflow.com/questions/827557/…
DhruvPathak
10
url='http://google' no está mal formado. Schema + nombre de host siempre es válido.
Viktor Joras

Respuestas:

90

regex de validación de URL de django ( fuente ):

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
fuente
una curiosidad ... le agregaste el ftp? ¿O tengo una versión antigua de Django?
Ruggero Turra
2
@ yugal-jindle sitedomain no es una URL válida. museo se debe a que .museum es un dominio de nivel superior (ICANN [1] los define), y no un dominio de sitio. [1] icann.org
glarrain
1
Este no parece funcionar con el nombre de usuario: contraseñ[email protected] URL de estilo
Adam Baxter
2
Esto no funcionará para las URL de IPv6, que tienen el formulariohttp://[2001:0DB8::3]:8080/index.php?valid=true#result
cimnine
124

De hecho, creo que esta es la mejor forma.

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

Si se establece verify_existsque True, en realidad va a comprobar que existe la URL, de lo contrario sólo comprobar si está correctamente formado.

editar: ah sí, esta pregunta es un duplicado de esto: ¿Cómo puedo verificar si existe una URL con los validadores de Django?

Drekembe
fuente
46
Pero esto solo funcionará en el entorno django, no de otra manera.
Yugal Jindle
19
verify_existses obsoleto. -1
g33kz0r
2
Agregue: desde django.conf import settings settings.configure (DEBUG = False) y elimine verify_exists para que siga funcionando con django 1.5
Dukeatcoding
1
@YugalJindle Correcto, pero quitarlo de Django es casi trivial: D. Entonces, uso este método
swdev
7
Tenga en cuenta que con django> = 1.5 ya no hay verify_exists. Además, en lugar de la valvariable, puede llamarlo comoURLValidator()('http://www.google.com')
luckydonald
122

Utilice el paquete 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
>>>

Instálelo desde PyPI con pip ( pip install validators).

Jabba
fuente
5
Lanzará un error para las URL de los archivos. Me gusta "file: ///users/file.txt"
Devavrata
2
Error para las URL de localhost validators.url("http://localhost:8080") ValidationFailure(func=url, args={'public': False, 'value': 'http://localhost:8080'})
Tom
5
@Lal Zada, antes de reclamar algo como esto, esfuércese y verifique el código, la expresión regular es bastante buena en realidad: validators.readthedocs.io/en/latest/_modules/validators/…
Drachenfels
1
La validación fn del paquete tiene muchas limitaciones arbitrarias, por lo que es un consejo terrible sugerirlo como una solución general.
ivan_pozdeev
2
@ivan_pozdeev: si es terrible, sugiera una solución mejor
Jabba
62

Una versión verdadera o falsa, basada en la respuesta de @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))

Da:

True
False
False
False
alemol
fuente
8
No sabía que podía probar una declaración if con una lista de elementos que no sean Ninguno. Eso es útil. También +1 por usar un módulo incorporado
Marc Maxmeister
9
Esto lo permite todo. Vuelve Truepara la cadena fakeo incluso para una cadena en blanco. Nunca habrá errores porque esos atributos siempre están ahí, y la lista siempre tendrá un valor booleano de Verdadero porque contiene esos atributos. Incluso si todos los atributos son Ninguno, la lista seguirá sin estar vacía. Necesita cierta validación de los atributos porque todo pasa como lo tiene ahora.
zondo
3
Las listas de objetos falsos se evalúan como Verdadero: print("I am true") if [False, None, 0, '', [], {}] else print("I am false.")imprime "Soy verdadero". cuando lo ejecuto. [result.scheme, result.netloc, result.path]siempre evalúa a True. print("I am True") if [] else print("I am False.")imprime "Soy falso". por lo que las listas vacías son falsas. El contenido de la matriz necesita evaluación con algo como la allfunción.
dmmfll
3
No estoy seguro de por qué necesitaría un camino como ese. Debes eliminar result.pathde la prueba.
Jerinaw
1
Esto es suficiente para mí, gracias. Acabo de agregar una validación simple para scheme: if not all([result.scheme in ["file", "http", "https"], result.netloc, result.path]):
Alexander Fortin
20

Hoy en día, uso lo siguiente, basado en la respuesta de Padam:

$ python --version
Python 3.6.5

Y así es como se ve:

from urllib.parse import urlparse

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

Solo usa is_url("http://www.asdf.com").

¡Espero eso ayude!

jonaprieto
fuente
Falla en caso de que el nombre de dominio comience con un guión, lo cual no es válido. tools.ietf.org/html/rfc952
Björn Lindqvist
1
Esto solo es bueno para dividir componentes en el caso especial de que se sepa que el URI NO está mal formado. Como respondí anteriormente a la otra respuesta similar, esto valida los URI con formato incorrecto, como https://https://https://www.foo.bar.
ingyhere
9

nota : lepl ya no es compatible, lo siento (puede usarlo y creo que el código siguiente funciona, pero no recibirá actualizaciones).

rfc 3696 http://www.faqs.org/rfcs/rfc3696.html define cómo hacer esto (para http URL y correo electrónico). Implementé sus recomendaciones en Python usando lepl (una biblioteca de analizador). ver 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
fuente
2
Genial, pero ¿qué pasa con FTP o HTTPS?
Adam Parkin
6
¿No ha bifurcado el código y lo ha implementado? es de código abierto.
Andrew Cooke
1
lepl ahora está descontinuado por el autor acooke.org/lepl/discontinued.html EDITAR: je, me acabo de dar cuenta de que usted es el autor
Emmett Butler
1
nota: lepl.apps.rfc3696 no funciona en Python 3.7.4
Sheile
9

Aterricé en esta página tratando de encontrar una forma sensata de validar cadenas como URL "válidas". Comparto aquí mi solución usando python3. No se requieren bibliotecas adicionales.

Consulte https://docs.python.org/2/library/urlparse.html si está utilizando python2.

Consulte https://docs.python.org/3.0/library/urllib.parse.html si está usando python3 como yo.

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 (esquema = '', netloc = '', ruta = 'dkakasdkjdjakdjadjfalskdjfalk', params = '', consulta = '', fragmento = '')

ParseResult (esquema = 'https', netloc = 'stackoverflow.com', ruta = '', params = '', consulta = '', fragmento = '')

La cadena 'dkakasdkjdjakdjadjfalskdjfalk' no tiene esquema ni netloc.

' https://stackoverflow.com ' es probablemente una URL válida.

Aquí hay una función más 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
fuente
4

EDITAR

Como señaló @Kwame, el siguiente código valida la URL incluso si no están presentes .como .coetc.

También señaló @Blaise, URL como https://www.google es una URL válida y debe hacer una verificación de DNS para verificar si se resuelve o no, por separado.

Esto es simple y funciona:

Por tanto, min_attrcontiene el conjunto básico de cadenas que debe estar presente para definir la validez de una URL, es decir, http://parte y google.comparte.

urlparse.schemetiendas http://y

urlparse.netloc almacenar el nombre de dominio 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()devuelve verdadero si todas las variables dentro de él devuelven verdadero. Entonces, si result.schemey result.netlocestá presente, es decir, tiene algún valor, entonces la URL es válida y, por lo tanto, regresa True.

Padam Sethia
fuente
Oh, buena captura ... Supongo que tengo que recuperar mi código. ¿Qué prefieres? ¿Hay otras opciones excepto regex?
Padam Sethia
https://www.googlees una URL válida. Es posible que en realidad no se resuelva, pero si eso le importa, debe hacer una verificación de DNS.
Blaise
traga excepciones
ivan_pozdeev
2

Validar URL con urllibuna expresión regular similar a Django

La expresión regular de validación de URL de Django era bastante buena, pero necesitaba modificarla un poco para mi caso de uso. ¡No dudes en adaptarlo al tuyo!

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

Explicación

  • El código solo valida la parte schemey la netlocparte de una URL determinada. (Para hacer esto correctamente, divido la URL urllib.parse.urlparse()en las dos partes correspondientes que luego se combinan con los términos de expresiones regulares correspondientes).
  • La netlocparte se detiene antes de la primera aparición de una barra /, por lo que los portnúmeros siguen siendo parte de netloc, por ejemplo:

    https://www.google.com:80/search?q=python
    ^^^^^   ^^^^^^^^^^^^^^^^^
      |             |      
      |             +-- netloc (aka "domain" in my code)
      +-- scheme
  • Las direcciones IPv4 también se validan

Soporte IPv6

Si desea que el validador de URL también funcione con direcciones IPv6, haga lo siguiente:

  • Agregue is_valid_ipv6(ip)de la respuesta de Markus Jarderot , que tiene una expresión regular de validación de IPv6 realmente buena
  • Agregar and not is_valid_ipv6(domain)al últimoif

Ejemplos

Aquí hay algunos ejemplos de la expresión regular para la parte netloc(también conocida como domain) en acción:

winklerrr
fuente
1

Todas las soluciones anteriores reconocen una cadena como " http://www.google.com/path,www.yahoo.com/path " como válida. Esta solución siempre funciona como debería

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