2010-11-30 12 views
6

Necesito identificar y almacenar de manera exclusiva algunas URL. El problema es que a veces vienen con ".." como http://somedomain.com/foo/bar/../../some/url que básicamente es http://somedomain.com/some/url si no estoy equivocado.Python: cómo resolver las URL que contienen '..'

¿Hay alguna función de Python o una forma complicada de resolver estas URL?

+0

URL o ruta del archivo? – delnan

+0

URL. Los puse así porque afterwords los compilo para el nombre de dominio, el esquema, etc. ... –

+2

Ver http://stackoverflow.com/questions/2131290/how-can-normalize-collapse-paths- or-urls-in-python-in-os-independent-way –

Respuesta

10

Hay una solución simple usando urlparse .urljoin:

>>> import urlparse 
>>> urlparse.urljoin('http://www.example.com/foo/bar/../../baz/bux/', '.') 
'http://www.example.com/baz/bux/' 

Sin embargo, si hay sin barra inclinada (el último componente es un archivo, no un directorio), el último componente será rem oved.

Esta revisión utiliza la función urlparse para extraer la ruta, luego usa (la versión de posixpath) os.path para normalizar los componentes. Compense por a mysterious issue with trailing slashes, luego vuelva a unir la URL. El siguiente es capaz doctest:

import urlparse 
import posixpath 

def resolveComponents(url): 
    """ 
    >>> resolveComponents('http://www.example.com/foo/bar/../../baz/bux/') 
    'http://www.example.com/baz/bux/' 
    >>> resolveComponents('http://www.example.com/some/path/../file.ext') 
    'http://www.example.com/some/file.ext' 
    """ 

    parsed = urlparse.urlparse(url) 
    new_path = posixpath.normpath(parsed.path) 
    if parsed.path.endswith('/'): 
     # Compensate for issue1707768 
     new_path += '/' 
    cleaned = parsed._replace(path=new_path) 
    return cleaned.geturl() 
+0

¡Super elegante! Funciona genial. ¡Gracias a un millón! –

+0

Era demasiado bueno para ser cierto :(. Si la ruta es un archivo, después de la unión, permaneceré con la "carpeta" del archivo: domain.com/some/path/file.ext dará como resultado domain.com/some/path /. Encontraré una solución para esto, porque tu solución es bastante impresionante –

+0

@Nicolae ¡Me arrepiento! 'urljoin' parece ser inteligente pero no es ideal para la situación. –

3

Esas son rutas de archivos. Mira os.path.normpath:

>>> import os 
>>> os.path.normpath('/foo/bar/../../some/url') 
'/some/url' 

EDIT:

Si esto es en Windows, la ruta de entrada utiliza las barras invertidas en lugar de barras. En este caso, usted todavía necesita os.path.normpath para deshacerse de los .. patrones (y // y /./ y todo lo que es redundante), a continuación, convertir las barras invertidas por barras:

def fix_path_for_URL(path): 
    result = os.path.normpath(path) 
    if os.sep == '\\': 
     result = result.replace('\\', '/') 
    return result 

EDIT 2:

Si desea normalizar las URL, hágalo (antes de quitarle el método y demás) con el módulo urlparse, como se muestra en the answer to this question.

Datos 3:

Parece que urljoin no normaliza la ruta de la base que se le da:

>>> import urlparse 
>>> urlparse.urljoin('http://somedomain.com/foo/bar/../../some/url', '') 
'http://somedomain.com/foo/bar/../../some/url' 

normpath por sí mismo no acaba de cortar, sea:

>>> import os 
>>> os.path.normpath('http://somedomain.com/foo/bar/../../some/url') 
'http:/somedomain.com/some/url' 

Tenga en cuenta que se comió la doble barra inicial.

Así que tenemos que hacer que se unen sus fuerzas:

def fix_URL(urlstring): 
    parts = list(urlparse.urlparse(urlstring)) 
    parts[2] = os.path.normpath(parts[2].replace('/', os.sep)).replace(os.sep, '/') 
    return urlparse.urlunparse(parts) 

Uso:

>>> fix_URL('http://somedomain.com/foo/bar/../../some/url') 
'http://somedomain.com/some/url' 
+3

Tenga en cuenta que "En Windows, convierte las barras diagonales en barras oblicuas hacia atrás." –

+0

Esto es casi perfecto, pero tal vez no fui lo suficientemente específico en mi pregunta (he editado para reflejar esto más correctamente): esas son URL, acabo de omitir el nombre de dominio y el esquema. –

2

quería comentar sobre la función resolveComponents en la respuesta de la parte superior.

Tenga en cuenta que si su ruta es /, el código agregará otro que puede ser problemático. por lo tanto, he cambiado la condición IF a:

if parsed.path.endswith('/') and parsed.path != '/': 
0

Según RFC 3986 esto debe suceder como parte del proceso de "resolución relativa". Entonces la respuesta podría ser urlparse.urljoin(url, ''). Pero debido a la falla urlparse.urljoin no elimina segmentos de puntos cuando el segundo argumento es url vacía. Puede usar yurl - biblioteca alternativa de manipulación de url. Es lo que hace este derecho:

>>> import yurl 
>>> print yurl.URL('http://somedomain.com/foo/bar/../../some/url') + yurl.URL() 
http://somedomain.com/some/url 
1

urljoin no funcionará, ya que sólo resuelve los segmentos de puntos si el segundo argumento no es absoluta (!?) o vacío. No solo eso, no maneja excesivamente .. s correctamente de acuerdo con RFC 3986 (deben eliminarse; urljoin no lo hace). posixpath.normpath no se puede utilizar ya sea (mucho menos os.path.normpath), ya que resuelve múltiples barras en una fila a sólo una (por ejemplo ///// convierte /), que es un comportamiento incorrecto para URLs.


La siguiente función de corto resuelve cualquier cadena de ruta URL correctamente. No se debe usar con las rutas relativas, sin embargo, dado que se necesitarán tomar decisiones adicionales sobre su comportamiento (¿Hay un error excesivo en ..? s Eliminar . al principio? ¿ambas?): en su lugar, une las URL antes de resolver si sabes que puedes manejar la pat relativa hs. Sin más preámbulos:

def resolve_url_path(path): 
    segments = path.split('/') 
    segments = [segment + '/' for segment in segments[:-1]] + [segments[-1]] 
    resolved = [] 
    for segment in segments: 
     if segment in ('../', '..'): 
      if resolved[1:]: 
       resolved.pop() 
     elif segment not in ('./', '.'): 
      resolved.append(segment) 
    return ''.join(resolved) 

Este maneja posterior segmentos de puntos (es decir, sin una barra diagonal) y barras consecutivas correctamente. Para resolver una URL completa, puede usar la siguiente envoltura (o simplemente en línea la función de resolución de ruta).

try: 
    # Python 3 
    from urllib.parse import urlsplit, urlunsplit 
except ImportError: 
    # Python 2 
    from urlparse import urlsplit, urlunsplit 

def resolve_url(url): 
    parts = list(urlsplit(url)) 
    parts[2] = resolve_url_path(parts[2]) 
    return urlunsplit(parts) 

entonces le puede llamar así:

>>> resolve_url('http://example.com/../thing///wrong/../multiple-slashes-yeah/.') 
'http://example.com/thing///multiple-slashes-yeah/' 

resolución URL correcta tiene más de unos pocos escollos, resulta!

+0

"no maneja excesivo '..'s correctamente" - para mí en Python 3.5 (pero no 2.7) –

+0

... ni en 3.4 :( –

0
import urlparse 
import posixpath 

parsed = list(urlparse.urlparse(url)) 
parsed[2] = posixpath.normpath(posixpath.join(parsed[2], rel_path)) 
proper_url = urlparse.urlunparse(parsed) 
Cuestiones relacionadas