大牛分享:.Net实现拉勾网爬虫

优采云 发布时间: 2020-09-04 21:12

  .Net实现拉钩网采集

  几天前,我看到一个用.NET Core编写的采集器,我有点莫名其妙的兴奋。我一直在使用Gathering来搜寻有关Lagou的招聘信息。这个傻瓜式工具等同于用HTML模板页面标记DOM。节点,然后在跟踪节点信息的同时在浏览器窗口上模拟人类的浏览行为。它有很多优点,但缺点也很明显:爬行速度慢;麻烦的数据清理和转储;仅了解过程,但不了解原理,网站更改了模板或需要抓取其他网站来再现效率,而是自己编写程序。

  然后自己实施?做吧!

  首先了解需要提取的网页结构。对于搜索结果,您需要单击控件以显示分页。别那么麻烦。检查网络,发现每次单击下一页时,都会将异步POST请求发送到一个地址:

  URL:https://www.lagou.com/jobs/positionAjax.json?px=new&needAddtionalResult=false

  其请求数据为(以.Net搜索的第二页为例):first = false&pn = 2&kd = .NET

  很明显,pn和kd分别在页码中传递并搜索关键词

  再次检查其响应消息,它在单个页面上返回所有作业信息,格式为JSON:

  

  您可以使用JavaScriptSerializer类的DeserializeObject方法将序列反向转换为字典。

  有关作业详细信息(每个作业的主页),返回html。 Html Agility Pack在该工具包之前用于解析html,但是据说AngleSharp具有更好的性能,我计划这次替换它。

  我立即想到使用Socket作为客户端程序。我首先尝试.NET Core,但发现它缺少很多库,这太麻烦了。我仍然使用.NET Framework。我很快遇到302重定向问题和Https证书问题。诸如线程阻塞,套接字更难以处理,果断地放弃它,HttpWebRequest很简单等一系列问题,但是Post请求也会发生302错误,掩盖了普通浏览器的请求标头或重定向它无法解决,尝试更改为Get方法后,发现所有问题都可以避免,而我不由得感到高兴,偶然访问得太频繁了,结果如下:

  

  这可以阻止我吗?您是如此困难,只需将整个车站关闭即可。

  我碰巧有一个尚未过期的Azure帐户,因此我可以打开一个虚拟机来玩。

  测试成功后,编写一个正式程序。我称之为入门级Lagoo 采集器。如果您使用更多它或将来遇到新问题,则必须对其进行升级。

  根据面向对象的思想,该程序就像在不同的车床上构造零件,最后将它们组装成产品,整个过程得以简化。我的基本想法是,将单个采集器实例采集和一组关联关键词(某些关键词可能无法区分,例如C#和.Net),另存为单个xml文档(也可以保存到数据库,Excel,缓存在,我更习惯于另存为xml,然后映射到Excel文档),该过程使用Log4Net记录日志。

  第一步:规定采集器材料获取方法:

  创建一个类:LagouWebCrawler,定义其构造函数和注册字段:

  class LagouWebCrawler

{

string CerPath;//网站证书本地保存地址

string XmlSavePath;//xml保存地址

string[] PositionNames;//关联关键词组

ILog LogToTxt;//Log4Net控制器

///

/// 引用拉勾职位采集器

///

/// 证书所在位置

/// xml文件写入地址/param>

/// 关联关键词组

/// Log4Net控制器

public LagouWebCrawler(string _cerPath, string _xmlSavePath,string [] _positionNames ,ILog log)

{

this.CerPath = _cerPath;

this.XmlSavePath = _xmlSavePath;

this.LogToTxt = log;

this.PositionNames = _positionNames;

}

  第2步:设计采集器的行为

  接下来,定义此采集器的行为。在采集器中,将主要功能用作其他功能的开始区域。主要函数名为CrawlerStart,它仅负责将搜索关键词组和json字符串拆分(读取(反序列化为字典))并写入最终的xml。它具有子功能,负责读取字典(数据清理)和将节点写入xml。该子函数名为JobCopyToXML,而xml及其节点的编写使用XDocument和XElement进行操作。由于必须从搜索列表中进入每个作业的主页以获取详细信息,并检查从Internet下载的数据,以清理一些会导致书写错误的控制字符,因此两个人负责网络爬网和特殊操作。字符。这两个函数调用名为GetHTMLToString和ReplaceIllegalClar的清洗方法。

  2. 1主要功能:

  main函数需要一些成员变量来存储XDocument和XElement对象,以及位置的统计信息和索引。同时,它还需要回调证书验证(我不知道这是什么,我没有时间直接从Internet学习和复制)。

  XDocument XWrite;//一组关联词搜索的所有职位放入一个XML文件中

XElement XJobs;//XDocument根节点

List IndexKey;//寄存职位索引键,用于查重。

int CountRepeat = 0;//搜索结果中的重复职位数

int CountAdd = 0;//去重后的总职位数

///

/// 爬取一组关联关键词的数据,格式清洗后存为xml文档

///

/// int[0]+int[1]=总搜索结果数;int[0]=去重后的结果数;int[1]=重复数

public int[] CrawlerStart()

{

XWrite = new XDocument();

XJobs = new XElement("Jobs");//根节点

IndexKey = new List();

foreach (string positionName in PositionNames)//挨个用词组中的关键词搜索

{

for (int i = 1; i 0)//可能关键词搜不到内容

{

XWrite.Add(XJobs);//将根节点添加进XDocument

//XmlDocument doc = new XmlDocument();

//doc.Normalize();

XWrite.Save(XmlSavePath);

LogToTxt.Info("爬取了一组关联词,添加了" + CountAdd + "个职位,文件地址:" + XmlSavePath);

}

return new int[] { CountAdd, CountRepeat };

}

catch (Exception ex)

{

LogToTxt.Error("XDocument导出到xml时失败,文件:" + XmlSavePath + ",错误信息:" + ex);

return new int[] { 0,0};

}

return new int[] { CountAdd, CountRepeat };

}

///

/// 回调验证证书-总是返回true-跳过验证

///

private bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) { return true; }

  2. 2个子功能:

  ///

/// 将每个职位数据清洗后添加到XDocument对象的根节点下

///

private bool JobCopyToXML(Dictionary dt)

{

int id = Convert.ToInt32(dt["positionId"]);//职位详情页的文件名,当作索引键

if (IndexKey.Contains(id))//用不同关联词搜出的职位可能有重复。

{

CountRepeat++;// 新增重复职位统计

return true;

}

IndexKey.Add(id);//添加一个索引

XElement xjob = new XElement("OneJob");

xjob.SetAttributeValue("id", id);

string positionUrl = @"https://www.lagou.com/jobs/" + id + ".html";//职位主页

try

{

xjob.SetElementValue("职位名称", dt["positionName"]);

xjob.SetElementValue("薪酬范围", dt["salary"]);

xjob.SetElementValue("经验要求", dt["workYear"]);

xjob.SetElementValue("*敏*感*词*要求", dt["education"]);

xjob.SetElementValue("工作城市", dt["city"]);

xjob.SetElementValue("工作性质", dt["jobNature"]);

xjob.SetElementValue("发布时间", Regex.Match(dt["createTime"].ToString(), @"[\d]{4}-[\d]{1,2}-[\d]{1,2}").Value);

xjob.SetElementValue("职位主页", positionUrl);

xjob.SetElementValue("职位诱惑", dt["positionAdvantage"]);

string html = GetHTMLToString(positionUrl, CerPath);//从职位主页爬取职位和企业的补充信息

var dom = new HtmlParser().Parse(html);//HTML解析成IDocument,使用Nuget AngleSharp 安装包

//QuerySelector :选择器语法 ,根据选择器选择dom元素,获取元素中的文本并进行格式清洗

xjob.SetElementValue("工作部门", dom.QuerySelector("div.company").TextContent.Replace((string)dt["companyShortName"], "").Replace("招聘", ""));

xjob.SetElementValue("工作地点", dom.QuerySelector("div.work_addr").TextContent.Replace("\n", "").Replace(" ", "").Replace("查看地图", ""));

string temp = dom.QuerySelector("dd.job_bt>div").TextContent;//职位描述,分别去除多余的空格和换行符

temp = string.Join(" ", temp.Trim().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));

xjob.SetElementValue("职位描述", string.Join("\n", temp.Split(new string[] { "\n ", " \n", "\n" }, StringSplitOptions.RemoveEmptyEntries)));

xjob.SetElementValue("企业官网", dom.QuerySelector("ul.c_feature a[rel=nofollow]").TextContent);

xjob.SetElementValue("企业简称", dt["companyShortName"]);

xjob.SetElementValue("企业全称", dt["companyFullName"]);

xjob.SetElementValue("企业规模", dt["companySize"]);

xjob.SetElementValue("发展阶段", dt["financeStage"]);

xjob.SetElementValue("所属领域", dt["industryField"]);

xjob.SetElementValue("*敏*感*词*页", @"https://www.lagou.com/gongsi/" + dt["companyId"] + ".html");

XJobs.Add(xjob);

CountAdd++;//新增职位统计

return true;

}

catch (Exception ex)

{

LogToTxt.Error("职位转换为XElement时出错,文件:"+ XmlSavePath+",Id="+id+",错误信息:"+ex);

Console.WriteLine("职位转换为XElement时出错,文件:" + XmlSavePath + ",Id=" + id + ",错误信息:" + ex);

return false;

}

}

  2. 3 Web采集器:

  ///

/// Get方式请求url,获取报文,转换为string格式

///

private string GetHTMLToString(string url, string path)

{

Thread.Sleep(1500);//尽量模仿人正常的浏览行为,每次进来先休息1.5秒,防止拉勾网因为访问太频繁屏蔽本地IP

try

{

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);

request.ClientCertificates.Add(new X509Certificate(path));//添加证书

request.Method = "GET";

request.KeepAlive = true;

request.Accept = "text/html, application/xhtml+xml, */*";

request.ContentType = "text/html";

request.UserAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)";

request.Credentials = CredentialCache.DefaultCredentials;//添加身份验证

request.AllowAutoRedirect = false;

byte[] responseByte = null;

using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())

using (MemoryStream _stream = new MemoryStream())

{

response.GetResponseStream().CopyTo(_stream);

responseByte = _stream.ToArray();

}

string html = Encoding.UTF8.GetString(responseByte);

return ReplaceIllegalClar(html);//进行特殊字符处理

}

catch (Exception ex)

{

LogToTxt.Error("网页:" + url + ",爬取时出现错误:" + ex);

Console.WriteLine("网页:" + url + ",爬取时出现错误:" + ex);

return "";

}

}

  2. 4特殊字符处理:

  private string ReplaceIllegalClar(string html)

{

StringBuilder info = new StringBuilder();

foreach (char cc in html)

{

int ss = (int)cc;

if (((ss >= 0) && (ss = 11) && (ss = 14) && (ss 0)

{

string str = xmlSavePath + ":用时" + sw.Elapsed + ";去重后的总搜索结果数=" + count[0] + ",搜索结果中的重复数=" + count[1];

Console.WriteLine(str);

}

else

{

Console.WriteLine("遇到错误,详情请检查日志");

}

Console.ReadKey();

  关于Internet上Log4Net的配置有很多共享。我更改了日志格式,以使其更易于阅读:

  采集整个网站的结果:

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线