Python - как проверить URL-адрес в Python? (Неправильный или нет)

117

Я получил urlот пользователя, и я должен ответить полученным HTML.

Как я могу проверить, не имеет ли URL неправильный формат?

Например :

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

Как мы можем этого добиться?

Югал Джиндл
источник
1
Просто попробуйте прочитать его, если, например, httplib выдает исключение, вы узнаете, что оно недействительно. Не все правильно оформленные URL-адреса действительны !
Carlpett
1
это поможет вам: stackoverflow.com/questions/827557/…
DhruvPathak
10
url='http://google' не деформирован. Схема + имя хоста всегда действительна.
Виктор Жорас

Ответы:

90

Регулярное выражение проверки URL-адреса django ( источник ):

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
источник
любопытство ... вы добавили ftp? Или у меня старая версия django?
Руджеро Турра
2
@ yugal-jindle sitedomain не является допустимым URL. Museum - это потому, что .museum является доменом верхнего уровня (их определяет ICANN [1]), а не сайтом. [1] icann.org
glarrain
1
Этот, похоже, не работает с URL-адресами в стиле username: password@example.com
Адам Бакстер,
2
Это не будет работать для URL-адресов IPv6, которые имеют формуhttp://[2001:0DB8::3]:8080/index.php?valid=true#result
cimnine
124

На самом деле, я считаю, что это лучший способ.

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-адрес, в противном случае он просто проверит, правильно ли он сформирован.

edit: ах да, этот вопрос является дубликатом этого: как я могу проверить, существует ли URL-адрес с помощью валидаторов Django?

Дрекембе
источник
46
Но это будет работать только в среде django, иначе.
Югал Джиндл
19
verify_existsустарела. -1
g33kz0r 02
2
Добавить: из django.conf импортировать настройки settings.configure (DEBUG = False) и удалить verify_exists, чтобы он работал с django 1.5
Dukeatcoding
1
@YugalJindle Правильно, но удалить его из Django почти тривиально: D. Итак, я использую этот метод
swdev
7
Обратите внимание, что с django> = 1.5 больше verify_existsне существует. Также вместо valпеременной вы можете называть это такURLValidator()('http://www.google.com')
luckydonald
122

Используйте пакет валидаторов :

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

Установите его из PyPI с помощью pip ( pip install validators).

Джабба
источник
5
Это вызовет ошибку для URL-адресов файлов. Like "file: ///users/file.txt"
Деваврата
2
Ошибки для URL-адресов localhost validators.url("http://localhost:8080") ValidationFailure(func=url, args={'public': False, 'value': 'http://localhost:8080'})
Том
5
@Lal Zada, прежде чем заявить что-то вроде этого, приложите немного усилий и проверьте код, на самом деле регулярное выражение неплохое: validators.readthedocs.io/en/latest/_modules/validators/…
Драхенфельс
1
Пакетная проверка fn имеет множество произвольных ограничений, поэтому предлагать ее в качестве общего решения - ужасный совет.
ivan_pozdeev
2
@ivan_pozdeev: если это ужасно, то предложите лучшее решение
Джабба
62

Версия 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'

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

дает:

True
False
False
False
alemol
источник
8
Я не знал, что вы можете протестировать оператор if со списком элементов, отличных от None. Это полезно. Также +1 за использование встроенного модуля
Марк Максмайстер
9
Это позволяет все. Он возвращается Trueдля строки fakeили даже для пустой строки. Ошибок никогда не будет, потому что эти атрибуты есть всегда, и список всегда будет иметь логическое значение True, потому что он содержит эти атрибуты. Даже если все атрибуты равны None, список все равно будет непустым. Вам нужна некоторая проверка атрибутов, потому что все проходит так, как у вас сейчас.
zondo
3
Списки ложных объектов оцениваются как True: print("I am true") if [False, None, 0, '', [], {}] else print("I am false.")печатает «Я прав». когда я его запускаю. [result.scheme, result.netloc, result.path]всегда оценивает True. print("I am True") if [] else print("I am False.")печатает «Я неправ». поэтому пустые списки ложны. Содержимое массива требует оценки с помощью чего-то вроде allфункции.
dmmfll
3
Не уверен, зачем вам нужен такой путь. Вы должны снять result.pathс теста.
Jerinaw
1
Мне этого достаточно, спасибо. Я только что добавил простую проверку scheme: if not all([result.scheme in ["file", "http", "https"], result.netloc, result.path]):
Александр Фортин,
20

В настоящее время я использую следующее, основываясь на ответе Падама:

$ 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") .

Надеюсь, поможет!

jonaprieto
источник
Не работает, если доменное имя начинается с дефиса, что недопустимо. tools.ietf.org/html/rfc952
Бьорн Линдквист,
1
Это полезно только для разделения компонентов в особом случае, когда известно, что URI НЕ имеет неправильного формата. Как я ранее отвечал на другой аналогичный ответ, это проверяет неверные URI, например https://https://https://www.foo.bar.
ingyhere
9

нота - lepl больше не поддерживается, извините (вы можете его использовать, и я думаю, что приведенный ниже код работает, но он не будет получать обновления).

rfc 3696 http://www.faqs.org/rfcs/rfc3696.html определяет, как это сделать (для URL-адресов http и электронной почты). Я реализовал его рекомендации на Python с помощью lepl (библиотека парсера). см. 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?
Адам Паркин
6
вы не разветвляли код и не реализовали их? это открытый исходный код.
Эндрю Кук 02
1
lepl теперь прекращен автором acooke.org/lepl/discontinued.html РЕДАКТИРОВАТЬ: хех, только что понял, что вы являетесь автором
Эммет Батлер
1
примечание: lepl.apps.rfc3696 не работает в Python 3.7.4
Sheile
9

Я попал на эту страницу, пытаясь найти разумный способ проверки строк как «действительных» URL-адресов. Я делюсь здесь своим решением с использованием python3. Никаких дополнительных библиотек не требуется.

См. Https://docs.python.org/2/library/urlparse.html если вы используете python2.

См. Https://docs.python.org/3.0/library/urllib.parse.html, если вы используете python3, как и я.

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 (схема = 'https', netloc = 'stackoverflow.com', путь = '', params = '', запрос = '', фрагмент = '')

Строка 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])
dmmfll
источник
4

РЕДАКТИРОВАТЬ

Как указано @Kwame, приведенный ниже код действительно проверяет URL-адрес, даже если .comили и .coт. Д. Отсутствуют .

также указано @Blaise, URL-адреса, такие как https://www.google, являются допустимыми URL- адресами, и вам необходимо выполнить проверку DNS, чтобы проверить, разрешается он или нет, отдельно.

Это просто и работает:

So 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()возвращает истину, если все переменные внутри нее возвращают истину. Таким образом, если result.schemeи result.netlocприсутствует, т.е. имеет какое-то значение, тогда URL-адрес действителен и, следовательно, возвращается True.

Падам Сетия
источник
О, хороший улов ... Думаю, мне нужно забрать свой код. Что вы предпочитаете, есть ли другие варианты, кроме регулярного выражения.
Padam Sethia
https://www.google- действительный URL. На самом деле он может не разрешиться, но если вас это волнует, вам необходимо выполнить проверку DNS.
Blaise
ласточки исключения
ivan_pozdeev
2

Подтвердить URL-адрес с помощью urllibрегулярного выражения, подобного Django

Регулярное выражение проверки URL-адреса Django было на самом деле довольно хорошим, но мне нужно было немного его настроить для моего варианта использования. Не стесняйтесь адаптировать его к своему!

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

объяснение

  • Код проверяет только schemeи netlocчасть данного URL. (Чтобы сделать это правильно, я разделил URL-адрес urllib.parse.urlparse()на две соответствующие части, которые затем сопоставляются с соответствующими условиями регулярного выражения.)
  • netlocЧасть останавливается до первого появления косых черт /, так что portцифры все еще частью netloc, например:

    https://www.google.com:80/search?q=python
    ^^^^^   ^^^^^^^^^^^^^^^^^
      |             |      
      |             +-- netloc (aka "domain" in my code)
      +-- scheme
  • Адреса IPv4 также проверяются

Поддержка IPv6

Если вы хотите, чтобы валидатор URL также работал с адресами IPv6, сделайте следующее:

  • Добавить is_valid_ipv6(ip)из ответа Маркуса Джардерота , в котором есть действительно хорошее регулярное выражение валидатора IPv6
  • Добавить and not is_valid_ipv6(domain)к последнемуif

Примеры

Вот несколько примеров регулярного выражения для части netloc(aka domain) в действии:

Winklerrr
источник
2

Все вышеперечисленные решения распознают строку типа " 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 3986 : a pathсостоит из segments, которые построены из pchars, которая может быть sub-delimsодной из них ",".
Андерс Касеорг
Да, символ "," входит в список допустимых разделителей, но строчка из моего примера даже в страшном сне не может быть действующим url =)
Сергей Дорофий