c#实现网页正文抓取

需要记住的,随笔记一下

 

 

1、抓取远程网页源码,这里要实现自动判断网页编码,否则有可能抓到乱码。我是先看应答的 http头的chareset,一般这个很准,但像csdn的新闻比较变态http应答的头里的chareset和网页的meta里声明的 chareset不一致,所以我手工加了一下判断,如果不一致再在内存流里用网页声明的编码读取一遍源码 2、把网页分割成几大块。试用了一下tidy的.net包装及HtmlParse的.net版本,都不太好用。于是我自己写了个算法,可以把网页里的div块,td块等都提取出来,支持嵌套的情况。一般只提取div的文字块儿就行了。 3、把汉字少于200的文本块去了,一般少于200字的文本块不会是正文,即便是正文,一般来说也不会有太多的价值,我直接去掉。 4、 因为div支持嵌套,所以剩下的文本块,有可能是重复的,一个是另一个的父节点,所以要把最里层的文本块找出来,最里层的文本块肯定是汉字最多的,而其它 文本最少的,所以要计算出剩余文本块中汉字占所有字符比例最高的文本块,基本上它就是正文的文本块了。当然有的网页正文里也可能还有div的文本块,这时 候可能会判断错误,但只要正文嵌套的Div文本块的汉字少于200字,我的算法还是能准确提取正文文本块的。这一步我用写了一个自定义的方法传递给 List的Sort方法。 5、把<p><br>等标签替换成特殊占位符[p][br]等,因为最终的正文需要保留段落和回车换行等格式。这一步用正则实现。 6、把最后剩下的文本块的html标签去掉,我用正则过滤的。 7、把[p]替换成回车换行加俩空格,把[br]替换成回车换行,这步也用正则。到此,正文提取完毕

主要代码:

 

[csharp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <span style="font-size:18px;">public class GetMainContentHelper  
  2. {  
  3.     ///<summary>  
  4.     /// 判断两段儿文本里哪个中文占的比例高  
  5.     ///</summary>  
  6.     ///<param name="x"></param>  
  7.     ///<param name="y"></param>  
  8.     ///<returns></returns>  
  9.     public static int CompareDinosByChineseLength(string x, string y)  
  10.     {  
  11.         if (x == null)  
  12.         {  
  13.             if (y == null)  
  14.             {  
  15.                 return 0;  
  16.             }  
  17.             else  
  18.             {  
  19.                 return -1;  
  20.             }  
  21.         }  
  22.         else  
  23.         {  
  24.             if (y == null)  
  25.             {  
  26.                 return 1;  
  27.             }  
  28.             else  
  29.             {  
  30.                 Regex r = new Regex("[\u4e00-\u9fa5]");  
  31.                 float xCount = (float)(r.Matches(x).Count) / (float)x.Length;  
  32.                 float yCount = (float)(r.Matches(y).Count) / (float)y.Length;  
  33.   
  34.                 int retval = xCount.CompareTo(yCount);  
  35.   
  36.                 if (retval != 0)  
  37.                 {  
  38.                     return retval;  
  39.                 }  
  40.                 else  
  41.                 {  
  42.                     return x.CompareTo(y);  
  43.                 }  
  44.             }  
  45.         }  
  46.     }  
  47.   
  48.     ///<summary>  
  49.     /// 获取一个网页源码中的标签列表,支持嵌套,一般或去div,td等容器  
  50.     ///</summary>  
  51.     ///<param name="input"></param>  
  52.     ///<param name="tag"></param>  
  53.     ///<returns></returns>  
  54.     public static List<string> GetTags(string input, string tag)  
  55.     {  
  56.         StringReader strReader = new StringReader(input);  
  57.         int lowerThanCharCounter = 0;  
  58.         int lowerThanCharPos = 0;  
  59.         Stack<int> tagPos = new Stack<int>();  
  60.         List<string> taglist = new List<string>();  
  61.         int i = 0;  
  62.         while (true)  
  63.         {  
  64.             try  
  65.             {  
  66.                 int intCharacter = strReader.Read();  
  67.                 if (intCharacter == -1) break;  
  68.   
  69.                 char convertedCharacter = Convert.ToChar(intCharacter);  
  70.   
  71.                 if (lowerThanCharCounter > 0)  
  72.                 {  
  73.                     if (convertedCharacter == '>')  
  74.                     {  
  75.                         lowerThanCharCounter--;  
  76.   
  77.                         string biaoqian = input.Substring(lowerThanCharPos, i - lowerThanCharPos + 1);  
  78.                         if (biaoqian.StartsWith(string.Format("<{0}", tag)))  
  79.                         {  
  80.                             tagPos.Push(lowerThanCharPos);  
  81.                         }  
  82.                         if (biaoqian.StartsWith(string.Format("</{0}", tag)))  
  83.                         {  
  84.                             if (tagPos.Count < 1)  
  85.                                 continue;  
  86.                             int tempTagPos = tagPos.Pop();  
  87.                             string strdiv = input.Substring(tempTagPos, i - tempTagPos + 1);  
  88.                             taglist.Add(strdiv);  
  89.                         }  
  90.                     }  
  91.                 }  
  92.   
  93.                 if (convertedCharacter == '<')  
  94.                 {  
  95.                     lowerThanCharCounter++;  
  96.                     lowerThanCharPos = i;  
  97.                 }  
  98.             }  
  99.             finally  
  100.             {  
  101.                 i++;  
  102.             }  
  103.         }  
  104.         return taglist;  
  105.     }  
  106.   
  107.     ///<summary>  
  108.     /// 获取指定网页的源码,支持编码自动识别  
  109.     ///</summary>  
  110.     ///<param name="url"></param>  
  111.     ///<returns></returns>  
  112.     public static string getDataFromUrl(string url)  
  113.     {  
  114.         string str = string.Empty;  
  115.         HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);  
  116.   
  117.         //设置http头  
  118.         request.AllowAutoRedirect = true;  
  119.         request.AllowWriteStreamBuffering = true;  
  120.         request.Referer = "";  
  121.         request.Timeout = 10 * 1000;  
  122.         request.UserAgent = "";  
  123.   
  124.         HttpWebResponse response = null;  
  125.         try  
  126.         {  
  127.             response = (HttpWebResponse)request.GetResponse();  
  128.             if (response.StatusCode == HttpStatusCode.OK)  
  129.             {  
  130.                 //根据http应答的http头来判断编码  
  131.                 string characterSet = response.CharacterSet;  
  132.                 Encoding encode;  
  133.                 if (characterSet != "")  
  134.                 {  
  135.                     if (characterSet == "ISO-8859-1")  
  136.                     {  
  137.                         characterSet = "gb2312";  
  138.                     }  
  139.                     encode = Encoding.GetEncoding(characterSet);  
  140.                 }  
  141.                 else  
  142.                 {  
  143.                     encode = Encoding.Default;  
  144.                 }  
  145.   
  146.                 //声明一个内存流来保存http应答流  
  147.                 Stream receiveStream = response.GetResponseStream();  
  148.                 MemoryStream mStream = new MemoryStream();  
  149.   
  150.                 byte[] bf = new byte[255];  
  151.                 int count = receiveStream.Read(bf, 0, 255);  
  152.                 while (count > 0)  
  153.                 {  
  154.                     mStream.Write(bf, 0, count);  
  155.                     count = receiveStream.Read(bf, 0, 255);  
  156.                 }  
  157.                 receiveStream.Close();  
  158.   
  159.                 mStream.Seek(0, SeekOrigin.Begin);  
  160.   
  161.                 //从内存流里读取字符串  
  162.                 StreamReader reader = new StreamReader(mStream, encode);  
  163.                 char[] buffer = new char[1024];  
  164.                 count = reader.Read(buffer, 0, 1024);  
  165.                 while (count > 0)  
  166.                 {  
  167.                     str += new String(buffer, 0, count);  
  168.                     count = reader.Read(buffer, 0, 1024);  
  169.                 }  
  170.   
  171.                 //从解析出的字符串里判断charset,如果和http应答的编码不一直  
  172.                 //那么以页面声明的为准,再次从内存流里重新读取文本  
  173.                 Regex reg =  
  174.                     new Regex(@"<meta[\s\S]+?charset=(.*)""[\s\S]+?>",  
  175.                               RegexOptions.Multiline | RegexOptions.IgnoreCase);  
  176.                 MatchCollection mc = reg.Matches(str);  
  177.                 if (mc.Count > 0)  
  178.                 {  
  179.                     string tempCharSet = mc[0].Result("$1");  
  180.                     if (string.Compare(tempCharSet, characterSet, true) != 0)  
  181.                     {  
  182.                         encode = Encoding.GetEncoding(tempCharSet);  
  183.                         str = string.Empty;  
  184.                         mStream.Seek(0, SeekOrigin.Begin);  
  185.                         reader = new StreamReader(mStream, encode);  
  186.                         buffer = new char[255];  
  187.                         count = reader.Read(buffer, 0, 255);  
  188.                         while (count > 0)  
  189.                         {  
  190.                             str += new String(buffer, 0, count);  
  191.                             count = reader.Read(buffer, 0, 255);  
  192.                         }  
  193.                     }  
  194.                 }  
  195.                 reader.Close();  
  196.                 mStream.Close();  
  197.             }  
  198.         }  
  199.         catch (Exception ex)  
  200.         {  
  201.             Trace.TraceError(ex.ToString());  
  202.         }  
  203.         finally  
  204.         {  
  205.             if (response != null)  
  206.                 response.Close();  
  207.         }  
  208.         return str;  
  209.     }  
  210.   
  211.     ///<summary>  
  212.     /// 从一段网页源码中获取正文  
  213.     ///</summary>  
  214.     ///<param name="input"></param>  
  215.     ///<returns></returns>  
  216.     public static string GetMainContent(string input)  
  217.     {  
  218.         string reg1 = @"<(p|br)[^<]*>";  
  219.         string reg2 =  
  220.             @"(
    ([^=]*)(=[^
    ]*)?\][\s\S]*?
    /\1
    )|(?<lj>(?=[^\u4E00-\u9FA5\uFE30-\uFFA0,."");])<a\s+[^>]*>[^<]{2,}</a>(?=[^\u4E00-\u9FA5\uFE30-\uFFA0,."");]))|(?<Style><style[\s\S]+?/style>)|(?<select><select[\s\S]+?/select>)|(?<Script><script[\s\S]*?/script>)|(?<Explein><\!\-\-[\s\S]*?\-\->)|(?<li><li(\s+[^>]+)?>[\s\S]*?/li>)|(?<Html></?\s*[^> ]+(\s*[^=>]+?=['""]?[^""']+?['""]?)*?[^\[<]*>)|(?<Other>&[a-zA-Z]+;)|(?<Other2>\#[a-z0-9]{6})|(?<Space>\s+)|(\&\#\d+\;)";  
  221.   
  222.         //1、获取网页的所有div标签  
  223.         List<string> list = GetTags(input, "div");  
  224.   
  225.         //2、去除汉字少于200字的div  
  226.         List<string> needToRemove = new List<string>();  
  227.         foreach (string s in list)  
  228.         {  
  229.             Regex r = new Regex("[\u4e00-\u9fa5]");  
  230.             if (r.Matches(s).Count < 300)  
  231.             {  
  232.                 needToRemove.Add(s);  
  233.             }  
  234.         }  
  235.         foreach (string s in needToRemove)  
  236.         {  
  237.             list.Remove(s);  
  238.         }  
  239.   
  240.         //3、把剩下的div按汉字比例多少倒序排列,  
  241.         list.Sort(CompareDinosByChineseLength);  
  242.         if (list.Count < 1)  
  243.         {  
  244.             return "";  
  245.         }  
  246.         input = list[list.Count - 1];  
  247.   
  248.         //4、把p和br替换成特殊的占位符[p][br]  
  249.         input = new Regex(reg1, RegexOptions.Multiline | RegexOptions.IgnoreCase).Replace(input, "[$1]");  
  250.   
  251.         //5、去掉HTML标签,保留汉字  
  252.         input = new Regex(reg2, RegexOptions.Multiline | RegexOptions.IgnoreCase).Replace(input, "");  
  253.   
  254.         //6、把特殊占维护替换成回车和换行  
  255.         input = new Regex("\\[p]", RegexOptions.Multiline | RegexOptions.IgnoreCase).Replace(input, "\r\n ");  
  256.         input = new Regex("\\[br]", RegexOptions.Multiline | RegexOptions.IgnoreCase).Replace(input, "\r\n");  
  257.         return input;  
  258.     }  
  259. }  
posted @ 2017-05-12 08:20  zheung  阅读(1782)  评论(0编辑  收藏  举报