利用C#实现网络爬虫

网络爬虫在信息检索与处理中有很大的作用,是收集网络信息的重要工具。

接下来就介绍一下爬虫的简单实现。

爬虫的工作流程如下

爬虫自指定的URL地址开始下载网络资源,直到该地址和所有子地址的指定资源都下载完毕为止。

下面开始逐步分析爬虫的实现。

1. 待下载集合与已下载集合

为了保存需要下载的URL,同时防止重复下载,我们需要分别用了两个集合来存放将要下载的URL和已经下载的URL。

因为在保存URL的同时需要保存与URL相关的一些其他信息,如深度,所以这里我采用了Dictionary来存放这些URL。

具体类型是Dictionary<string, int> 其中string是Url字符串,int是该Url相对于基URL的深度。

每次开始时都检查未下载的集合,如果已经为空,说明已经下载完毕;如果还有URL,那么就取出第一个URL加入到已下载的集合中,并且下载这个URL的资源。

2. HTTP请求和响应

C#已经有封装好的HTTP请求和响应的类HttpWebRequest和HttpWebResponse,所以实现起来方便不少。

为了提高下载的效率,我们可以用多个请求并发的方式同时下载多个URL的资源,一种简单的做法是采用异步请求的方法。

控制并发的数量可以用如下方法实现

private void DispatchWork()
{
 if (_stop) //判断是否中止下载
 {
  return;
 }
 for (int i = 0; i < _reqCount; i++)
 {
  if (!_reqsBusy[i]) //判断此编号的工作实例是否空闲
  {
   RequestResource(i); //让此工作实例请求资源
  }
 }
}

由于没有显式开新线程,所以用一个工作实例来表示一个逻辑工作线程

private bool[] _reqsBusy = null; //每个元素代表一个工作实例是否正在工作
private int _reqCount = 4; //工作实例的数量

每次一个工作实例完成工作,相应的_reqsBusy就设为false,并调用DispatchWork,那么DispatchWork就能给空闲的实例分配新任务了。

接下来是发送请求

private void RequestResource(int index)
 {
  int depth;
  string url = "";
  try
  {
   lock (_locker)
   {
    if (_urlsUnload.Count <= 0) //判断是否还有未下载的URL
    {
     _workingSignals.FinishWorking(index); //设置工作实例的状态为Finished
     return;
    }
    _reqsBusy[index] = true;
    _workingSignals.StartWorking(index); //设置工作状态为Working
    depth = _urlsUnload.First().Value; //取出第一个未下载的URL
    url = _urlsUnload.First().Key;
    _urlsLoaded.Add(url, depth); //把该URL加入到已下载里
    _urlsUnload.Remove(url); //把该URL从未下载中移除
   }

   HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
   req.Method = _method; //请求方法
   req.Accept = _accept; //接受的内容
   req.UserAgent = _userAgent; //用户代理
   RequestState rs = new RequestState(req, url, depth, index); //回调方法的参数
   var result = req.BeginGetResponse(new AsyncCallback(ReceivedResource), rs); //异步请求
   ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, //注册超时处理方法
     TimeoutCallback, rs, _maxTime, true);
  }
  catch (WebException we)
  {
   MessageBox.Show("RequestResource " + we.Message + url + we.Status);
  }
 }

第7行为了保证多个任务并发时的同步,加上了互斥锁。_locker是一个Object类型的成员变量。

第9行判断未下载集合是否为空,如果为空就把当前工作实例状态设为Finished;如果非空则设为Working并取出一个URL开始下载。当所有工作实例都为Finished的时候,说明下载已经完成。由于每次下载完一个URL后都调用DispatchWork,所以可能激活其他的Finished工作实例重新开始工作。

第26行的请求的额外信息在异步请求的回调方法作为参数传入,之后还会提到。

第27行开始异步请求,这里需要传入一个回调方法作为响应请求时的处理,同时传入回调方法的参数。

第28行给该异步请求注册一个超时处理方法TimeoutCallback,最大等待时间是_maxTime,且只处理一次超时,并传入请求的额外信息作为回调方法的参数。

RequestState的定义是

class RequestState
{
 private const int BUFFER_SIZE = 131072; //接收数据包的空间大小
 private byte[] _data = new byte[BUFFER_SIZE]; //接收数据包的buffer
 private StringBuilder _sb = new StringBuilder(); //存放所有接收到的字符

 public HttpWebRequest Req { get; private set; } //请求
 public string Url { get; private set; } //请求的URL
 public int Depth { get; private set; } //此次请求的相对深度
 public int Index { get; private set; } //工作实例的编号
 public Stream ResStream { get; set; } //接收数据流
 public StringBuilder Html
 {
  get
  {
   return _sb;
  }
 }

 public byte[] Data
 {
  get
  {
   return _data;
  }
 }

 public int BufferSize
 {
  get
  {
   return BUFFER_SIZE;
  }
 }

 public RequestState(HttpWebRequest req, string url, int depth, int index)
 {
  Req = req;
  Url = url;
  Depth = depth;
  Index = index;
 }
}

TimeoutCallback的定义是

private void TimeoutCallback(object state, bool timedOut)
{
 if (timedOut) //判断是否是超时
 {
  RequestState rs = state as RequestState;
  if (rs != null)
  {
   rs.Req.Abort(); //撤销请求
  }
  _reqsBusy[rs.Index] = false; //重置工作状态
  DispatchWork(); //分配新任务
 }
}

接下来就是要处理请求的响应了

private void ReceivedResource(IAsyncResult ar)
{
 RequestState rs = (RequestState)ar.AsyncState; //得到请求时传入的参数
 HttpWebRequest req = rs.Req;
 string url = rs.Url;
 try
 {
  HttpWebResponse res = (HttpWebResponse)req.EndGetResponse(ar); //获取响应
  if (_stop) //判断是否中止下载
  {
   res.Close();
   req.Abort();
   return;
  }
  if (res != null && res.StatusCode == HttpStatusCode.OK) //判断是否成功获取响应
  {
   Stream resStream = res.GetResponseStream(); //得到资源流
   rs.ResStream = resStream;
   var result = resStream.BeginRead(rs.Data, 0, rs.BufferSize, //异步请求读取数据
    new AsyncCallback(ReceivedData), rs);
  }
  else //响应失败
  {
   res.Close();
   rs.Req.Abort();
   _reqsBusy[rs.Index] = false; //重置工作状态
   DispatchWork(); //分配新任务
  }
 }
 catch (WebException we)
 {
  MessageBox.Show("ReceivedResource " + we.Message + url + we.Status);
 }
}

第19行这里采用了异步的方法来读数据流是因为我们之前采用了异步的方式请求,不然的话不能够正常的接收数据。

该异步读取的方式是按包来读取的,所以一旦接收到一个包就会调用传入的回调方法ReceivedData,然后在该方法中处理收到的数据。

该方法同时传入了接收数据的空间rs.Data和空间的大小rs.BufferSize。

接下来是接收数据和处理

private void ReceivedData(IAsyncResult ar)
{
 RequestState rs = (RequestState)ar.AsyncState; //获取参数
 HttpWebRequest req = rs.Req;
 Stream resStream = rs.ResStream;
 string url = rs.Url;
 int depth = rs.Depth;
 string html = null;
 int index = rs.Index;
 int read = 0;

 try
 {
  read = resStream.EndRead(ar); //获得数据读取结果
  if (_stop)//判断是否中止下载
  {
   rs.ResStream.Close();
   req.Abort();
   return;
  }
  if (read > 0)
  {
   MemoryStream ms = new MemoryStream(rs.Data, 0, read); //利用获得的数据创建内存流
   StreamReader reader = new StreamReader(ms, _encoding);
   string str = reader.ReadToEnd(); //读取所有字符
   rs.Html.Append(str); // 添加到之前的末尾
   var result = resStream.BeginRead(rs.Data, 0, rs.BufferSize, //再次异步请求读取数据
    new AsyncCallback(ReceivedData), rs);
   return;
  }
  html = rs.Html.ToString();
  SaveContents(html, url); //保存到本地
  string[] links = GetLinks(html); //获取页面中的链接
  AddUrls(links, depth + 1); //过滤链接并添加到未下载集合中

  _reqsBusy[index] = false; //重置工作状态
  DispatchWork(); //分配新任务
 }
 catch (WebException we)
 {
  MessageBox.Show("ReceivedData Web " + we.Message + url + we.Status);
 }
}

第14行获得了读取的数据大小read,如果read>0说明数据可能还没有读完,所以在27行继续请求读下一个数据包;

如果read<=0说明所有数据已经接收完毕,这时rs.Html中存放了完整的HTML数据,就可以进行下一步的处理了。

第26行把这一次得到的字符串拼接在之前保存的字符串的后面,最后就能得到完整的HTML字符串。

然后说一下判断所有任务完成的处理

private void StartDownload()
{
 _checkTimer = new Timer(new TimerCallback(CheckFinish), null, 0, 300);
 DispatchWork();
}

private void CheckFinish(object param)
{
 if (_workingSignals.IsFinished()) //检查是否所有工作实例都为Finished
 {
  _checkTimer.Dispose(); //停止定时器
  _checkTimer = null;
  if (DownloadFinish != null && _ui != null) //判断是否注册了完成事件
  {
   _ui.Dispatcher.Invoke(DownloadFinish, _index); //调用事件
  }
 }
}

第3行创建了一个定时器,每过300ms调用一次CheckFinish来判断是否完成任务。
第15行提供了一个完成任务时的事件,可以给客户程序注册。_index里存放了当前下载URL的个数。

该事件的定义是

public delegate void DownloadFinishHandler(int count);

/// <summary>
/// 全部链接下载分析完毕后触发
/// </summary>
public event DownloadFinishHandler DownloadFinish = null;

3. 保存页面文件

这一部分可简单可复杂,如果只要简单地把HTML代码全部保存下来的话,直接存文件就行了。

private void SaveContents(string html, string url)
{
 if (string.IsNullOrEmpty(html)) //判断html字符串是否有效
 {
  return;
 }
 string path = string.Format("{0}\\{1}.txt", _path, _index++); //生成文件名

 try
 {
  using (StreamWriter fs = new StreamWriter(path))
  {
   fs.Write(html); //写文件
  }
 }
 catch (IOException ioe)
 {
  MessageBox.Show("SaveContents IO" + ioe.Message + " path=" + path);
 }

 if (ContentsSaved != null)
 {
  _ui.Dispatcher.Invoke(ContentsSaved, path, url); //调用保存文件事件
 }
}

第23行这里又出现了一个事件,是保存文件之后触发的,客户程序可以之前进行注册。

public delegate void ContentsSavedHandler(string path, string url);

/// <summary>
/// 文件被保存到本地后触发
/// </summary>
public event ContentsSavedHandler ContentsSaved = null;

 4. 提取页面链接

提取链接用正则表达式就能搞定了,不懂的可以上网搜。

下面的字符串就能匹配到页面中的链接

http://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?

详细见代码

private string[] GetLinks(string html)
{
 const string pattern = @"http://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?";
 Regex r = new Regex(pattern, RegexOptions.IgnoreCase); //新建正则模式
 MatchCollection m = r.Matches(html); //获得匹配结果
 string[] links = new string[m.Count]; 

 for (int i = 0; i < m.Count; i++)
 {
  links[i] = m[i].ToString(); //提取出结果
 }
 return links;
}

5. 链接的过滤

不是所有的链接我们都需要下载,所以通过过滤,去掉我们不需要的链接

这些链接一般有:

1)、已经下载的链接
2)、深度过大的链接
3)、其他的不需要的资源,如图片、CSS等

//判断链接是否已经下载或者已经处于未下载集合中
private bool UrlExists(string url)
{
 bool result = _urlsUnload.ContainsKey(url);
 result |= _urlsLoaded.ContainsKey(url);
 return result;
}

private bool UrlAvailable(string url)
{
 if (UrlExists(url))
 {
  return false; //已经存在
 }
 if (url.Contains(".jpg") || url.Contains(".gif")
  || url.Contains(".png") || url.Contains(".css")
  || url.Contains(".js"))
 {
  return false; //去掉一些图片之类的资源
 }
 return true;
}

private void AddUrls(string[] urls, int depth)
{
 if (depth >= _maxDepth)
 {
  return; //深度过大
 }
 foreach (string url in urls)
 {
  string cleanUrl = url.Trim(); //去掉前后空格
  cleanUrl = cleanUrl.TrimEnd('/'); //统一去掉最后面的'/'
  if (UrlAvailable(cleanUrl))
  {
   if (cleanUrl.Contains(_baseUrl))
   {
    _urlsUnload.Add(cleanUrl, depth); //是内链,直接加入未下载集合
   }
   else
   {
    // 外链处理
   }
  }
 }
}

第34行的_baseUrl是爬取的基地址,如http://news.sina.com.cn/,将会保存为news.sina.com.cn,当一个URL包含此字符串时,说明是该基地址下的链接;否则为外链。

_baseUrl的处理如下,_rootUrl是第一个要下载的URL

/// <summary>
/// 下载根Url
/// </summary>
public string RootUrl
{
 get
 {
  return _rootUrl;
 }
 set
 {
  if (!value.Contains("http://"))
  {
   _rootUrl = "http://" + value;
  }
  else
  {
   _rootUrl = value;
  }
  _baseUrl = _rootUrl.Replace("www.", ""); //全站的话去掉www
  _baseUrl = _baseUrl.Replace("http://", ""); //去掉协议名
  _baseUrl = _baseUrl.TrimEnd('/'); //去掉末尾的'/'
 }
}

至此,基本的爬虫功能实现就介绍完了。

最后附上源代码和DEMO程序,爬虫的源代码在Spider.cs中,DEMO是一个WPF的程序,Test里是一个控制台的单线程版版本。

下载地址:C#实现网络爬虫DEMO

以上就是C#实现网络爬虫的全部过程,代码解析很详细,希望对大家的学习有所帮助。

(0)

相关推荐

  • C#制作多线程处理强化版网络爬虫

    上次做了一个帮公司妹子做了爬虫,不是很精致,这次公司项目里要用到,于是有做了一番修改,功能添加了网址图片采集,下载,线程处理界面网址图片下载等. 说说思路:首相获取初始网址的所有内容 在初始网址采集图片 去初始网址采集链接 把采集到的链接放入队列 继续采集图片,然后继续采集链接,无限循环 还是上图片大家看一下, 处理网页内容抓取跟网页网址爬取都做了改进,下面还是大家来看看代码,有不足之处,还请之处! 网页内容抓取HtmlCodeRequest, 网页网址爬取GetHttpLinks,用正则去筛选

  • 基于C#实现网络爬虫 C#抓取网页Html源码

    最近刚完成一个简单的网络爬虫,开始的时候很迷茫,不知道如何入手,后来发现了很多的资料,不过真正能达到我需要,有用的资料--代码很难找.所以我想发这篇文章让一些要做这个功能的朋友少走一些弯路. 首先是抓取Html源码,并选择<ul class="post_list">  </ul>节点的href:要添加using System.IO;using System.Net; private void Search(string url) { string rl; Web

  • 利用C#实现最基本的小说爬虫示例代码

    前言 作为一个新手,最近在学习C#,自己折腾弄了个简单的小说爬虫,实现了把小说内容爬下来写入txt,还只能爬指定网站. 第一次搞爬虫,涉及到了网络协议,正则表达式,弄得手忙脚乱跑起来效率还差劲,慢慢改吧.下面话不多说了,来一起看看详细的介绍吧. 爬的目标:http://www.166xs.com/xiaoshuo/83/83557/ 一.先写HttpWebRequest把网站扒下来 这里有几个坑,大概说下: 第一个就是记得弄个代理IP爬网站,第一次忘了弄代理然后ip就被封了..... 第二个就是

  • C#多线程爬虫抓取免费代理IP的示例代码

    这里用到一个HTML解析辅助类:HtmlAgilityPack,如果没有网上找一个增加到库里,这个插件有很多版本,如果你开发环境是使用VS2005就2.0的类库,VS2010就使用4.0,以此类推..........然后直接创建一个控制台应用,将我下面的代码COPY替换就可以运行,下面就来讲讲我两年前做爬虫经历,当时是给一家公司做,也是用的C#,不过当时遇到一个头痛的问题就是抓的图片有病毒,然后系统挂了几次.所以抓网站图片要注意安全,虽然我这里没涉及到图片,但是还是提醒下看文章的朋友. clas

  • 基于C#实现网页爬虫

    本文实例为大家分享了基于C#实现网页爬虫的详细代码,供大家参考,具体内容如下 HTTP请求工具类: 功能: 1.获取网页html 2.下载网络图片 using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; using System.Windows.Form

  • C#网络爬虫代码分享 C#简单的爬取工具

    公司编辑妹子需要爬取网页内容,叫我帮忙做了一简单的爬取工具 这是爬取网页内容,像是这对大家来说都是不难得,但是在这里有一些小改动,代码献上,大家参考 private string GetHttpWebRequest(string url) { HttpWebResponse result; string strHTML = string.Empty; try { Uri uri = new Uri(url); WebRequest webReq = WebRequest.Create(uri);

  • C#简单爬虫案例分享

    本文实例为大家分享了C#简单爬虫案例,供大家参考,具体内容如下 using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace ConsoleApplication1 { class Program

  • 利用C#实现网络爬虫

    网络爬虫在信息检索与处理中有很大的作用,是收集网络信息的重要工具. 接下来就介绍一下爬虫的简单实现. 爬虫的工作流程如下 爬虫自指定的URL地址开始下载网络资源,直到该地址和所有子地址的指定资源都下载完毕为止. 下面开始逐步分析爬虫的实现. 1. 待下载集合与已下载集合 为了保存需要下载的URL,同时防止重复下载,我们需要分别用了两个集合来存放将要下载的URL和已经下载的URL. 因为在保存URL的同时需要保存与URL相关的一些其他信息,如深度,所以这里我采用了Dictionary来存放这些UR

  • 详解Python3网络爬虫(二):利用urllib.urlopen向有道翻译发送数据获得翻译结果

    上一篇内容,已经学会了使用简单的语句对网页进行抓取.接下来,详细看下urlopen的两个重要参数url和data,学习如何发送数据data 一.urlopen的url参数 Agent url不仅可以是一个字符串,例如:http://www.baidu.com.url也可以是一个Request对象,这就需要我们先定义一个Request对象,然后将这个Request对象作为urlopen的参数使用,方法如下: # -*- coding: UTF-8 -*- from urllib import re

  • 利用Python网络爬虫爬取各大音乐评论的代码

    python爬虫--爬取网易云音乐评论 方1:使用selenium模块,简单粗暴.但是虽然方便但是缺点也是很明显,运行慢等等等. 方2:常规思路:直接去请求服务器 1.简易看出评论是动态加载的,一定是ajax方式. 2.通过网络抓包,可以找出评论请求的的URL 得到请求的URL 3.去查看post请求所上传的数据 显然是经过加密的,现在就需要按着网易的思路去解读加密过程,然后进行模拟加密. 4.首先去查看请求是经过那些js到达服务器的 5.设置断点:依次对所发送的内容进行观察,找到评论对应的UR

  • Java实现爬虫给App提供数据(Jsoup 网络爬虫)

    一.需求 最近基于 Material Design 重构了自己的新闻 App,数据来源是个问题. 有前人分析了知乎日报.凤凰新闻等 API,根据相应的 URL 可以获取新闻的 JSON 数据.为了锻炼写代码能力,笔者打算爬虫新闻页面,自己获取数据构建 API. 二.效果图 下图是原网站的页面 爬虫获取了数据,展示到 APP 手机端 三.爬虫思路 关于App 的实现过程可以参看这几篇文章,本文主要讲解一下如何爬虫数据. Android下录制App操作生成Gif动态图的全过程 :http://www

  • python 网络爬虫初级实现代码

    首先,我们来看一个Python抓取网页的库:urllib或urllib2. 那么urllib与urllib2有什么区别呢? 可以把urllib2当作urllib的扩增,比较明显的优势是urllib2.urlopen()可以接受Request对象作为参数,从而可以控制HTTP Request的header部. 做HTTP Request时应当尽量使用urllib2库,但是urllib.urlretrieve()函数以及urllib.quote等一系列quote和unquote功能没有被加入urll

  • 基于Python实现的百度贴吧网络爬虫实例

    本文实例讲述了基于Python实现的百度贴吧网络爬虫.分享给大家供大家参考.具体如下: 完整实例代码点击此处本站下载. 项目内容: 用Python写的百度贴吧的网络爬虫. 使用方法: 新建一个BugBaidu.py文件,然后将代码复制到里面后,双击运行. 程序功能: 将贴吧中楼主发布的内容打包txt存储到本地. 原理解释: 首先,先浏览一下某一条贴吧,点击只看楼主并点击第二页之后url发生了一点变化,变成了: http://tieba.baidu.com/p/2296712428?see_lz=

  • 以Python的Pyspider为例剖析搜索引擎的网络爬虫实现方法

    在这篇文章中,我们将分析一个网络爬虫. 网络爬虫是一个扫描网络内容并记录其有用信息的工具.它能打开一大堆网页,分析每个页面的内容以便寻找所有感兴趣的数据,并将这些数据存储在一个数据库中,然后对其他网页进行同样的操作. 如果爬虫正在分析的网页中有一些链接,那么爬虫将会根据这些链接分析更多的页面. 搜索引擎就是基于这样的原理实现的. 这篇文章中,我特别选了一个稳定的."年轻"的开源项目pyspider,它是由 binux 编码实现的. 注:据认为pyspider持续监控网络,它假定网页在一

  • Android编写简单的网络爬虫

    一.网络爬虫的基本知识 网络爬虫通过遍历互联网络,把网络中的相关网页全部抓取过来,这体现了爬的概念.爬虫如何遍历网络呢,互联网可以看做是一张大图,每个页面看做其中的一个节点,页面的连接看做是有向边.图的遍历方式分为宽度遍历和深度遍历,但是深度遍历可能会在深度上过深的遍历或者陷入黑洞.所以,大多数爬虫不采用这种形式.另一方面,爬虫在按照宽度优先遍历的方式时候,会给待遍历的网页赋予一定优先级,这种叫做带偏好的遍历. 实际的爬虫是从一系列的种子链接开始.种子链接是起始节点,种子页面的超链接指向的页面是

  • hadoop中实现java网络爬虫(示例讲解)

    这一篇网络爬虫的实现就要联系上大数据了.在前两篇java实现网络爬虫和heritrix实现网络爬虫的基础上,这一次是要完整的做一次数据的收集.数据上传.数据分析.数据结果读取.数据可视化. 需要用到 Cygwin:一个在windows平台上运行的类UNIX模拟环境,直接网上搜索下载,并且安装: Hadoop:配置Hadoop环境,实现了一个分布式文件系统(Hadoop Distributed File System),简称HDFS,用来将收集的数据直接上传保存到HDFS,然后用MapReduce

  • Python网络爬虫与信息提取(实例讲解)

    课程体系结构: 1.Requests框架:自动爬取HTML页面与自动网络请求提交 2.robots.txt:网络爬虫排除标准 3.BeautifulSoup框架:解析HTML页面 4.Re框架:正则框架,提取页面关键信息 5.Scrapy框架:网络爬虫原理介绍,专业爬虫框架介绍 理念:The Website is the API ... Python语言常用的IDE工具 文本工具类IDE: IDLE.Notepad++.Sublime Text.Vim & Emacs.Atom.Komodo E

随机推荐