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

目前很多大型网站在反爬虫时采取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
西刺代理IP
以上就是本篇的所有内容,有需要代码的可以前往下载

发布了12 篇原创文章 · 获赞 27 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/Leaderxin/article/details/102764234