如何规范化 Java 中的 URL?

Posted

技术标签:

【中文标题】如何规范化 Java 中的 URL?【英文标题】:How to normalize a URL in Java? 【发布时间】: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。例如,如果标题相同,我会忽略子域。

【问题讨论】:

【参考方案1】:

你看过 URI 类吗?

http://docs.oracle.com/javase/7/docs/api/java/net/URI.html#normalize()

【讨论】:

好一个!然而,它对我来说还远远不够。我做的第一件事是推销以下参数: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。我认为你没有理解这个问题。

以上是关于如何规范化 Java 中的 URL?的主要内容,如果未能解决你的问题,请参考以下文章

规范化字符串以在 Java 中创建安全的 URL

如何规范化 Java 中的 EOL 字符?

规范化/规范化 URL?

规范化/规范化 URL?

Spring “请求被拒绝,因为 URL 未规范化。”如何判断使用了啥网址?

打开图表 - 已发布操作中的已获取和规范 URL