用C跑爬虫

news/2024/7/19 12:39:26 标签: 爬虫, c语言, java

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

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

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

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

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

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

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

  1. 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就能给空闲的实例分配新任务了。

接下来是发送请求 每次一个工作实例完成工作,相应的_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);
  }
 }

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;
 }
}

http://www.niftyadmin.cn/n/157435.html

相关文章

letcode 2.两数相加(官方题解笔记)

题目描述 给你两个 非空 的链表&#xff0c;表示两个非负的整数。 它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 除数字 0 之外&#xff0c;这两个数都不会以 0 开头。 1…

2018年国赛高教杯数学建模A题高温作业专用服装设计解题全过程文档及程序

2018年国赛高教杯数学建模 A题 高温作业专用服装设计 原题再现 在高温环境下工作时&#xff0c;人们需要穿着专用服装以避免灼伤。专用服装通常由三层织物材料构成&#xff0c;记为I、II、III层&#xff0c;其中I层与外界环境接触&#xff0c;III层与皮肤之间还存在空隙&…

WuThreat身份安全云-TVD每日漏洞情报-2023-03-14

漏洞名称:Citrix Linux 客户端不安全的临时 ICA 文件创建 漏洞级别:中危 漏洞编号:CVE-2023-24486 相关涉及:Citrix Workspace Linux应用程序 < 2302 漏洞状态:POC 参考链接:https://tvd.wuthreat.com/#/listDetail?TVD_IDTVD-2023-06102 漏洞名称:XHCMS login.php SQL注…

字符函数和字符串函数(上)-C语言详解

CSDN的各位友友们你们好,今天千泽为大家带来的是C语言中字符函数和字符串函数的详解,掌握了这些内容能够让我们更加灵活的运用字符串,接下来让我们一起走进今天的内容吧!写这篇文章需要在cplusplus.com上大量截图,十分不易!如果对您有帮助的话希望能够得到您的支持和帮助,我会持…

什么是数据和数据质量?

云质QMS原创 转载请注明来源 作者&#xff1a;王洪石 1. 什么是数据&#xff1f; 数据(data)&#xff1a;是事实或观察的结果&#xff0c;是对客观事物的逻辑归纳&#xff0c;是用于表示客观事物的未经加工的的原始素材。数据可以是连续的值&#xff0c;比如声音、图像&#x…

区块链基本原理

区块链的起源 创始者介绍 姓名&#xff1a;中本聪&#xff08;英语&#xff1a;SatoshiNakamoto&#xff09;&#xff0c;自称日裔美国人&#xff0c;日本媒体常译为中本哲史&#xff0c;此名是比特币协议及其相关软件Bitcoin-Qt的创造者&#xff0c;但真实身份未知。 中本聪于…

“手撕“ BootStrap 方法

文章目录Part.I IntroductionChap.I bootstrap 方法简介Chap.II 预备知识Part.II 非参数 bootstrap 方法Chap.I 估计量标准误差的bootstrap估计Chap.II bootstrap 置信区间Chap.III bootstrap-t 法Chap.IV 一个实例Part.III 参数 bootstrap 方法Chap.I 一个实例Part.I Introduc…

2023年全国最新安全员精选真题及答案25

百分百题库提供安全员考试试题、建筑安全员考试预测题、建筑安全员ABC考试真题、安全员证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 41.&#xff08;单选题&#xff09;在可能发生人身伤害、设备或设施损坏和环境破坏的场…