隐藏

C#攻克反爬虫之代理IP爬取

发布:2022/9/26 15:54:28作者:管理员 来源:本站 浏览次数:790

目前很多大型网站在反爬虫时采取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