首页 > IT职业 > IT职场 > .NetCore实践爬虫系统(二)自定义规则
用,抛砖引玉,共同交流。
爬虫系统之所以重要,正是他能支持各种各样的数据。要支持识别数据,第一步就是要将规则剥离出来,支持用户自定义。
爬虫规则,实际上是跟商品有点类似,如动态属性,但也有它特殊的地方,如规则可以循环嵌套,递归,相互引用,链接可以无限下去抓取。更复杂的,就需要自然语言识别,语义分析等领域了。
我用PPT画了个演示图。用于演示支持分析文章,活动,天气等各种类型的规则。
PPT示意图
先来定义个采集规则接口,根据规则获取单个或一批内容。
/// <summary>
/// 采集规则接口
/// </summary>
public interface IDataSplider
{
/// <summary>
/// 得到内容
/// </summary>
/// <param name="rule"></param>
/// <returns></returns>
List<SpliderContent> GetByRule(SpliderRule rule);
/// <summary>
/// 得到属性信息
/// </summary>
/// <param name="node"></param>
/// <param name="rule"></param>
/// <returns></returns>
List<Field> GetFields(HtmlNode node, SpliderRule rule);
}
必不可少的规则类,用来配置XPath根路径。
/// <summary>
/// 采集规则-能满足列表页/详情页。
/// </summary>
public class SpliderRule
{
public string Id { get; set; } public string Url { get; set; } /// <summary>
/// 网页块
/// </summary>
public string ContentXPath { get; set; } /// <summary>
/// 支持列表式
/// </summary>
public string EachXPath { get; set; } /// <summary>
///
/// </summary>
public List<RuleField> RuleFields { get; set; }
}
然后就是属性字段的自定义设置,这里根据内容特性,加入了正则支持。例如评论数是数字,可用正则筛选出数字。还有Attribute字段,用来获取node的Attribute信息。
/// <summary>
/// 自定义属性字段
/// </summary>
public class RuleField
{
public string Id { get; set; } public string DisplayName { get; set; } /// <summary>
/// 用于存储的别名
/// </summary>
public string FieldName { get; set; } public string XPath { get; set; } public string Attribute { get; set; } /// <summary>
/// 针对获取的HTml正则过滤
/// </summary>
public string InnerHtmlRegex { get; set; } /// <summary>
/// 针对获取的Text正则过滤
/// </summary>
public string InnerTextRegex { get; set; } /// <summary>
/// 是否优先取InnerText
/// </summary>
public bool IsFirstInnerText { get; set; }
}
下面是根据文章爬虫规则的解析步骤,实现接口IDataSplider
/// <summary>
/// 支持列表和详情页
/// </summary>
public class ArticleSplider : IDataSplider
{ /// <summary>
/// 根据Rule
/// </summary>
/// <param name="rule"></param>
/// <returns></returns>
public List<SpliderContent> GetByRule(SpliderRule rule)
{
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<SpliderContent>(); //列表页
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<Field> GetFields(HtmlNode item, SpliderRule rule)
{
var fields = new List<Field>();
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;
}
}
还是以博客园为例,配置内容和属性的自定义规则
/// <summary>
///
/// </summary>
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<RuleField>() { 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已经能支持大部分内容管理系统单页面抓取。但无法支持规则相互引用,然后根据抓取的内容引用配置规则继续抓取。(这里也许有什么专门的名词来描述:递归爬虫?)。
今天主要是在上篇文章的基础上重构而来,支持了规则配置。为了有点新意,就多提供两个配置例子吧。
我们以上篇文章为例,获取文章详情。
主要结点是标题,内容。其他额外属性暂不处理。
文章详情结构图
/// <summary>
/// 详情
/// </summary>
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<RuleField>() { 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);
}
}
效果同样完美!
文章详情
作者:范存威
链接:https://www.jianshu.com/p/101afc0fafd4
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/1334/viewspace-2820218/ ,如需转载,请注明出处,否则将追究法律责任。