在各类文本处理中,往往要涉及到在文本中提取数据、替换数据的操作,这种情况下,我们一般使用正则表达式系统来简化我们的处理(一般没谁会自己写吧?写了效率也不一定高啊)。同样,在涉及到HTML的一些处理中,个人也非常喜欢使用正则表达式系统来进行HTML元素内容的替换、提取等操作,HTML也是文本嘛。
随着使用的频次增多,对正则表达式系统的了解自然也就不断深入,在后来的一次,突然发现,正则表达式原来可以提取HTML元素(标签)中的所有属性,甚至 可以直接将HTML解析成DOM树。面对突如其来的强大功能,一时间还真有些不知所措,要知道,在之前,解析HTML咱是靠第三方工具的。正则表达式最多也就是提取下特定的字串内容,替换下内容,像对于提取HTML元素中多个属性的方法想都没想能用正则表达式,而是毫不犹豫的将元素提取出来后老老实实的去做字串处理,要不就是使用第三方的插件来完成这无比艰巨的工作。现在发现正则表达式竟然可以如此简单的完成,太惊喜了!
为了展现正则表达式的强大功能,让我们赶紧找个例子来试试吧——用正则表达式提取HTML标签(元素)中所有的属性。
输入内容:任意HTML源码
要求:提取指定元素的所有属性
输出内容:指定HTML元素的所有属性
来看看正则表达式是怎么完成的,代码:
// 注意:本例仅为测试用,投入到生产环境中需要根据需求进行调整
// 测试结果相当的不理想:解析64KB数据,10个匹配项,耗时99s
public static IList<IDictionary<string,string>> ParseHtml(string html)
{
IList<IDictionary<string, string>> tags = new List<IDictionary<string, string>>();
//MitchellChu .NET Blog
// 首先定义下正则表达式
Regex regexParser = new Regex("<img(?:\\s*([\\w\\-]+)(?:\\s*=\\s*(?<qouta>['\"]?)([^'\"]*)\\k<qouta>)?\\s*)*>", RegexOptions.IgnoreCase);
// 这里注意
// 1. 使用qouta的原因是因为引号要配对
// 2. 引号在实际中,也有可能不存在,要加上?
// 3. 只要属性名和属性值,因此其他的我们都忽略
MatchCollection tagsMatched = regexParser.Matches(html);
// 遍历所有的匹配HTML元素
foreach (Match m in tagsMatched)
{
Dictionary<string, string> attrs = new Dictionary<string, string>();
//将匹配到的HTML元素内的属性键值对存入字典中
for (int i = 0; i < m.Groups[1].Captures.Count; i++)
{
attrs.Add(m.Groups[1].Captures[i].Value, m.Groups[2].Captures[i].Value);
}
tags.Add(attrs);
}
return tags;
}
从例子中我们可以看到,我们能够非常简单的将HTML元素中的所有属性都取到。虽然结果能够得到,但在实际使用中如果直接这么放上去那肯定要出问题的,Mitchell本地测试的结果是:64KB的数据,10个匹配项,耗时99s!CPU很弱?No!你没看错,99s的时间,差不多2分钟了。怎么会这样呢?原因在于正则表达式系统对于当前的正则表达式的解读太深入了。具体原因你可以思考下(Tips:正则表达式是个贪婪的家伙),下面放出稍微优化过的正则表达式吧:
//方法也稍微做了修改,将正则表达式作为参数传入方法内部
public static IList<IDictionary<string,string>> ParseHtml(string html, string patern)
{
IList<IDictionary<string, string>> tags = new List<IDictionary<string, string>>();
//MitchellChu .NET Blog
// 首先定义下正则表达式
Regex regexParser = new Regex(patern, RegexOptions.IgnoreCase);
// 这里注意
// 1. 使用qouta的原因是因为引号要配对
// 2. 引号在实际中,也有可能不存在,要加上?
// 3. 只要属性名和属性值,因此其他的我们都忽略
MatchCollection tagsMatched = regexParser.Matches(html);
// 遍历所有的匹配HTML元素
foreach (Match m in tagsMatched)
{
Dictionary<string, string> attrs = new Dictionary<string, string>();
//将匹配到的HTML元素内的属性键值对存入字典中
for (int i = 0; i < m.Groups[1].Captures.Count; i++)
{
attrs.Add(m.Groups[1].Captures[i].Value, m.Groups[2].Captures[i].Value);
}
tags.Add(attrs);
}
return tags;
}
//这里调用方法
var result = ParseHtml(html, "<img(?:\\s+([\\w\\-]+)(?:\\s*=\\s*(?<qouta>['\"]?)([^'\">]*)\\k<qouta>)?)*\\s*>");
// 同样的方法,仅仅是正则表达式的改变
// 同样测试64KB数据,10个匹配项,耗时 11ms
做同样的事情,但是经过稍微优化后的正则表达式在性能上远远超过没有经过优化的正则表达式。由此可见,正则还是把双刃剑,需要好好谨慎的使用,不然会让你的系统慢如蜗牛。