使用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.伯乐在线用户地域分布图
因为伯乐地域字段可以自定义,所以这里地域过滤了无关数据,取中国地市为标准,其他一律归入未知。
在这里我们可以看出伯乐的用户大部分分布在北京,上海,广州,深圳

文章评论