之前写过一篇关于用webBrowser抓取动态网页信息的随笔。正如文中提到的,速度是硬伤,并且如果是非动态信息则不必这么麻烦,最近正好有一需求:抓取“驴评网”上的信息
1、所有的州、国家、省、市、区名称
2、该市的所有景点信息(该网站中,大部分都是以市级作为最后的支节点,如果是以区作为最终节点的则以区为单位获取相应景点信息)
3、该市的所有酒店信息

首先,我们需要通过发送一个url来获取返回html源码内容。在这里我们使用的是WebRequest类。使用前需要引用System.Net命名空间
public static string GetUrltoHtml(string Url, string type)
       {
           try
           {
               System.Net.WebRequest wReq = System.Net.WebRequest.Create(Url);
             
               System.Net.WebResponse wResp = wReq.GetResponse();
               System.IO.Stream respStream = wResp.GetResponseStream();
             
               using (System.IO.StreamReader reader = new System.IO.StreamReader(respStream, Encoding.GetEncoding(type)))
               {
              
                   return reader.ReadToEnd();
               }
           }
           catch (System.Exception ex)
           {
               errorMsg = ex.Message;
               return "";
           }
       }
上面的是最通用的代码,可以很清楚的看出用法,在实际使用当中我为了更方便使用,对它再次进行了封装。如下是代码片段
Doc.LoadHtml(getHtmlDoc(Inurl, "GB2312"));   在这里仅需传入要访问的网址(URL)和编码(在这里是GB2312)
由于该网站是按行政区域,州、国家、地区等等按层次划分的,按层次通过超链接可以得到下一层需访问地址的url,所以在这里我先手工找出几个大洲的地址
 private const string urlEurope = @"http://www.lvping.com/alllocations-d120002-europe.html"; //欧洲
        private const string urlNorthAmerica = @"http://www.lvping.com/alllocations-d120004-northamerica.html";   //北美
        private const string urlSouthAmerica = @"http://www.lvping.com/alllocations-d120005-southamerica.html";   //南美
        private const string urlAfrica = @"http://www.lvping.com/alllocations-d120006-africa.html";     //非洲
        private const string urlasia = @"http://www.lvping.com/alllocations-d120001-asia.html";   //亚洲
        private const string urlOceania = @"http://www.lvping.com/alllocations-d120003-oceania.html";     //大洋洲
        private const string urlChina = @"http://www.lvping.com/alllocations-d110000-china.html";  //中国
然后对该大洲的地址进行层层访问,一直访问到最终一级的支节点,最后将该支节点的url保存起来。在这里我是通过遍历来实现的,参考代码如下:
//Inurl  要访问的网址,search=“Main_ulCommon” ID为“Main_ulCommon”的是还有下级链接的,比如法国,普罗旺斯等,search2=“Main_ulSpecial”  ID为=“Main_ulSpecial”的标示是最终
//支节点了,在这里支需将href里面的地址,即最终节点的URL地址保存起来即可。
 private void firstStep(string Inurl,string search,string search2)  
        {
            HtmlAgilityPack.HtmlDocument Doc = new HtmlAgilityPack.HtmlDocument();
            Doc.LoadHtml(getHtmlDoc(Inurl, "GB2312"));
            if (Doc.GetElementbyId(search) != null)
            {
                HtmlAgilityPack.HtmlNode nodeBase = Doc.GetElementbyId(search);
                string basePath = nodeBase.XPath;
                foreach (HtmlAgilityPack.HtmlNode childNode in nodeBase.ChildNodes)
                {
                    foreach (HtmlAgilityPack.HtmlNode chilNodeDiv in childNode.ChildNodes)
                    {
                        string a = chilNodeDiv.FirstChild.XPath;
                        HtmlAgilityPack.HtmlNode aNode = nodeBase.SelectSingleNode(a);
                        string result = aNode.OuterHtml;
                        hrefList.Add(result);
                    }
                }
            }
            //else
            if(Doc.GetElementbyId(search2)!=null)
            {
                HtmlAgilityPack.HtmlNode nodeBase = Doc.GetElementbyId(search2);
                string basePath = nodeBase.XPath;
                foreach (HtmlAgilityPack.HtmlNode childNode in nodeBase.ChildNodes)
                {
                    foreach (HtmlAgilityPack.HtmlNode chilNodeDiv in childNode.ChildNodes)
                    {
                        string a = chilNodeDiv.FirstChild.XPath;
                        HtmlAgilityPack.HtmlNode aNode = nodeBase.SelectSingleNode(a);
                        string result = aNode.OuterHtml;
                        hrefListFinally.Add(result);
                    }
                }
            }
        }
最终的支节点地址效果图如下。

网络爬虫(httpwebrequest)驴评网信息为例

完成上述步骤,我们就获取了该网站,所以最终节点的地址了,然后通过分析,我又发现他们的url地址是有规律的,比如:
http://www.lvping.com/tourism-d392-marseille.html         最终支节点地址
http://www.lvping.com/attractions-d392-marseille.html    景点
http://www.lvping.com/hotels-d392-marseille.html  酒店
大家是不是很容易发现,除了中间的景点,酒店,单词变化之外其他的都是没有改变的,如此我们只需要略微替换就可以得到所有5W多个城市所对应的酒店,和景点信息的网址了。
我实际使用过程当中是在获取到之前的5W多个url地址后,转存到excel表格中。然后再另起一个窗口,在需要的时候读取excel表格中的地址信息,来获取对应的景点,酒店信息。
这个过程就比较简单了,不过我实际使用当中在读取excel的时候碰到点小问题,在这里记录下解决方法。在导入的时候提示外部表不是预期的格式,由于我用的是2007版的,所以我这里是另存为xls格式的excel文件,重新读取就OK了。

另外对于酒店信息块,和景点信息块的查找我都是通过htmlagilitypack来处理的,确实比较好用,在这里记录下差用的几种方法:
1、对于有ID的,可以使用
HtmlAgilityPack.HtmlDocument Doc = new HtmlAgilityPack.HtmlDocument();
Doc.LoadHtml(getHtmlDoc(Inurl, "GB2312"));
DOC.GetElementbyId(这里填入html源码中的ID);  
2、获取所有的超链接:doc.DocumentNode.Descendants("a")    name属性等于kw的input标签。
3、Doc.DocumentNode.SelectSingleNode("//div[@class='breadBar']"); 标示class属性为“breadBar”DIV的标签。
而对应的酒店信息与景点信息可以通过 Doc.GetElementbyId("Main_DistrictSightListPage1_panelByReview");的方式来获得。然后进行加工截取需要的部分即可。
主要代码如下:
 private string getInformationContent(string url,string infTpye)
        {
            string result = string.Empty;
          
        
                if (infTpye == enumInfType.tourism.ToString())
                {
                    HtmlAgilityPack.HtmlDocument Doc = new HtmlAgilityPack.HtmlDocument();
                    Doc.LoadHtml(getHtmlDoc(getInformationURL(url, infTpye), "GB2312"));
                    if (Doc.DocumentNode.SelectSingleNode("//div[@class='breadBar']") != null)
                    {
                        HtmlAgilityPack.HtmlNode node = Doc.DocumentNode.SelectSingleNode("//div[@class='breadBar']");
                        result = node.InnerText;
                    }
                  
                }
                if (infTpye == enumInfType.attraction.ToString())
                {
                    HtmlAgilityPack.HtmlDocument Doc = new HtmlAgilityPack.HtmlDocument();
                    Doc.LoadHtml(getHtmlDoc(getInformationURL(url, infTpye), "GB2312"));
                    if (Doc.GetElementbyId("Main_DistrictSightListPage1_panelByReview") != null)
                    {
                        HtmlAgilityPack.HtmlNode node = Doc.GetElementbyId("Main_DistrictSightListPage1_panelByReview");
                       result=node.InnerText;
                    }
                }
                if (infTpye == enumInfType.hotel.ToString())
                {
                   
                    HtmlAgilityPack.HtmlDocument Doc = new HtmlAgilityPack.HtmlDocument();
                    Doc.LoadHtml(getHtmlDoc(getInformationURL(url, infTpye), "GB2312"));
                    if (Doc.GetElementbyId("hotellist") != null)
                    {
                        HtmlAgilityPack.HtmlNode node = Doc.GetElementbyId("hotellist");
                       result=node.InnerText;
                    }
                }
           
            return result;
        }

由于数据量较大,此次我是讲返回的所有结果信息存储到连接的数据库中。最后所有文件都转存到数据库里了,剩下的就是提取需要信息的过程了,可以通过sql语句,也可以重新转回程序进行处理。

另外在export到数据库的时候还出现了点小插曲,有个地区的名字叫做 L'aldosa 如果直接用sql串,insert into会引发歧义报错,因此在这里我选择了创建存储过程来避免这种问题的出现,部分代码如下:

 for (int i = 0; i < dt.Rows.Count; i++)
                {
                    //string sql = "insert into addressInformation(url,information,attractionURL,attractionINF,hotelsURL,hotelINF) values('" + dt.Rows[i]["处理后url"].ToString() + "','" + dt.Rows[i]["地区信息"] + "','" + dt.Rows[i]["attractionURL地址"] + "','" + dt.Rows[i]["景点信息"] + "','" + dt.Rows[i]["hotelsURL地址"] + "','" + dt.Rows[i]["酒店信息"] + "')";
                    //SqlCommand cmd = new SqlCommand(sql, con);
                    //cmd.ExecuteNonQuery();
                    //由于要插入的地区信息中有包含单引号的内容因此采用预编译的存储过程处理
                    SqlCommand cmd = new SqlCommand("InsertINFproc", con);
                    cmd.CommandType = CommandType.StoredProcedure;
                    SqlParameter para1 = new SqlParameter("@处理后url", SqlDbType.VarChar, 200);
                    SqlParameter para2 = new SqlParameter("@地区信息", SqlDbType.VarChar, 200);
                    SqlParameter para3 = new SqlParameter("@attractionURL地址", SqlDbType.VarChar, 200);
                    SqlParameter para4 = new SqlParameter("@景点信息", SqlDbType.Text);
                    SqlParameter para5 = new SqlParameter("@hotelsURL地址", SqlDbType.VarChar, 200);
                    SqlParameter para6 = new SqlParameter("@酒店信息", SqlDbType.Text);
                    para1.Direction = ParameterDirection.Input;
                    para2.Direction = ParameterDirection.Input;
                    para3.Direction = ParameterDirection.Input;
                    para4.Direction = ParameterDirection.Input;
                    para5.Direction = ParameterDirection.Input;
                    para6.Direction = ParameterDirection.Input;
                    cmd.Parameters.Add(para1);
                    cmd.Parameters.Add(para2);
                    cmd.Parameters.Add(para3);
                    cmd.Parameters.Add(para4);
                    cmd.Parameters.Add(para5);
                    cmd.Parameters.Add(para6);
                    para1.Value = dt.Rows[i]["处理后url"].ToString();
                    para2.Value = dt.Rows[i]["地区信息"].ToString();
                    para3.Value = dt.Rows[i]["attractionURL地址"].ToString();
                    para4.Value = dt.Rows[i]["景点信息"].ToString();
                    para5.Value = dt.Rows[i]["hotelsURL地址"].ToString();
                    para6.Value = dt.Rows[i]["酒店信息"].ToString();
                    cmd.ExecuteNonQuery();
                }
                MessageBox.Show("导入完成");
            }
            catch(Exception e)
            {
                MessageBox.Show(e.Message);
            }
            finally
            {
                con.Close();
            }

  也可以省去存储过程的步骤,实例代码如下:

 string sqlInsertText = "insert into " + dtName + "(title,url,name,haveChildNode) values(@title,@url,@name,@haveChildNode)";
                    SqlCommand cmdInsert = new SqlCommand(sqlInsertText, con);

                    SqlParameter Ptitle = new SqlParameter("@title", SqlDbType.VarChar);
                    SqlParameter Purl = new SqlParameter("@url", SqlDbType.VarChar);
                    SqlParameter Pname = new SqlParameter("@name", SqlDbType.VarChar);
                    SqlParameter PhaveChildNode = new SqlParameter("@haveChildNode", SqlDbType.VarChar);
                    Ptitle.Value = dr["title"];
                    Purl.Value = dr["url"];
                    Pname.Value = dr["name"];
                    PhaveChildNode.Value = dr["haveChileNode"];

                    cmdInsert.Parameters.Add(Ptitle);
                    cmdInsert.Parameters.Add(Purl);
                    cmdInsert.Parameters.Add(Pname);
                    cmdInsert.Parameters.Add(PhaveChildNode);
                    cmdInsert.ExecuteNonQuery();

  

保存到数据库中的效果图如下(初步未处理的)

网络爬虫(httpwebrequest)驴评网信息为例