陈程的技术博客

  • 关于作者
全栈软件工程师
一个专注于技术研究创新的程序员
  1. 首页
  2. .NET
  3. 正文

使用C#+Jumony开发网络爬虫并对数据做相关分析

2016年9月6日 710点热度 0人点赞 0条评论

使用C# + Jumony开发网络爬虫

现在开发网络爬虫大部分都用python,发现用C#来写爬虫太少,我自己尝试用C#写了一个定向爬虫,在这里我向大家介绍它,目前已经把它开源到github上了,想要深入了解的朋友直戳下面的链接:

[https://github.com/zuiyuewentian/Reptile.git]

首先介绍下该爬虫的设计模型:

1.定义相关的网站入口,爬取内容页,爬取规则

2.使用多线程,从不同的网站入口开始爬取网站的URL链接

3.获取URL链接加入到待爬取链接的集合中

4.从待爬取的URL链接爬取有效URL加入到有效URL链接的集合中

5.同步读取有效URL链接中想要获取内容。

6.直到待爬取的URL链接爬取完毕,爬取URL链接的功能爬取完毕,直到有效URL链接的内容爬取完毕,同步读取URL获取内容结束,程序结束。

大致如图:

目前该爬虫的模型尚未加入元素:

1.加入代理IP功能,部分网站会对IP跟踪封杀爬虫。

2.未对相对链接URL做处理,目前爬虫只是爬取了a标签的href链接。

3.未对URL链接地址做压缩处理,节省了时间,但加大了内存的消耗。

4.未处理中断保存功能。

 

以上四点需要之后在此之上优化完善。

 

在写爬虫之前需要先了解下将会使用的jumony —— 一个HTML引擎,我在其中使用jumony作为解析HTML的工具,快速获取网页内容。

jumony是一个开源项目,相关资料可以去github上获取。使用的时候可以使用vs的NuGet管理搜索jumony安装到项目里即可使用,如图。

我在项目里简单的做了下封装来使用。

部分代码如下:

使用之前引用代码:

JumonyHelper内容:

using Ivony.Html;
 private Ivony.Html.Parser.JumonyParser jumony;

        public IHtmlDocument doc;

        public JumonyHelper(string url)
        {
            jumony = new Ivony.Html.Parser.JumonyParser();
            WebClient web = new WebClient();
            web.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36");
            byte[] downData;
            try
            {
                downData = web.DownloadData(url);
            }
            catch
            {
                return;
            }
            string html = Encoding.UTF8.GetString(downData);
            doc = new Ivony.Html.Parser.JumonyParser().Parse(html);
            // doc = new Ivony.Html.Parser.JumonyParser().LoadDocument(url);
        }

        /// <summary>
        /// 获取同一个Class的内容
        /// </summary>
        /// <param name="cssKey">css样式关键字</param>
        /// <returns></returns>
        public List<string> GetClassTxt(string cssKey)
        {
            try
            {
                List<string> returnValue = new List<string>();
                if (!doc.Exists(cssKey))
                    return null;
                var values = doc.Find(cssKey);
                if (values == null)
                    return null;
                foreach (var item in values)
                {
                    returnValue.Add(item.InnerText());
                }
                return returnValue;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        /// <summary>
        /// 获取元素Id的内容
        /// </summary>
        /// <param name="cssKeyId">css样式id</param>
        /// <returns></returns>
        public string GetElementByIdTxt(string cssKeyId)
        {
            try
            {
                if (!doc.Exists(cssKeyId))
                    return null;
                var values = doc.GetElementById(cssKeyId);
                if (values == null)
                    return null;
                return values.InnerText();
            }
            catch (Exception ex)
            {
                return null;
            }
        }

这里,我放弃了使用jumony的加载url的方法LoadDocument,使用WebClient的DownloadData方法。

原因是jumony的方法里似乎没有找到处理网页编码的方法,这让我略显无奈,而且jumony相关学习资料也不多,乱码问题没办法解决。

这里实例化后主要提供出doc的节点自己调用,也可以调用下面获取id或者获取class的方法。

 

相关jumony的css选择器功能可以去jumony社区查询,在本项目中使用方法,也是使用了基本的css选择器方法,可以作为相关参考。

 

该项目以爬取伯乐在线(http://www.jobbole.com/)用户信息为实例,并对爬取的用户做数据分析。

1.配置相关参数

BASEURL = "jobbole.com" 所属网站,,URL链接不能超出该网站的范畴

NEEDURL = "jobbole.com/members"最终需要匹配的URL规则关键字

ThreadCount = 1;开启线程数量

BASEURLDEPTH = 4; 爬虫的深度要求不能大于BASEURLDEPTH

NEEDURLDEPTH = 3;所需要的URL深度要求=NEEDURLDEPTH

 

2.爬取需要的页面URL链接功能部分代码:

/// <summary>
      /// 开始爬取
      /// </summary>
      public void ReptileStart()
      {
          ThreadList = new Thread[ThreadCount];
          for (int thread = 0; thread < ThreadCount; thread++)
          {
              ThreadList[thread] = new Thread(new ParameterizedThreadStart(Reptile));
              ThreadList[thread].Name = "myThread" + thread;
              ThreadList[thread].IsBackground = true;
              ThreadList[thread].Start(PageStartURL[thread]);
          }
      }

      public void Reptile(object url)
      {
          //初始网址抓取
          string repUrl = url.ToString();
          ReptileURL(repUrl);
          lock (ReptileObj)
          {
              ReptileUrl.Add(repUrl);
          }
          lock (VisitedObj)
          {
              VisitedUrl.Add(repUrl);
          }
          //循环抓取
          int startIndex = 0;
          while (true)
          {
              int reptileUrlCount = ReptileUrl.Count();
              if (reptileUrlCount <= 0)
                  break;
              if (reptileUrlCount <= VisitedUrl.Count())
              {
                  IsEndReptile = true;
                  break;
              }
              for (int i = startIndex; i < reptileUrlCount; i++)
              {
                  string rurl = ReptileUrl[i];
                  // 去除重复爬网页
                  if (VisitedUrl.Contains(rurl))
                      continue;
                  ReptileURL(rurl);
                  lock (VisitedObj)
                  {
                      VisitedUrl.Add(rurl);
                  }
              }
              startIndex = reptileUrlCount;
          }
      }

      /// <summary>
      /// NeedUrl ID
      /// </summary>
      int MemberIndex = 1;
      /// <summary>
      /// 爬取网页中a标签的链接地址
      /// </summary>
      /// <param name="url"></param>
      public void ReptileURL(string url)
      {
          if (!IsBaseUrl(url))
              return;
          JumonyHelper jumonyHelper = new JumonyHelper(url);
          if (jumonyHelper.doc == null)
              return;
          var urlList = jumonyHelper.doc.Find("a[href]");
          if (urlList == null)
              return;
          foreach (var item in urlList)
          {
              string itemUrl = item.Attribute("href").Value();
              if (!ReptileUrl.Contains(itemUrl))
              {
                  lock (ReptileObj)
                  {
                      ReptileUrl.Add(itemUrl);
                  }
                  if (IsNeedUrl(itemUrl))
                  {
                      itemUrl = itemUrl.TrimEnd('/');
                      if (!NeedUrlList.Contains(itemUrl))
                      {
                          lock (NeedObj)
                          {
                              NeedUrlList.Add(itemUrl);
                              insertURLXML(MemberIndex, itemUrl);
                              MemberIndex++;
                          }
                      }
                  }
              }
          }
      }

3.爬取内容页部分代码

int UserIndex = 1;
      public void LoadUser(string userUrl)
      {
          MemberEntity member = new MemberEntity();
          member.url = userUrl;
          try
          {
              JumonyHelper jumonyHelper = new JumonyHelper(userUrl);
              var nameValue = jumonyHelper.doc.FindFirst(".profile-title");
              string name = nameValue.InnerText();
              member.name = name;
              var profiles = jumonyHelper.doc.Find(".profile-points > li");
              foreach (var item in profiles)
              {
                  string value = item.InnerText();
                  if (value.Contains("\r\n"))
                  {
                      value = value.Replace("\r\n", "|");
                      string[] pros = value.Split('|');
                      if (pros[1] == "声望")
                      {
                          member.reputation = pros[0];
                      }
                      else if (pros[1] == "勋章")
                      {
                          member.medal = pros[0];
                      }
                      else if (pros[1] == "积分")
                      {
                          member.point = pros[0];
                      }
                  }
              }
              var profile = jumonyHelper.doc.FindFirst(".profile-bio");
              member.profile = profile.InnerText();
              var follows = jumonyHelper.doc.Find(".profile-follow");
              foreach (var item in follows)
              {
                  string value = item.InnerText();
                  if (!String.IsNullOrEmpty(value))
                  {
                      if (value.Contains("关注"))
                      {
                          string following = value.Split('(')[1].Split(')')[0];
                          member.following = following;
                      }
                      else if (value.Contains("粉丝"))
                      {
                          string follower = value.Split('(')[1].Split(')')[0];
                          member.follower = follower;
                      }
                  }
              }
              var infos = jumonyHelper.doc.Find(".member-info > span");
              foreach (var item in infos)
              {
                  string value = item.InnerText();
                  if (!String.IsNullOrEmpty(value))
                  {
                      if (value.Contains("注册"))
                      {
                          string date = value.Split(':')[1];
                          member.Date = date;
                      }
                      else if (value.Contains("城市"))
                      {
                          string city = value.Split(':')[1];
                          member.city = city;
                      }
                  }
              }

              var image = jumonyHelper.doc.FindFirst(".profile-img > a > img");
              string imageUrl = image.Attribute("src").Value();
              member.image = imageUrl;

              if (jumonyHelper.doc.Exists("i[title]"))
              {
                  var sexHtml = jumonyHelper.doc.FindFirst("i[title]");
                  string sex = sexHtml.Attribute("title").Value();
                  member.sex = sex;
              }
              else
              {
                  member.sex = "";
              }
              member.Id = UserIndex;
              lock (ReptileObj)
              {
                  memberHelper.AddMember(member);
                  insertXML(member);
                  UserIndex++;
              }
          }
          catch (Exception ex)
          {
              WriteTxt.WriteNewTxt("ERRORLOG", "++++错误数据+++" + ex.Message);
          }
      }

了解详细内容请查看github项目。

 

爬虫项目到现在为止大致介绍完毕,请大家需要注意一下几点:

1.爬虫对目标网站会造成流量攻击,甚至会导致目标网站奔溃,请大家合理利用爬虫,可以设置爬取时间再深夜时段或者,爬取一段暂停一段时间。

2.请选择合适的入口,以连接本站页面多为好。

3.开启多线程爬取时应该以使用电脑的效率最高为目的,而不应该随意开启太多线程。

 

这边统计对伯乐在线用户爬取共计6000多人,活跃用户大约1000多人,以下是用户的具体情况图

1.2015年和2016年伯乐在线注册情况曲线图

此图明显可以看出2016年注册人数比2015年人数要多很多,影响力日益变大,可以预计2~3年左右伯乐将会成为一个颇有影响力程序员社区。

2.伯乐在线男女比例图

伯乐的注册没有设置性别必填,导致统计男女数量里有大量的未知数据,基本可以判断,非活跃用户肯定大部分处于未知用户中

3.伯乐在线男女比例图

排除未知数据干扰,大约可以判断男生大约占用户总数的80%以上

4.伯乐在线积分榜

看看这些人的具体是那些?

5.伯乐在线声望榜

6.伯乐在线粉丝榜

7.伯乐在线用户地域分布图

因为伯乐地域字段可以自定义,所以这里地域过滤了无关数据,取中国地市为标准,其他一律归入未知。

在这里我们可以看出伯乐的用户大部分分布在北京,上海,广州,深圳

标签: winform 爬虫
最后更新:2021年4月1日

博主

全栈工程师,侧重项目技术解决方案规划和开发

打赏 点赞
< 上一篇
下一篇 >

文章评论

取消回复

分类
  • .NET (65)
  • docker (3)
  • linux (12)
  • python (20)
  • web (14)
  • 小程序 (4)
  • 数据库 (2)
  • 未分类 (4)
  • 杂七杂八 (10)
标签聚合
python centos linux DevExpress C# nginx js winform
最新 热点 随机
最新 热点 随机
.NET开发手册标准参考 招募兼职前端开发 Centos安装dotnet6环境 VS上切换分支,vs编译运行出现bug,A fatal error was encountered彻底解决方案 用C#封装一个线程安全的缓存器,达到目标定时定量更新入库 C#通过特性的方式去校验指定数据是否为空
nginx部署SSL证书和二级域名 小程序-上传图片功能 Centos安装dotnet6环境 DreamSkin自定义美化控件-带智能下拉提示的TextBox控件 winform 微秒级别的定时器 dynamic的操作

COPYRIGHT © 2021 陈程的技术博客. ALL RIGHTS RESERVED.

THEME KRATOS MADE BY VTROIS