1、什麼是網路爬蟲 關於爬蟲百度百科這樣定義的:網路爬蟲(又被稱為網頁蜘蛛,網路機器人,在FOAF社區中間,更經常的稱為網頁追逐者),是一種按照一定的規則,自動地抓取萬維網信息的程式或者腳本。另外一些不常使用的名字還有螞蟻、自動索引、模擬程式或者蠕蟲。從搜索引擎開始,爬蟲應該就出現了,爬蟲所做的事情 ...
1、什麼是網路爬蟲
關於爬蟲百度百科這樣定義的:網路爬蟲(又被稱為網頁蜘蛛,網路機器人,在FOAF社區中間,更經常的稱為網頁追逐者),是一種按照一定的規則,自動地抓取萬維網信息的程式或者腳本。另外一些不常使用的名字還有螞蟻、自動索引、模擬程式或者蠕蟲。從搜索引擎開始,爬蟲應該就出現了,爬蟲所做的事情就是分析URL、下載WebServer返回的HTML、分析HTML內容、構建HTTP請求的模擬、在爬蟲過程中存儲有用的信息等等。簡單點說,就是把別人網站上的東西爬下來,至於爬做什麼用就看你自己了。
寫網路爬蟲很多語言都可以寫,比如眾所周知的Python以及、PHP、C、Java等等。今天我就基於.Net中的HtmlAgilityPack類寫一個簡單的爬蟲。
2、HtmlAgilityPack類
HtmlAgilityPack 是 .NET 下的一個 HTML 解析類庫。支持用 XPath 來解析 HTML 。命名空間: HtmlAgilityPack,下載地址:http://htmlagilitypack.codeplex.com/releases/view/90925
2.1基本屬性
Attributes 獲取節點的屬性集合
ChildNodes 獲取子節點集合(包括文本節點)
Closed 該節點是否已關閉(</xxx>)
ClosingAttributes 在關閉標簽的屬性集合
FirstChild 獲取第一個子節點
HasAttributes 判斷該節點是否含有屬性
HasChildNodes 判斷該節點是否含有子節點
HasClosingAttributes 判斷該節點的關閉標簽是否含有屬性(</xxx class="xxx">)
Id 獲取該節點的Id屬性
InnerHtml 獲取該節點的Html代碼
InnerText 獲取該節點的內容,與InnerHtml不同的地方在於它會過濾掉Html代碼,而InnerHtml是連Html代碼一起輸出
LastChild 獲取最後一個子節點
Line 獲取該節點的開始標簽或開始代碼位於整個HTML源代碼的第幾行(行號)
LinePosition 獲取該節點位於第幾列
Name Html元素名
NextSibling 獲取下一個兄弟節點
NodeType 獲取該節點的節點類型
OriginalName 獲取原始的未經更改的元素名
OuterHtml 整個節點的代碼
OwnerDocument 節點所在的HtmlDocument文檔
ParentNode 獲取該節點的父節點
PreviousSibling 獲取前一個兄弟節點
StreamPosition 該節點位於整個Html文檔的字元位置
XPath 根據節點返回該節點的XPath
2.2方法
IEnumerable<HtmlNode> Ancestors(); 返回此元素的所有上級節點的集合。
IEnumerable<HtmlNode> Ancestors(string name); 返回此元素參數名字匹配的所有上級節點的集合。
IEnumerable<HtmlNode> AncestorsAndSelf(); 返回此元素的所有上級節點和自身的集合。
IEnumerable<HtmlNode> AncestorsAndSelf(string name); 返回此元素的名字匹配的所有上級節點和自身的集合。
HtmlNode AppendChild(HtmlNode newChild); 將參數元素追加到為調用元素的子元素(追加在最後)
void AppendChildren(HtmlNodeCollection newChildren); 將參數集合中的元素追加為調用元素的子元素(追加在最後)
HtmlNode PrependChild(HtmlNode newChild); 將參數中的元素作為子元素,放在調用元素的最前面
void PrependChildren(HtmlNodeCollection newChildren); 將參數集合中的所有元素作為子元素,放在調用元素前面
static bool CanOverlapElement(string name); 確定是否可以保存重覆的元素
IEnumerable<HtmlAttribute> ChildAttributes(string name); 獲取所有子元素的屬性(參數名要與元素名匹配)
HtmlNode Clone(); 本節點克隆到一個新的節點
HtmlNode CloneNode(bool deep); 節點克隆到一個新的幾點,參數確定是否連子元素一起克隆
HtmlNode CloneNode(string newName); 克隆的同時更改元素名
HtmlNode CloneNode(string newName, bool deep); 克隆的同時更改元素名。參數確定是否連子元素一起克隆
void CopyFrom(HtmlNode node); 創建重覆的節點和其下的子樹。
void CopyFrom(HtmlNode node, bool deep); 創建節點的副本。
XPathNavigator CreateNavigator(); 返回的一個對於此文檔的XPathNavigator
static HtmlNode CreateNode(string html); 靜態方法,允許用字元串創建一個新節點
XPathNavigator CreateRootNavigator(); 創建一個根路徑的XPathNavigator
IEnumerable<HtmlNode> DescendantNodes(); 獲取所有子代節點
IEnumerable<HtmlNode> DescendantNodesAndSelf(); 獲取所有的子代節點以及自身
IEnumerable<HtmlNode> Descendants(); 獲取枚舉列表中的所有子代節點
IEnumerable<HtmlNode> Descendants(string name); 獲取枚舉列表中的所有子代節點,註意元素名要與參數匹配
IEnumerable<HtmlNode> DescendantsAndSelf(); 獲取枚舉列表中的所有子代節點以及自身
IEnumerable<HtmlNode> DescendantsAndSelf(string name); 獲取枚舉列表中的所有子代節點以及自身,註意元素名要與參數匹配
HtmlNode Element(string name); 根據參數名獲取一個元素
IEnumerable<HtmlNode> Elements(string name); 根據參數名獲取匹配的元素集合
bool GetAttributeValue(string name, bool def); 幫助方法,用來獲取此節點的屬性的值(布爾類型)。如果未找到該屬性,則將返回預設值。
int GetAttributeValue(string name, int def); 幫助方法,用來獲取此節點的屬性的值(整型)。如果未找到該屬性,則將返回預設值。
string GetAttributeValue(string name, string def); 幫助方法,用來獲取此節點的屬性的值(字元串類型)。如果未找到該屬性,則將返回預設值。
HtmlNode InsertAfter(HtmlNode newChild, HtmlNode refChild); 將一個節點插入到第二個參數節點的後面,與第二個參數是兄弟關係
HtmlNode InsertBefore(HtmlNode newChild, HtmlNode refChild); 講一個節點插入到第二個參數節點的後面,與第二個參數是兄弟關係
static bool IsCDataElement(string name); 確定是否一個元素節點是一個 CDATA 元素節點。
static bool IsClosedElement(string name); 確定是否封閉的元素節點
static bool IsEmptyElement(string name); 確定是否一個空的元素節點。
static bool IsOverlappedClosingElement(string text); 確定是否文本對應於一個節點可以保留重疊的結束標記。
void Remove(); 從父集合中移除調用節點
void RemoveAll(); 移除調用節點的所有子節點以及屬性
void RemoveAllChildren(); 移除調用節點的所有子節點
HtmlNode RemoveChild(HtmlNode oldChild); 移除調用節點的指定名字的子節點
HtmlNode RemoveChild(HtmlNode oldChild, bool keepGrandChildren);移除調用節點調用名字的子節點,第二個參數確定是否連孫子節點一起移除
HtmlNode ReplaceChild(HtmlNode newChild, HtmlNode oldChild); 將調用節點原有的一個子節點替換為一個新的節點,第二個參數是舊節點
HtmlNodeCollection SelectNodes(string xpath); 根據XPath獲取一個節點集合
HtmlNode SelectSingleNode(string xpath); 根據XPath獲取唯一的一個節點
HtmlAttribute SetAttributeValue(string name, string value); 設置調用節點的屬性
string WriteContentTo(); 將該節點的所有子級都保存到一個字元串中。
void WriteContentTo(TextWriter outText); 將該節點的所有子級都保存到指定的 TextWriter。
string WriteTo(); 將當前節點保存到一個字元串中。
void WriteTo(TextWriter outText); 將當前節點保存到指定的 TextWriter。
void WriteTo(XmlWriter writer);
3、第一個爬蟲程式
3.1在VS2017中建立一個web項目拖個伺服器控制項按鈕上去(我VS是用2017的)
3.2後臺代碼及解釋
/// <summary> /// 博客園精華單機按鈕 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void btntwo_Click(object sender, EventArgs e) { int sum = 0; for (int i = 1; i < 2; i++) { SqlDB sb = new SqlDB(); HtmlWeb wb = new HtmlWeb(); string webaddress= ""; if (i==1) { webaddress="http://www.cnblogs.com/pick/";//為啥http://www.cnblogs.com/pick/#p10進去還是第一頁 string webbbb = GetHTML(webaddress); } else { webaddress = string.Format("http://www.cnblogs.com/pick/{0}", ("#p"+i.ToString()).ToString()); string webbbb = GetHTML(webaddress); } try { HtmlDocument doc = wb.Load(webaddress); HtmlNode node = doc.GetElementbyId("post_list"); if (node != null) { foreach (HtmlNode hnode in node.ChildNodes) { if (hnode.Attributes["class"] == null || hnode.Attributes["class"].Value != "post_item") continue; HtmlNode hn = HtmlNode.CreateNode(hnode.OuterHtml); //推薦 int recommend = Convert.ToInt32((hn.SelectSingleNode("//*[@class=\"diggnum\"]").InnerText).Trim()); //標題 string title = hn.SelectSingleNode("//*[@class=\"titlelnk\"]").InnerText; //網址 string webhref = hn.SelectSingleNode("//*[@class=\"titlelnk\"]").Attributes["href"].Value.ToString(); //介紹 string introduce = hn.SelectSingleNode("//*[@class=\"post_item_summary\"]").InnerText; string articletimetest = hn.SelectSingleNode("//*[@class=\"post_item_foot\"]").InnerText; //發表時間(陳樹義 發佈於 2017 - 11 - 15 10:13 評論(41)閱讀(7372) string articletime = ((hn.SelectSingleNode("//*[@class=\"post_item_foot\"]").InnerText).Trim()).Replace("\r\n", "+"); //分割字元串 string[] st = articletime.Split('+'); //取出(發佈於 2017 - 11 - 15 10:13) string pp = (st[1].ToString()).Trim(); //分割字元串 string[] qq = pp.Split(' '); //取出(2017/11/15 10 :13) DateTime gg = Convert.ToDateTime(qq[1].ToString() + " " + qq[2].ToString()); try { string sql = string.Format(@"insert into CnblogsList( Recommend, Title,Contents, Introduce, WebHref, ArticleTime) values({0},'{1}','{2}','{3}','{4}','{5}')", recommend, title, GetContentsString(webhref), introduce, webhref, gg); sb.ExecuteNonQuery(sql); sum++; } catch (Exception ex) { Response.Write(ex.Message); } Response.Write(GetContentsString(webhref)+"<hr /> <hr />"); // 陳樹義 發佈於 2017 - 11 - 15 10:13 評論(41)閱讀(7372) Response.Write("推薦:" + (hn.SelectSingleNode("//*[@class=\"diggnum\"]").InnerText)+"<br />"); Response.Write("標題:" + (hn.SelectSingleNode("//*[@class=\"titlelnk\"]").InnerText) + "<br />"); Response.Write("標題對應的網址:" + (hn.SelectSingleNode("//*[@class=\"titlelnk\"]").Attributes["href"].Value.ToString()) + "<br />"); Response.Write("介紹:" + (hn.SelectSingleNode("//*[@class=\"post_item_summary\"]").InnerText) + "<br />"); Response.Write("時間:" + (hn.SelectSingleNode("//*[@class=\"post_item_foot\"]").InnerText) + "<br /><hr />"); } } else { Response.Write("節點為空 +++++ 出錯節點Iiiii是:" + i.ToString() + "<br /> 網址:" + webaddress.ToString() + "<br /> 插入數據:"+ sum +"條"); return; } } catch (Exception esz) { Response.Write(esz.Message); } } }
這是獲取網頁源碼的代碼
/// <summary> /// 獲取網頁源碼 /// </summary> /// <param name="url"></param> /// <returns></returns> public string GetHTML(string url) { WebClient web = new WebClient(); web.Encoding = Encoding.UTF8; string buffer = web.DownloadString(url); return buffer; }
上面的for迴圈目的是獲取精華區後面頁數的內容,但是效果並不是這樣的
我早谷歌上F12調試發現並不是這樣的額 請求地址不對。為以為在後面加?PageIndex= XX就可以實現 還是不可以 因為這是經過處理的,在Header上面有地址https://www.cnblogs.com/mvc/AggSite/PostList.aspx你們仔細就可以看到。這樣做瞭如果頁數寫多了就會重覆出現第一頁的內容 資料庫中是保存的 資料庫的表結構我上圖
我一弄才發現原來這個地址是博客園裡面的內容時刻更新的。經過處理取出精華區的。一次去可以取出2天的內容 不管是發佈在博客園首頁的還是沒有發佈在首頁的都有。我就爬蟲爬了一次 試試看 這是第二個按鈕的源碼
/// <summary> /// 博客園數據最近大約2天 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void BtnNowTowDay_Click(object sender, EventArgs e) { int sum = 0; for (int i = 1; i < 200; i++) { //這是SqlDB類 SqlDB sb = new SqlDB(); //實例HtmlWdb HtmlWeb wb = new HtmlWeb(); //網址 string webaddress = ""; webaddress = string.Format("https://www.cnblogs.com/mvc/AggSite/PostList.aspx?PageIndex={0}", + i); //網頁源碼 有html的 string webbbb = GetHTML(webaddress); try { HtmlDocument doc = wb.Load(webaddress); //獲取div[@class='post_item的上級目錄 //這個可以根據節點 ID選擇 建議多看一下Xpath 就好理解了 HtmlNode node = doc.DocumentNode.SelectSingleNode("//div[@class='post_item']").SelectSingleNode(".."); //HtmlNode node = doc.GetElementbyId("#id"); if (node != null) { foreach (HtmlNode hnode in node.ChildNodes) { if (hnode.Attributes["class"] == null || hnode.Attributes["class"].Value != "post_item") continue; HtmlNode hn = HtmlNode.CreateNode(hnode.OuterHtml); //推薦 int recommend = Convert.ToInt32((hn.SelectSingleNode("//*[@class=\"diggnum\"]").InnerText).Trim()); //標題 string title = hn.SelectSingleNode("//*[@class=\"titlelnk\"]").InnerText; //網址 string webhref = hn.SelectSingleNode("//*[@class=\"titlelnk\"]").Attributes["href"].Value.ToString(); //介紹 string introduce = hn.SelectSingleNode("//*[@class=\"post_item_summary\"]").InnerText; string articletimetest = hn.SelectSingleNode("//*[@class=\"post_item_foot\"]").InnerText; //發表時間(陳樹義 發佈於 2017 - 11 - 15 10:13 評論(41)閱讀(7372) string articletime = ((hn.SelectSingleNode("//*[@class=\"post_item_foot\"]").InnerText).Trim()).Replace("\r\n", "+"); //分割字元串 string[] st = articletime.Split('+'); //取出(發佈於 2017 - 11 - 15 10:13) string pp = (st[1].ToString()).Trim(); //分割字元串 string[] qq = pp.Split(' '); //取出(2017/11/15 10 :13) DateTime gg = Convert.ToDateTime(qq[1].ToString() + " " + qq[2].ToString()); try { string sql = string.Format(@"insert into CnblogsList( Recommend, Title,Contents, Introduce, WebHref, ArticleTime) values({0},'{1}','{2}','{3}','{4}','{5}')", recommend, title, GetContentsString(webhref), introduce, webhref, gg); sb.ExecuteNonQuery(sql); sum++; } catch (Exception ex) { Response.Write(ex.Message); } } } else { Response.Write("節點為空 +++++ 出錯節點Iiiii是:" + i.ToString() + "<br /> 網址:" + webaddress.ToString() + "<br /> 插入數據:" + sum + "條"); return; } } catch (Exception esz) { Response.Write(esz.Message); } } }
下麵是住區自己博客園的後臺
/// <summary> /// 抓取自己博客園的 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void btnWY_Click(object sender, EventArgs e) { SqlDB sb = new SqlDB(); HtmlWeb wh = new HtmlWeb(); HtmlDocument doc = wh.Load("http://www.cnblogs.com/w5942066/"); HtmlNode node = doc.DocumentNode.SelectSingleNode("//div[@class='day']").SelectSingleNode(".."); if(node !=null) { foreach(HtmlNode hn in node.ChildNodes) { if(hn.Attributes["class"] == null || hn.Attributes["class"].Value!="day") { continue; } HtmlNode hnn = HtmlNode.CreateNode(hn.OuterHtml); //shijian string recommend = hnn.SelectSingleNode("//*[@class=\"dayTitle\"]").InnerText; //標題 string title = hnn.SelectSingleNode("//*[@class=\"postTitle\"]").InnerText; //網址 string webhref = hnn.SelectSingleNode("//*[@class=\"postTitle2\"]").Attributes["href"].Value.ToString(); //介紹 string introduce = hnn.SelectSingleNode("//*[@class=\"c_b_p_desc\"]").InnerText; //時間 // string articletimetest = hn.SelectSingleNode("//*[@class=\"postDesc\"]").InnerText; //發表時間(陳樹義 發佈於 2017 - 11 - 15 10:13 評論(41)閱讀(7372) // string articletime = ((hn.SelectSingleNode("//*[@class=\"postDesc\"]").InnerText).Trim()).Replace("\r\n", "+"); //分割字元串 //string[] st = articletime.Split('+'); //取出(發佈於 2017 - 11 - 15 10:13) //string pp = (st[1].ToString()).Trim(); //分割字元串 //string[] qq = pp.Split(' '); //取出(2017/11/15 10 :13) DateTimeFormatInfo dtFormat = new DateTimeFormatInfo(); dtFormat.ShortDatePattern = "yyyy年MM月dd日"; DateTime dt = Convert.ToDateTime(recommend, dtFormat); try { string sql = string.Format(@"insert into CnblogsList( Recommend, Title,Contents, Introduce, WebHref, ArticleTime) values({0},'{1}','{2}','{3}','{4}','{5}')", 520, title, GetContentsString(webhref), introduce, webhref, dt); sb.ExecuteNonQuery(sql); } catch (Exception ex) { Response.Write(ex.Message); } } }
3.3列表顯示出來
這裡我用的是一個原始的GridView前臺頁面代碼
<form id="form1" runat="server"> <div class="main"> <h1 style="height:35px;line-height:30px;text-align:center;padding-top:50px;">博客園文章列表</h1><br /><hr /> <asp:GridView ID="gdMain" runat="server" Width="100%" AllowPaging="True" AutoGenerateColumns="False" DataKeyNames="ID" DataSourceID="SqlDataSource1" CellPadding="4" ForeColor="#333333" GridLines="None" PageSize="30" > <AlternatingRowStyle BackColor="White" /> <Columns> <asp:BoundField HeaderStyle-Width="3%" ItemStyle-HorizontalAlign="Center" DataField="ID" HeaderText="編號" InsertVisible="False" ReadOnly="True" SortExpression="ID" > <HeaderStyle Width="10%"></HeaderStyle> <ItemStyle HorizontalAlign="Center"></ItemStyle> </asp:BoundField> <asp:BoundField ItemStyle-Wrap="false" HeaderStyle-Width="10%" ItemStyle-HorizontalAlign="Left" DataField="Title" HeaderText="標題" SortExpression="Title" > <HeaderStyle Width="10%"></HeaderStyle> <ItemStyle HorizontalAlign="Center"></ItemStyle> </asp:BoundField> <asp:BoundField ItemStyle-Wrap="false" HeaderStyle-Width="30%" ItemStyle-HorizontalAlign="left" DataField="Contents" HeaderText="內容" SortExpression="Contents" > <HeaderStyle Width="30%"></HeaderStyle> <ItemStyle HorizontalAlign="Center"></ItemStyle> </asp:BoundField> <asp:BoundField ItemStyle-Wrap="false" HeaderStyle-Width="20%" ItemStyle-HorizontalAlign="left" DataField="WebHref" HeaderText="原文網址" SortExpression="WebHref" > <HeaderStyle Width="20%"></HeaderStyle> <ItemStyle HorizontalAlign="Center"></ItemStyle> </asp:BoundField> <asp:BoundField ItemStyle-Wrap="false" HeaderStyle-Width="10%" ItemStyle-HorizontalAlign="Center" DataField="ArticleTime" HeaderText="時間" SortExpression="ArticleTime" > <HeaderStyle Width="10%"></HeaderStyle> <ItemStyle HorizontalAlign="Center"></ItemStyle> </asp:BoundField> <asp:HyperLinkField DataNavigateUrlFields="ID" DataNavigateUrlFormatString="PageInfo.aspx?ID={0}" HeaderStyle-Width="5%" ItemStyle-HorizontalAlign="Center" Text="詳細" DataTextFormatString="詳細" Target="_blank"/> </Columns> <EditRowStyle BackColor="#2461BF" /> <FooterStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" /> <HeaderStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" /> <PagerSettings PageButtonCount="30" /> <PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign="Center" /> <RowStyle BackColor="#EFF3FB" /> <SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" /> <SortedAscendingCellStyle BackColor="#F5F7FB" /> <SortedAscendingHeaderStyle BackColor="#6D95E1" /> <SortedDescendingCellStyle BackColor="#E9EBEF" /> <SortedDescendingHeaderStyle BackColor="#4870BE" Wrap="False" /> </asp:GridView> <asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:TestBaseConnectionString %>" SelectCommand="select ID,SUBSTRING(Title,0,30)AS Title,SUBSTRING(Contents,0,30)AS Contents ,SUBSTRING(Webhref,0,20)AS WebHref , ArticleTime from CnblogsList"></asp:SqlDataSource> </div> </form>
後臺部分
namespace NetCrawlerTest.Winfrom { public partial class CnblogListData : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { //if(!IsPostBack) //{ // GetData(); //} } public void GetData() { SqlDB sb = new SqlDB(); string _Sql = "select ID,SUBSTRING(Title,0,50)AS Title,SUBSTRING(Contents,0,60)AS Contents ,SUBSTRING(Webhref,0,50)AS WebHref , ArticleTime from CnblogsList"; gdMain.DataSource = sb.ExecuteDataSet(_Sql).Tables[0]; gdMain.DataBind(); } } }
好了來看看效果
3.4具體的博客顯示
前臺代碼就是上面圖片中的PageInfo頁面
<form id="form1" runat="server"> <div class="main"> <h1 style="height:35px;line-height:30px;text-align:center; padding-top:50px"><%=GetTitle() %></h1><br /> <div style="text-align:right;"> <a href="CnblogListData.aspx" target="_parent">返回列表</a>            <a href="PageInfo.aspx?ID=<%=UpIDS() %>" style="width:100px; ">上一條</a>    |     <a href="PageInfo.aspx?ID=<%=UpIDX() %>" style="width:100px;">下一條</a>    </div><hr /> <div style="text-align:center;">發表時間:<%=GetTime() %>