2010-06-07 14 views
26

URL normalization (lub URL kanonicznej) jest to proces, w którym adresy są zmodyfikowane, a znormalizowane w spójny sposób. Celem procesu normalizacji jest przekształcenie adresu URL w znormalizowany lub kanoniczny adres URL, aby możliwe było sprawdzenie, czy dwa syntaktycznie różne adresy URL są równoważne.Jak normalizować URL w Javie?

Strategie obejmują dodawanie ukośnych ukośników, https => http, itd. Strona Wikipedia zawiera wiele list.

Masz ulubioną metodę robienia tego w Javie? Być może biblioteka (Nutch?), Ale jestem otwarty. Mniejsza i mniejsza liczba zależności jest lepsza.

Na razie coś podam i sprawdzę to pytanie.

EDYCJA: Chcę agresywnie znormalizować, aby liczyć adresy URL tak samo, jeśli odnoszą się do tej samej treści. Na przykład ignoruję parametry utm_source, utm_medium, utm_campaign. Na przykład ignoruję poddomeny, jeśli tytuł jest taki sam.

Odpowiedz

20
+3

Dobra! Jednak dla mnie to nie wystarczy. Pierwszą rzeczą, którą pomogłem było podanie następujących parametrów: utm_source, utm_medium, utm_campaign.Znajdują się one na wielu adresach URL w dziale, ale ich usunięcie pozostawia te adresy semantycznie takie same dla celów analizy, do których treści się odnoszą. – dfrankow

+1

@dfrankow To niekoniecznie musi być prawda. Nie ma nic, co mogłoby powstrzymać witrynę przed wyświetlaniem różnych treści na podstawie tych parametrów. –

+0

Oczywiście, ale praktycznie rzecz biorąc, są one używane przez niektóre pakiety marketingowe (Google Analytics?) Do śledzenia kampanii, więc prawdopodobnie nie będą się różnić. – dfrankow

18

Znalazłem to pytanie ubiegłej nocy, ale nie było odpowiedzi, której szukałem, więc zrobiłem własną. Tutaj jest okrywać ktoś w przyszłości chce go:

/** 
* - Covert the scheme and host to lowercase (done by java.net.URL) 
* - Normalize the path (done by java.net.URI) 
* - Add the port number. 
* - Remove the fragment (the part after the #). 
* - Remove trailing slash. 
* - Sort the query string params. 
* - Remove some query string params like "utm_*" and "*session*". 
*/ 
public class NormalizeURL 
{ 
    public static String normalize(final String taintedURL) throws MalformedURLException 
    { 
     final URL url; 
     try 
     { 
      url = new URI(taintedURL).normalize().toURL(); 
     } 
     catch (URISyntaxException e) { 
      throw new MalformedURLException(e.getMessage()); 
     } 

     final String path = url.getPath().replace("/$", ""); 
     final SortedMap<String, String> params = createParameterMap(url.getQuery()); 
     final int port = url.getPort(); 
     final String queryString; 

     if (params != null) 
     { 
      // Some params are only relevant for user tracking, so remove the most commons ones. 
      for (Iterator<String> i = params.keySet().iterator(); i.hasNext();) 
      { 
       final String key = i.next(); 
       if (key.startsWith("utm_") || key.contains("session")) 
       { 
        i.remove(); 
       } 
      } 
      queryString = "?" + canonicalize(params); 
     } 
     else 
     { 
      queryString = ""; 
     } 

     return url.getProtocol() + "://" + url.getHost() 
      + (port != -1 && port != 80 ? ":" + port : "") 
      + path + queryString; 
    } 

    /** 
    * Takes a query string, separates the constituent name-value pairs, and 
    * stores them in a SortedMap ordered by lexicographical order. 
    * @return Null if there is no query string. 
    */ 
    private static SortedMap<String, String> createParameterMap(final String queryString) 
    { 
     if (queryString == null || queryString.isEmpty()) 
     { 
      return null; 
     } 

     final String[] pairs = queryString.split("&"); 
     final Map<String, String> params = new HashMap<String, String>(pairs.length); 

     for (final String pair : pairs) 
     { 
      if (pair.length() < 1) 
      { 
       continue; 
      } 

      String[] tokens = pair.split("=", 2); 
      for (int j = 0; j < tokens.length; j++) 
      { 
       try 
       { 
        tokens[j] = URLDecoder.decode(tokens[j], "UTF-8"); 
       } 
       catch (UnsupportedEncodingException ex) 
       { 
        ex.printStackTrace(); 
       } 
      } 
      switch (tokens.length) 
      { 
       case 1: 
       { 
        if (pair.charAt(0) == '=') 
        { 
         params.put("", tokens[0]); 
        } 
        else 
        { 
         params.put(tokens[0], ""); 
        } 
        break; 
       } 
       case 2: 
       { 
        params.put(tokens[0], tokens[1]); 
        break; 
       } 
      } 
     } 

     return new TreeMap<String, String>(params); 
    } 

    /** 
    * Canonicalize the query string. 
    * 
    * @param sortedParamMap Parameter name-value pairs in lexicographical order. 
    * @return Canonical form of query string. 
    */ 
    private static String canonicalize(final SortedMap<String, String> sortedParamMap) 
    { 
     if (sortedParamMap == null || sortedParamMap.isEmpty()) 
     { 
      return ""; 
     } 

     final StringBuffer sb = new StringBuffer(350); 
     final Iterator<Map.Entry<String, String>> iter = sortedParamMap.entrySet().iterator(); 

     while (iter.hasNext()) 
     { 
      final Map.Entry<String, String> pair = iter.next(); 
      sb.append(percentEncodeRfc3986(pair.getKey())); 
      sb.append('='); 
      sb.append(percentEncodeRfc3986(pair.getValue())); 
      if (iter.hasNext()) 
      { 
       sb.append('&'); 
      } 
     } 

     return sb.toString(); 
    } 

    /** 
    * Percent-encode values according the RFC 3986. The built-in Java URLEncoder does not encode 
    * according to the RFC, so we make the extra replacements. 
    * 
    * @param string Decoded string. 
    * @return Encoded string per RFC 3986. 
    */ 
    private static String percentEncodeRfc3986(final String string) 
    { 
     try 
     { 
      return URLEncoder.encode(string, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~"); 
     } 
     catch (UnsupportedEncodingException e) 
     { 
      return string; 
     } 
    } 
} 
+0

Dziękuję za to, podoba mi się podejście, ale znalazłem kilka problemów z implementacją: 1) Wyjątek modyfikacji współbieżnej jest wywoływany w pętli usuwającej klucze utm_ i sesyjne (chyba że jest to ostatni wpis), ponieważ jesteś usuwanie z kolekcji podczas iteracji. Powinieneś użyć iteratora i metody remove(). 2) ponowne wychodzenie z parametrów powoduje uszkodzenie niektórych stron internetowych, które wypróbowałem. To dobrze, jeśli używasz tylko wersji kanonicznej do porównywania adresów URL, co właśnie zrobiłem. Wyobrażam sobie, że usunięcie tokena sesji może również spowodować uszkodzenie niektórych stron, więc jest to naprawdę dyskusyjne. –

+1

Nie jest dobrze usuwać końcowy ukośnik z adresu URL. W rzeczywistości tworzy inny adres URL. Na przykład aliasowanie Apache może nie działać, jeśli jest skonfigurowane z końcowym ukośnikiem. – rustyx

2

Nie, nie ma nic w standardowych bibliotekach, aby to zrobić. Kanonizacja obejmuje takie elementy jak dekodowanie niepotrzebnie zakodowanych znaków, przekształcanie nazw hostów na małe litery itp.

np. http://ACME.com/./foo%26bar staje:

http://acme.com/foo&bar

URI normalize() robi nie to zrobić.

1

W Javie normalizować URL ręcznie

String company_website = "http://www.foo.bar.com/whatever&stuff"; 

try { 
    URL url = new URL(company_website); 
    System.out.println(url.getProtocol() + "://" + url.getHost()); 
} catch (MalformedURLException e) { 
    e.printStackTrace(); 
} 

//prints `http://www.foo.bar.com` 

Java klasy URL ma wszelkiego rodzaju metod analizowania wszelkich część adresu URL.

+2

Normalizacja/kanonizacja odnosi się do przekształcenia, które zapewnia, że ​​dane zdefiniowane jako równoważne semantycznie stają się identyczne. Usuwanie podstawowych danych nie jest normalizacją. – AndrewF