Python - Comment valider une url en python? (Mal formé ou pas)

116

J'ai urlde l'utilisateur et je dois répondre avec le HTML récupéré.

Comment puis-je vérifier si l'URL est malformée ou non?

Par exemple :

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

Comment pouvons-nous y parvenir ?

Yugal Jindle
la source
1
Essayez simplement de le lire, si par exemple httplib lève une exception, vous saurez qu'elle n'était pas valide. Toutes les URL bien formées ne sont pas valides !
carlpett
1
cela vous aidera: stackoverflow.com/questions/827557/…
DhruvPathak
10
url='http://google' n'est pas mal formé. Schéma + nom d'hôte est toujours valide.
Viktor Joras
Est-ce que cela répond à votre question? Comment valider une URL avec une expression régulière en Python?
AMC le

Réponses:

90

django url validation regex ( 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
cetver
la source
une curiosité ... avez-vous ajouté le ftp? Ou ai-je une ancienne version de django?
Ruggero Turra
2
@ yugal-jindle sitedomain n'est pas une URL valide. museum est dû au fait que .museum est un domaine de premier niveau (l'ICANN [1] les définit), et non un domaine de site. [1] icann.org
glarrain
1
Celui-ci ne semble pas fonctionner avec le nom d' utilisateur: URL de style [email protected]
Adam Baxter
2
Cela ne fonctionnera pas pour les URL IPv6, qui ont la formehttp://[2001:0DB8::3]:8080/index.php?valid=true#result
cimnine
124

En fait, je pense que c'est la meilleure façon.

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 vous définissez verify_existssur True, il vérifiera réellement que l'URL existe, sinon il vérifiera simplement si elle est correctement formée.

edit: ah ouais, cette question est un double de ceci: Comment puis-je vérifier si une URL existe avec les validateurs de Django?

Drekembe
la source
46
Mais cela ne fonctionnera que dans l'environnement django, pas autrement.
Yugal Jindle
19
verify_existsest obsolète. -1
g33kz0r
2
Ajoutez: à partir de django.conf import settings settings.configure (DEBUG = False) et supprimez le verify_exists pour qu'il continue de fonctionner avec django 1.5
Dukeatcoding
1
@YugalJindle Correct, mais le retirer de Django est presque trivial: D. Donc, j'utilise cette méthode
swdev
7
Remarque, avec django> = 1.5 il n'y en a verify_existsplus. Aussi au lieu de la valvariable, vous pouvez l'appeler commeURLValidator()('http://www.google.com')
luckydonald
122

Utilisez le package des validateurs :

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

Installez-le depuis PyPI avec pip ( pip install validators).

Jabba
la source
5
Cela générera une erreur pour les URL de fichiers. Comme "file: ///users/file.txt"
Devavrata
2
Échec pour les URL de l'hôte local validators.url("http://localhost:8080") ValidationFailure(func=url, args={'public': False, 'value': 'http://localhost:8080'})
Tom
5
@Lal Zada, avant de réclamer quelque chose comme ça, faites quelques efforts et vérifiez le code, l'expression régulière est plutôt bonne en fait: validators.readthedocs.io/en/latest/_modules/validators/…
Drachenfels
1
La validation fn du paquet a de nombreuses limitations arbitraires, c'est donc un terrible conseil de le suggérer comme solution générale.
ivan_pozdeev
2
@ivan_pozdeev: si c'est terrible, alors suggérez une meilleure solution
Jabba
62

Une version True ou False, basée sur la réponse @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))

Donne:

True
False
False
False
alémol
la source
8
Je ne savais pas que vous pouviez tester une instruction if avec une liste d'éléments non-None. Cela est utile. Également +1 pour l'utilisation d'un module intégré
Marc Maxmeister
9
Cela permet tout. Il renvoie Truepour la chaîne fakeou même pour une chaîne vide. Il n'y aura jamais d'erreur car ces attributs sont toujours là et la liste aura toujours une valeur booléenne True car elle contient ces attributs. Même si tous les attributs sont Aucun, la liste sera toujours non vide. Vous avez besoin d'une certaine validation des attributs car tout se passe comme vous l'avez maintenant.
zondo
3
Les listes de faux objets évaluent à Vrai: print("I am true") if [False, None, 0, '', [], {}] else print("I am false.")affiche «Je suis vrai». quand je l'exécute. [result.scheme, result.netloc, result.path]évalue toujours à True. print("I am True") if [] else print("I am False.")imprime "Je suis faux." les listes vides sont donc fausses. Le contenu du tableau doit être évalué avec quelque chose comme la allfonction.
dmmfll
3
Je ne sais pas pourquoi vous auriez besoin d'un chemin comme celui-là. Vous devez retirer result.pathdu test.
Jerinaw
1
Cela me suffit, merci. Je viens d'ajouter une simple validation pour scheme: if not all([result.scheme in ["file", "http", "https"], result.netloc, result.path]):
Alexander Fortin le
20

De nos jours, j'utilise ce qui suit, basé sur la réponse du Padam:

$ python --version
Python 3.6.5

Et voici à quoi ça ressemble:

from urllib.parse import urlparse

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

Utilisez simplement is_url("http://www.asdf.com").

J'espère que ça aide!

Jonaprieto
la source
Il échoue au cas où le nom de domaine commence par un tiret, ce qui n'est pas valide. tools.ietf.org/html/rfc952
Björn Lindqvist
1
Ceci n'est bon que pour fractionner les composants dans le cas particulier où l'URI est connu pour ne PAS être mal formé. Comme j'ai répondu précédemment à l'autre réponse similaire, cela valide les URI malformés, comme https://https://https://www.foo.bar.
ingyhere
9

note - lepl n'est plus pris en charge, désolé (vous pouvez l'utiliser, et je pense que le code ci-dessous fonctionne, mais il ne recevra pas de mises à jour).

rfc 3696 http://www.faqs.org/rfcs/rfc3696.html définit comment procéder (pour les URL http et les e-mails). J'ai implémenté ses recommandations en python en utilisant lepl (une bibliothèque d'analyseurs). voir http://acooke.org/lepl/rfc3696.html

utiliser:

> 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
la source
2
Bien, mais qu'en est-il du FTP ou du HTTPS?
Adam Parkin
6
vous n'avez pas forké le code et ne les avez pas implémentés? c'est open source.
andrew cooke
1
lepl est maintenant interrompu par l'auteur acooke.org/lepl/discontinued.html EDIT: heh, vient de réaliser que vous êtes l'auteur
Emmett Butler
1
note: lepl.apps.rfc3696 ne fonctionne pas en Python 3.7.4
Sheile
9

J'ai atterri sur cette page en essayant de trouver un moyen sensé de valider les chaînes comme URL "valides". Je partage ici ma solution utilisant python3. Aucune bibliothèque supplémentaire requise.

Voir https://docs.python.org/2/library/urlparse.html si vous utilisez python2.

Voir https://docs.python.org/3.0/library/urllib.parse.html si vous utilisez python3 comme je le suis.

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 (schéma = '', netloc = '', chemin = 'dkakasdkjdjakdjadjfalskdjfalk', params = '', query = '', fragment = '')

ParseResult (schéma = 'https', netloc = 'stackoverflow.com', chemin = '', params = '', query = '', fragment = '')

La chaîne 'dkakasdkjdjakdjadjfalskdjfalk' n'a pas de schéma ni de netloc.

« https://stackoverflow.com » est probablement une URL valide.

Voici une fonction plus concise:

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
la source
4

ÉDITER

Comme indiqué par @Kwame, le code ci-dessous valide l'url même si le .comou .coetc ne sont pas présents.

également souligné par @Blaise, les URL comme https://www.google sont des URL valides et vous devez faire une vérification DNS pour vérifier si elles se résolvent ou non, séparément.

Ceci est simple et fonctionne:

min_attrContient donc l'ensemble de base de chaînes qui doit être présent pour définir la validité d'une URL, c'est-à-dire une http://partie et une google.compartie.

urlparse.schememagasins http://et

urlparse.netloc stocker le nom de domaine 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()renvoie true si toutes les variables qu'il contient renvoient true. Donc, si result.schemeet result.netlocest présent, c'est-à-dire a une valeur, alors l'URL est valide et donc retourne True.

Padam Sethia
la source
Oh, belle prise ... Je suppose que je dois reprendre mon code. Que préférez-vous, y a-t-il d'autres options à part regex.
Padam Sethia le
https://www.googleest une URL valide. Cela ne résoudra peut-être pas vraiment, mais si cela vous intéresse, vous devez effectuer une vérification DNS.
Blaise
avale des exceptions
ivan_pozdeev
2

Valider l'URL avec une urllibexpression régulière de type Django

Le regex de validation d'URL de Django était en fait assez bon, mais j'avais besoin de le peaufiner un peu pour mon cas d'utilisation. N'hésitez pas à l'adapter au vôtre!

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

Explication

  • Le code valide uniquement la partie schemeet netlocd'une URL donnée. (Pour ce faire correctement, j'ai divisé l'URL avec urllib.parse.urlparse()les deux parties correspondantes qui sont ensuite mises en correspondance avec les termes regex correspondants.)
  • La netlocpartie s'arrête avant la première occurrence d'une barre oblique /, donc les portnombres font toujours partie de netloc, par exemple:

    https://www.google.com:80/search?q=python
    ^^^^^   ^^^^^^^^^^^^^^^^^
      |             |      
      |             +-- netloc (aka "domain" in my code)
      +-- scheme
  • Les adresses IPv4 sont également validées

Prise en charge d'IPv6

Si vous souhaitez que le validateur d'URL fonctionne également avec les adresses IPv6, procédez comme suit:

  • Ajouter à is_valid_ipv6(ip)partir de la réponse de Markus Jarderot , qui a un très bon validateur IPv6 regex
  • Ajouter and not is_valid_ipv6(domain)au dernierif

Exemples

Voici quelques exemples de regex pour la partie netloc(aka domain) en action:

winklerrr
la source
2

Toutes les solutions ci-dessus reconnaissent une chaîne comme " http://www.google.com/path,www.yahoo.com/path " comme valide. Cette solution fonctionne toujours comme il se doit

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