目前很多大型网站在反爬虫时采取IP限制策略,限制同一个IP的请求频率及次数,或者同一IP在达到请求次数后强制登陆验证等。此时我们就需要用到代理IP来突破限制,此篇我们介绍通过DotnetSpider框架爬取西刺高匿代理IP的过程。
DotnetSpider框架简介
DotnetSpider是.net core开发的开源爬虫项目,基本开箱即用,对于爬虫各个部分的封装已经比较成熟,github下载地址:
https://github.com/dotnetcore/DotnetSpider
下载下来运行几个示例基本就知道如何使用了,示例在DotnetSpider.Sample解决方案下面,下面我们看一下例子中的CnblogsSpider。
CnblogsSpider在被调用时会执行Initialize()方法做一些爬虫配置的初始化,如下:
protected override async Task Initialize()
{
NewGuidId();
Depth = 3;
AddDataFlow(new ListNewsParser()).AddDataFlow(new ConsoleStorage());
await AddRequests("https://news.cnblogs.com/n/page/1/");
}
其中最重要的包含三部分,ListNewsParser,ConsoleStorage和最后的AddRequests。ListNewsParser是定义爬虫如何解析我们要爬取的网页以及返回怎样的一个结果,ConsoleStorage是定义爬虫如何保存我们爬取的结果,AddRequests则是指定爬虫爬取的目标url。下面我们分别看一下ListNewsParser和ConsoleStorage的代码。
class ListNewsParser : DataParser
{
public ListNewsParser()
{
Required = DataParserHelper.CheckIfRequiredByRegex("news\\.cnblogs\\.com/n/page");
// 如果你还想翻页则可以去掉注释
//FollowRequestQuerier =
// BuildFollowRequestQuerier(DataParserHelper.QueryFollowRequestsByXPath(".//div[@class='pager']"));
}
protected override Task<DataFlowResult> Parse(DataFlowContext context)
{
var news = context.Selectable.XPath(".//div[@class='news_block']").Nodes();
foreach (var item in news)
{
var title = item.Select(Selectors.XPath(".//h2[@class='news_entry']"))
.GetValue(ValueOption.InnerText);
var url = item.Select(Selectors.XPath(".//h2[@class='news_entry']/a/@href")).GetValue();
var summary = item.Select(Selectors.XPath(".//div[@class='entry_summary']"))
.GetValue(ValueOption.InnerText);
var views = item.Select(Selectors.XPath(".//span[@class='view']")).GetValue(ValueOption.InnerText)
.Replace(" 人浏览", "");
var request = CreateFromRequest(context.Response.Request, url);
request.AddProperty("title", title);
request.AddProperty("summary", summary);
request.AddProperty("views", views);
context.AddExtraRequests(request);
}
return Task.FromResult(DataFlowResult.Success);
}
}
其中关键方法是Parse(),在Parse()中通过Xpath取得目标网页上的信息,并最终存入字典title,summary,views。如果我们要写爬虫可以新建一个类继承自DataParser类,然后实现方法Parse,在Parse()中通过Xpath或其它的一些途径获取想要的信息并封装成我们想要的结果。
/// <summary>
/// 控制台打印解析结果(所有解析结果)
/// </summary>
public class ConsoleStorage : StorageBase
{
/// <summary>
/// 根据配置返回存储器
/// </summary>
/// <param name="options">配置</param>
/// <returns></returns>
public static ConsoleStorage CreateFromOptions(SpiderOptions options)
{
return new ConsoleStorage();
}
protected override Task<DataFlowResult> Store(DataFlowContext context)
{
var items = context.GetData();
Console.WriteLine(JsonConvert.SerializeObject(items));
return Task.FromResult(DataFlowResult.Success);
}
}
ConsoleStorage 是定义如何输出保存我们在DataParser中获取的数据,关键方法是Store(),我们可以看到里面是将结果对象序列化为Json字符串并在控制台中输出了。目前DotnetSpider中已经封装了一些常用的保存类,如控制台输出,输出保存JSON文件,保存到Mysql/Sqlserver等,可以在DotnetSpider解决方案下DataFlow-Storage文件夹下看到。同样地我们可以自定义自己的存储方式,只需要继承StorageBase类并实现Store(),在方法中定义存储在文件,数据库或者缓存中。
西刺代理IP网页分析爬取
西刺代理官网地址https://www.xicidaili.com/nn/
我们先通过F12查看我们要获取的代理IP和端口的元素位置
西刺代理IP
页面是有分页的,访问时在url后面评上页数就是访问指定的页数。所有的数据都在页面中一个id='ip_list’的table中,这样我们只需要爬取这个table并取得其中每一行的数据即可。下面开始编写DataParser类。
class MyParser : DataParser<IPDataEntry>
{
protected override Task<DataFlowResult> Parse(DataFlowContext context)
{
var typeName = typeof(IPDataEntry).FullName;
//首先取得table元素
var table = context.Selectable.Select(Selectors.XPath(".//table[@id='ip_list']")).GetValue();
//取得table行数
var rowcount = System.Text.RegularExpressions.Regex.Matches(table, @"</tr>").Count;
//遍历table每一行,取得其中的ip和port
for (int i = 2; i <= rowcount; i++)
{
//IP和端口分别在第二列和第三列
var IP = context.Selectable.Select(Selectors.XPath($".//table[@id='ip_list']//tr[{i}]/td[2]/text()")).GetValue();
var PORT = context.Selectable.Select(Selectors.XPath($".//table[@id='ip_list']//tr[{i}]/td[3]/text()")).GetValue();
ProxyDataItem proxy = new ProxyDataItem()
{
IpAddress = IP,
Port = PORT
};
//验证抓取到的ip和端口是否可用,可用则加入到结果中
if (ValidateHelper.ValidateProxy(proxy))
{
context.AddData(typeName + (i - 1),
new IPDataEntry
{
IP = IP,
PORT = PORT
});
//Console.WriteLine($"{IP}:{PORT}");
}
}
return Task.FromResult(DataFlowResult.Success);
}
}
在上面的MyParser类中我们对table中的IP和端口进行了爬取,并且简单验证了代理IP的可用信,将可用的IP构建成了IPDataEntry就存放在结果中。下面我们需要编写Storage类用来保存结果,由于代理IP存在不稳定性,经常失效,所以我们不做持久化存储,就存在Redis缓存中,对Redis缓存及使用不太清楚的可以看我的上一篇博文C# Redis使用及帮助类
下面是RedisStorage类:
public class RedisStorage : StorageBase
{
/// <summary>
/// 根据配置返回存储器
/// </summary>
/// <param name="options"></param>
/// <returns></returns>
public static RedisStorage CreateFromOptions(SpiderOptions options)
{
return new RedisStorage();
}
protected override Task<DataFlowResult> Store(DataFlowContext context)
{
RedisHelper redisHelper = new RedisHelper("localhost");
var items = context.GetData();
var list = new List<IPDataEntry>();
foreach (var item in items)
{
list.Add(item.Value as IPDataEntry);
}
//先将缓存中的旧数据情掉
redisHelper.delKey("IPDATA");
//将新抓取的数据存入缓存
redisHelper.addList<IPDataEntry>("IPDATA",list);
return Task.FromResult(DataFlowResult.Success);
}
}
最后我们在DotnetSpider.Sample解决方案的Program.cs中调用我们编写的IPDataSpider并运行。下面我们看下爬虫运行完写入缓存中的代理IP
Copyright © 广州京杭网络科技有限公司 2005-2024 版权所有 粤ICP备16019765号
广州京杭网络科技有限公司 版权所有