【问题标题】:How to normalize a URL in Java?如何规范化 Java 中的 URL?
【发布时间】:2011-02-28 22:26:58
【问题描述】:

URL normalization(或 URL 规范化)是以一致的方式修改和标准化 URL 的过程。规范化过程的目标是将 URL 转换为规范化或规范化 URL,以便确定两个语法不同的 URL 是否等效。

策略包括添加斜杠、https => http 等。维基百科页面列出了很多。

在 Java 中有一个最喜欢的方法吗?也许是一个图书馆 (Nutch?),但我是开放的。依赖项越小越少越好。

我现在将手动编写一些代码并密切关注这个问题。

编辑:如果 URL 引用相同的内容,我想积极规范化以将 URL 计为相同。例如,我忽略了参数 utm_source、utm_medium、utm_campaign。例如,如果标题相同,我会忽略子域。

【问题讨论】:

    标签: java url-rewriting


    【解决方案1】:

    【讨论】:

    • 好一个!然而,它对我来说还远远不够。我做的第一件事是推销以下参数:utm_source、utm_medium、utm_campaign。它们存在于大量的 URL 上,但删除它们会使 URL 在语义上保持不变,以便分析它们引用的内容。
    • @dfrankow 这不一定是真的。没有什么可以阻止网站根据这些参数提供不同的内容。
    • 当然,但实际上,它们被某些营销包(谷歌分析?)用于跟踪活动,因此它们不太可能发生变化。
    【解决方案2】:

    您可以通过Restlet 框架使用Reference.normalize() 来执行此操作。您还应该能够使用此类非常方便地删除不需要的元素。

    【讨论】:

      【解决方案3】:

      我昨晚发现了这个问题,但没有我想要的答案,所以我自己做了。这是以防将来有人想要它:

      /**
       * - 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;
              }
          }
      }
      

      【讨论】:

      • 谢谢你,我喜欢这种方法,但我发现实现的一些问题:1)在删除 utm_ 和会话密钥的循环中引发了并发修改异常(除非它是最后一个entry),因为您要在迭代期间从集合中删除。您应该使用迭代器和 remove() 方法。 2)参数的重新转义破坏了我尝试过的一些网站。如果您只是使用规范版本来比较 URL,那很好,这就是我最终所做的。我想删除会话令牌也可能会破坏某些网站,所以它真的没有实际意义。
      • 从 URL 中去掉尾部斜杠是不好的。实际上,它创建了一个不同的 URL。例如,如果使用尾部斜杠设置 Apache 别名,它可能不起作用。
      【解决方案4】:

      因为您还想识别引用相同内容的 URL,我发现 WWW2007 中的这篇论文非常有趣:Do Not Crawl in the DUST: Different URLs with Similar Text。它为您提供了一个很好的理论方法。

      【讨论】:

        【解决方案5】:

        不,标准库中没有任何东西可以做到这一点。规范化包括解码不必要的编码字符、将主机名转换为小写等。

        例如http://ACME.com/./foo%26bar 变为:

        http://acme.com/foo&amp;bar

        URI 的 normalize() 这样做。

        【讨论】:

        • new URI("http://ACME.com/./foo%26bar").normalize() 结果为http://ACME.com/foo%26bar。它不会将主机转换为小写,但会正确处理相等:new URI("http://ACME.com/./foo%26bar").normalize().equals(new URI("http://acme.com/foo%26bar"))
        【解决方案6】:

        强化学习库: https://github.com/backchatio/rl 远远超出了 java.net.URL.normalize()。 它在 Scala 中,但我想它应该可以在 Java 中使用。

        【讨论】:

          【解决方案7】:

          在 Java 中,规范化 URL 的各个部分

          网址示例:https://i0.wp.com:55/lplresearch.com/wp-content/feb.png?ssl=1&amp;myvar=2#myfragment

          protocol:        https 
          domain name:     i0.wp.com 
          subdomain:       i0 
          port:            55 
          path:            /lplresearch.com/wp-content/uploads/2019/01/feb.png?ssl=1 
          query:           ?ssl=1" 
          parameters:      &myvar=2 
          fragment:        #myfragment 
          

          进行 URL 解析的代码:

          import java.util.*; 
          import java.util.regex.*; 
          public class regex { 
              public static String getProtocol(String the_url){ 
                  Pattern p = Pattern.compile("^(http|https|smtp|ftp|file|pop)://.*"); 
                  Matcher m = p.matcher(the_url); 
                  return m.group(1); 
              } 
              public static String getParameters(String the_url){ 
                  Pattern p = Pattern.compile(".*(\\?[-a-zA-Z0-9_.@!$&''()*+,;=]+)(#.*)*$");
                  Matcher m = p.matcher(the_url); 
                  return m.group(1); 
              } 
              public static String getFragment(String the_url){ 
                  Pattern p = Pattern.compile(".*(#.*)$"); 
                  Matcher m = p.matcher(the_url); 
                  return m.group(1); 
              } 
              public static void main(String[] args){ 
                  String the_url = 
                      "https://i0.wp.com:55/lplresearch.com/" + 
                      "wp-content/feb.png?ssl=1&myvar=2#myfragment"; 
                  System.out.println(getProtocol(the_url)); 
                  System.out.println(getFragment(the_url)); 
                  System.out.println(getParameters(the_url)); 
              }   
          } 
          

          打印

          https
          #myfragment
          ?ssl=1&myvar=2
          

          然后,您可以推送和拉取 URL 的各个部分,直到它们符合要求。

          【讨论】:

          • 规范化/规范化是指确保定义为语义等价的数据变得相同的转换。剥离基本数据不是标准化。
          • 是的,但“正常化”的官方规则存在冲突并继续分歧,其中一些是出于数据网络战一般规则下的恶意和敌意。因此,对您而言“正常化”的差异可能是在不同国家/文化/计划下为其他人带来破坏性差异的差异。我们必须敲定分歧,例如:“为什么“ww3.whatever.com”在加拿大和乌克兰与“btap7://ww9.whatever.drone”正常化,但在中国却没有,因为他们的内容审查员不足-海缆?
          【解决方案8】:

          我有一个简单的方法来解决它。这是我的代码

          public static String normalizeURL(String oldLink)
          {
              int pos=oldLink.indexOf("://");
              String newLink="http"+oldLink.substring(pos);
              return newLink;
          }
          

          【讨论】:

          • 这只是在所有情况下将协议更改为 http。我认为你没有理解这个问题。
          猜你喜欢
          • 2012-05-22
          • 2011-03-22
          • 2017-04-26
          • 2010-09-12
          • 1970-01-01
          • 1970-01-01
          • 2011-05-09
          • 2012-05-04
          相关资源
          最近更新 更多