來自:從此啟程
連結:http://www.cnblogs.com/fancunwei/p/9588629.html
回顧
我們講了利用HtmlAgilityPack,輸入XPath路徑,識別網頁節點,獲取我們需要的內容。評論中也得到了大家的一些支援與建議。
下麵繼續我們的爬蟲系統實踐之路。本篇文章不包含依賴註入/資料訪問/UI介面等,只包含核心的爬蟲相關知識,只能作為Demo使用,拋磚引玉,共同交流。
抽象規則
爬蟲系統之所以重要,正是他能支援各種各樣的資料。要支援識別資料,第一步就是要將規則剝離出來,支援使用者自定義。
爬蟲規則,實際上是跟商品有點類似,如動態屬性,但也有它特殊的地方,如規則可以迴圈巢狀,遞迴,相互取用,連結可以無限下去抓取。更複雜的,就需要自然語言識別,語意分析等領域了。
我用PPT畫了個演示圖。用於演示支援分析文章,活動,天氣等各種型別的規則。
編碼實現
先來定義個採集規則介面,根據規則獲取單個或一批內容。
///
/// 採集規則介面
///
public interface IDataSplider
{
///
/// 得到內容
///
///
///
List
///
/// 得到屬性資訊
///
///
///
///
List
}
必不可少的規則類,用來配置XPath根路徑。
///
/// 採集規則-能滿足串列頁/詳情頁。
///
public class SpliderRule
{
public string Id { get; set; }
public string Url { get; set; }
///
/// 網頁塊
///
public string ContentXPath { get; set; }
///
/// 支援串列式
///
public string EachXPath { get; set; }
///
///
///
public List
}
然後就是屬性欄位的自定義設定,這裡根據內容特性,加入了正則支援。例如評論數是數字,可用正則篩選出數字。還有Attribute欄位,用來獲取node的Attribute資訊。
///
/// 自定義屬性欄位
///
public class RuleField
{
public string Id { get; set; }
public string DisplayName { get; set; }
///
/// 用於儲存的別名
///
public string FieldName { get; set; }
public string XPath { get; set; }
public string Attribute { get; set; }
///
/// 針對獲取的HTml正則過濾
///
public string InnerHtmlRegex { get; set; }
///
/// 針對獲取的Text正則過濾
///
public string InnerTextRegex { get; set; }
///
/// 是否優先取InnerText
///
public bool IsFirstInnerText { get; set; }
}
下麵是根據文章爬蟲規則的解析步驟,實現介面IDataSplider
///
/// 支援串列和詳情頁
///
public class ArticleSplider : IDataSplider
{
///
/// 根據Rule
///
///
///
public List
{
var url = rule.Url;
HtmlWeb web = new HtmlWeb();
//1.支援從web或本地path載入html
var htmlDoc = web.Load(url);
var contentnode = htmlDoc.DocumentNode.SelectSingleNode(rule.ContentXPath);
var list = new List
//串列頁
if (!string.IsNullOrWhiteSpace(rule.EachXPath))
{
var itemsNodes = contentnode.SelectNodes(rule.EachXPath);
foreach (var item in itemsNodes)
{
var fields = GetFields(item, rule);
list.Add(new SpliderContent()
{
Fields = fields,
SpliderRuleId = rule.Id
});
}
return list;
}
//詳情頁
var cfields = GetFields(contentnode, rule);
list.Add(new SpliderContent()
{
Fields = cfields,
SpliderRuleId = rule.Id
});
return list;
}
public List
{
var fields = new List
foreach (var rulefield in rule.RuleFields)
{
var field = new Field() { DisplayName = rulefield.DisplayName, FieldName = “” };
var fieldnode = item.SelectSingleNode(rulefield.XPath);
if (fieldnode != null)
{
field.InnerHtml = fieldnode.InnerHtml;
field.InnerText = fieldnode.InnerText;
field.AfterRegexHtml = !string.IsNullOrWhiteSpace(rulefield.InnerHtmlRegex) ? Regex.Replace(fieldnode.InnerHtml, rulefield.InnerHtmlRegex, “”) : fieldnode.InnerHtml;
field.AfterRegexText = !string.IsNullOrWhiteSpace(rulefield.InnerTextRegex) ? Regex.Replace(fieldnode.InnerText, rulefield.InnerTextRegex, “”) : fieldnode.InnerText;
//field.AfterRegexHtml = Regex.Replace(fieldnode.InnerHtml, rulefield.InnerHtmlRegex, “”);
//field.AfterRegexText = Regex.Replace(fieldnode.InnerText, rulefield.InnerTextRegex, “”);
if (!string.IsNullOrWhiteSpace(rulefield.Attribute))
{
field.Value = fieldnode.Attributes[rulefield.Attribute].Value;
}
else
{
field.Value = rulefield.IsFirstInnerText ? field.AfterRegexText : field.AfterRegexHtml;
}
}
fields.Add(field);
}
return fields;
}
}
還是以部落格園為例,配置內容和屬性的自定義規則
///
///
///
public void RunArticleRule()
{
var postitembodyXPath = “div[@class=’post_item_body’]//”;
var postitembodyFootXPath = postitembodyXPath+ “div[@class=’post_item_foot’]//”;
var rule = new SpliderRule()
{
ContentXPath = “//div[@id=’post_list’]”,
EachXPath = “div[@class=’post_item’]”,
Url = “https://www.cnblogs.com”,
RuleFields = new List
new RuleField(){ DisplayName=”推薦”, XPath=”*//span[@class=’diggnum’]”, IsFirstInnerText=true },
new RuleField(){ DisplayName=”標題”,XPath=postitembodyXPath+”a[@class=’titlelnk’]”, IsFirstInnerText=true },
new RuleField(){ DisplayName=”URL”,XPath=postitembodyXPath+”a[@class=’titlelnk’]”,Attribute=”href”, IsFirstInnerText=true },
new RuleField(){ DisplayName=”簡要”,XPath=postitembodyXPath+”p[@class=’post_item_summary’]”, IsFirstInnerText=true },
new RuleField(){ DisplayName=”作者”,XPath=postitembodyFootXPath+”a[@class=’lightblue’]”, IsFirstInnerText=true },
new RuleField(){ DisplayName=”作者URL”,XPath=postitembodyFootXPath+”a[@class=’lightblue’]”,Attribute=”href”, IsFirstInnerText=true },
new RuleField(){ DisplayName=”討論數”, XPath=”span[@class=’article_comment’]”,IsFirstInnerText=true, InnerTextRegex=@”[^0-9]+” },
new RuleField(){ DisplayName=”閱讀數”, XPath=postitembodyFootXPath+”span[@class=’article_view’]”,IsFirstInnerText=true, InnerTextRegex=@”[^0-9]+” },
}
};
var splider = new ArticleSplider();
var list = splider.GetByRule(rule);
foreach (var item in list)
{
var msg = string.Empty;
item.Fields.ForEach(M =>
{
if (M.DisplayName != “簡要” && !M.DisplayName.Contains(“URL”))
{
msg += $”{M.DisplayName}:{M.Value}”;
}
});
Console.WriteLine(msg);
}
}
執行效果
效果完美!
經過簡單的重構,我們已經達到了上篇的效果。
常用規則模型和自定義規則模型
寫到這裡,我想到了一般UML圖工具或Axsure原型等,都會內建各種常用元件,那麼文章爬蟲模型也是我們內建的一種常用元件了。
後續我們完全可以按照上面的套路支援其他模型。除了常用模型之外,在網頁或客戶端上,高階的爬蟲工具會支援使用者自定義配置,根據配置來獲取內容。
上面的SpliderRule已經能支援大部分內容管理系統單頁面抓取。但無法支援規則相互取用,然後根據抓取的內容取用配置規則繼續抓取。(這裡也許有什麼專門的名詞來描述:遞迴爬蟲?)。
今天主要是在上篇文章的基礎上重構而來,支援了規則配置。為了有點新意,就多提供兩個配置例子吧。
例子1:文章詳情
我們以上篇文章為例,獲取文章詳情。 主要結點是標題,內容。其他額外屬性暫不處理。
編碼實現
///
/// 詳情
///
public void RunArticleDetail() {
var rule = new SpliderRule()
{
ContentXPath = “//div[@id=’post_detail’]”,
EachXPath = “”,
Url = ” https://www.cnblogs.com/fancunwei/p/9581168.html”,
RuleFields = new List
new RuleField(){ DisplayName=”標題”,XPath=”*//div[@class=’post’]//a[@id=’cb_post_title_url’]”, IsFirstInnerText=true },
new RuleField(){ DisplayName=”詳情”,XPath=”*//div[@class=’postBody’]//div[@class=’blogpost-body’]”,Attribute=””, IsFirstInnerText=false }
}
};
var splider = new ArticleSplider();
var list = splider.GetByRule(rule);
foreach (var item in list)
{
var msg = string.Empty;
item.Fields.ForEach(M =>
{
Console.WriteLine($”{M.DisplayName}:{M.Value}”);
});
Console.WriteLine(msg);
}
}
執行效果
效果同樣完美!
例子2:天氣預報
天氣預報的例子,我們就以上海8-15天預報為例。
分析結構
點選連結,我們發現 今天/7天/8-15天/40天分別是不同的路由頁面,那就簡單了,我們只考慮當前頁面就行。
還有個問題,那個晴天雨天的圖片,是按樣式顯示的。我們雖然能抓到html,但樣式還未考慮,,HtmlAgilityPack應該有個從WebBrowser獲取網頁的,似乎能支援樣式。本篇文章先跳過這個問題,以後再細究。
配置規則
根據網頁結構,配置對應規則。
public void RunWeather()
{
var rule = new SpliderRule()
{
ContentXPath = “//div[@id=’15d’]”,
EachXPath = “*//li”,
Url = “http://www.weather.com.cn/weather15d/101020100.shtml”,
RuleFields = new List
() { new RuleField(){ DisplayName=”日期”,XPath=”span[@class=’time’]”, IsFirstInnerText=true },
new RuleField(){ DisplayName=”天氣”,XPath=”span[@class=’wea’]”,Attribute=””, IsFirstInnerText=false },
new RuleField(){ DisplayName=”區間”,XPath=”span[@class=’tem’]”,Attribute=””, IsFirstInnerText=false },
new RuleField(){ DisplayName=”風向”,XPath=”span[@class=’wind’]”,Attribute=””, IsFirstInnerText=false },
new RuleField(){ DisplayName=”風力”,XPath=”span[@class=’wind1′]”,Attribute=””, IsFirstInnerText=false },
}
};
var splider = new ArticleSplider();
var list = splider.GetByRule(rule);
foreach (var item in list)
{
var msg = string.Empty;
item.Fields.ForEach(M =>
{
msg += $”{M.DisplayName}:{M.Value} “;
});
Console.WriteLine(msg);
}
}
執行效果
效果再次完美!
原始碼
程式碼已提交到GitHub:https://github.com/fancunwei/CsharpFanDemo
總結探討
綜上所述,我們實現單頁面的自定義規則,但也遺留了一個小問題。天氣預報晴天陰天效果圖,原文是用樣式展示的。
針對這種不規則問題,如果程式碼定製當然很容易,但如果做成通用,有什麼好辦法呢?請提出你的建議!
下篇文章,繼續探討多頁面/遞迴爬蟲自定義規則的實現。
●編號152,輸入編號直達本文
●輸入m獲取文章目錄
Web開發
更多推薦《18個技術類公眾微信》
涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。