如何规范化 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&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&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?的主要内容,如果未能解决你的问题,请参考以下文章