程序员人生 网站导航

被管理员和谐了的最高票答案“知乎数据抓取程序”(.net、c#数据挖掘)

栏目:php教程时间:2015-03-04 08:49:08


问:能利用爬虫技术做到哪些很酷很有趣很有用的事情?

准备学习python爬虫。各位大神都会用爬虫做哪些有趣的事情?

今天突然想玩玩爬虫,就提了这个问题。随着YouTube上的1个tutor写了个简单的程序,爬了1点豆瓣的数据。主要用到request和bs4(BeautifulSoup)模块。虽然简陋,毕竟是人生中的第1只爬虫啊……以示记念,代码写在博客里了:我的第1只爬虫:爬取豆瓣读书


没想到第1次答题竟然登上了知乎互联网专栏的榜首了,也上了知乎首页,惋惜被和谐了。




答:

监测她(他)的知乎,她关注、回答、赞了某个问题立马电脑和手机都弹出提示是否是很酷!先上两张图:




我是个.NET程序猿,有1天女神告知我有1个很不错的社区叫“知乎”,我常常1过来就看到她在看知乎,但每次我想看她都看了啥啊,她就遮住屏幕不让我看。因而乎,在我心里埋下了1颗强烈的好奇心。知乎中搜了下她的名字,经过各种挑选知道了她的知乎空间。第1时间出现的想法是我要写个监测程序,她关注的所有问题我都想知道。
连续奋战5小时,至清晨3点程序终究写出来了。主要HttpWebRequest加正则表达式来抓取数据,程序开机自动运行,数据库设在1台24小时开机的服务器上。多个监测客户端同时运行,公司的,家里的,远程服务器上的。每隔5分钟自动循环读取1次数据,如果检测到关注了新的问题,立马将它们发送至我的QQ邮箱和我的163邮箱,大家都知道QQ邮箱有提示功能,1发过来,立马会弹出1个窗体告知你有新的邮件。手机qq客户端也有,所以不管我是在上班的路上,还是在电脑旁,只要她有新动态我立马就知道了。是否是很酷?
监测程序已运行3个多月了,搜集了他23百个关注的问题,我知道她1般都是吃中饭或晚餐前喜欢看1下知乎,晚上睡前会看会,她睡得早但偶尔清晨1点多还看知乎。她关注情感类的问题最多,而且那段时间我1直在追她,所以我能根据她关注的问题来推测她的1些想法,包括约会聊天时我可以聊1些她感兴趣的话题。所以实用性还是比较强的。
假设某1天清晨1点,手机突然响了1下,发现她关注了某个问题。立马给她发1条短信过去,你是否是还没睡啊? 是啊,你怎样知道我没睡的? 凭感觉! 嘿嘿。 然后渐渐靠近她关注的那个话题去聊,这是否是会让她感觉到你特别懂她。好奇你竟然知道她睡没睡,好奇你和她聊的话那末符合她的心声。

如此利器,有谁想要的吗? 赞超过1百,程序和源代码都放知乎上同享。

====21日9:37更新=====
没想到第1次答题就上榜了,好不开心,来来来,别停哈。怒上榜首,我开源12306抢票源码。我先赴约去开源我的知乎监测程序吧,1会把链接发过来。谢谢大家的赞!


===============


====21日11:50更新=====
《关于隐私》
先说明下,很多人都说我这样做侵犯隐私?没有吧,这些数据都是公然的啊,她也知道我关注了她呀,但蛋疼的知乎客户端没有这么细致的提示功能,我乃至在客户端上找不到我都关注了哪些人。知乎手机app开发团队弱爆了,这么强有力的需求竟然没有满足? 而且,知乎!你怎样就没有定阅功能呢?邮箱定阅! 我提出来啦哈,采用了给我大V可好?

《关于匿不匿名》
FK,男子汉大丈夫,匿啥名啊。女神知道了就知道了,又不是做甚么丧尽天良的事,敢作敢当!之所以写这么1个程序,也其实不是完全的偷窥心理。对1个程序员来讲,写出1个新鲜的程序是能给程序员1种很大的乐趣的,这1般人难以理解,想当年在学校时,通宵写俄罗斯方块,白天上课不听课在那研究1个方块当按左键是甚么模样甚么逻辑,右键又怎样。这是非常成心思的事,编程实际上是1个艺术活,好的框架和优良的代码让人1看就感觉特别享受。 所以我写这么个程序一样也是满足自己的1种乐趣。没必要匿!

《关于女神追到没?》
追到啦,哈哈!好爽啊,9月份去骑了趟川藏线,路遇佛像及经轮,我就祈祷我要娶她做老婆。且动身前找牛逼大神算了1卦(中国易学协会副会长),说我10月份很有姻缘缘分。因而,我在世界3大冰川之1的-来古冰川的河床上找了1下午的石头,终究找到1颗天然红色心形的爱情石,回来后我就拿着石头跟她表白了,然后就成了!虽然她说我表白像检讨1样,但也很感人!
惋惜,我们在1起没多久就分手了。缘由1两句话说不完,总之不管以后怎样。都祝愿她,虽然在1起不长时间,但那是很美的回想。我会收藏!
===================


=====2015⑴⑵1 上午11:16分更新=======

程序开源地址:

下载源程序http://download.csdn.net/detail/wuyidexinsheng/8382253
程序博文地址:http://blog.csdn.net/wuyidexinsheng/article/details/42964707


步入正题,思路描写:

下来源程序后,先自己去建数据库改配置哈,否则程序运行不起来的。
<span style="font-size:10px;"><?xml version="1.0"?> <configuration> <appSettings> <!--数据有两种存储方式,1种存储于本地程序目录下的Ids.txt,但那只存了问题ID,完全的数据存于oracle数据库中--> <add key="connStr" value="Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=DZZH)));User Id=xxxxx;Password=xxxx"/> <!-- 设置监测循环时间:秒 --> <add key="Interval" value="600"/> <!--设置自动发送信息机器人邮箱--> <add key="smtpAddress" value="smtp.163.com"/> <!--用户名--> <add key="sendEmailFrom" value="xxxxxxxx@163.com"/> <!--密码--> <add key="sendEmailFromPwd" value="xxxxxxxxx"/> <!--接收邮箱地址--> <add key="strMailAddressTo" value="xxxxxxxx@163.com,xxxxxxxx@qq.com"/> <!--邮件名称抬头--> <add key="EmailName" value="zhApp-家里电脑"/> <!--END--> <!--监测地址--> <add key="WatchingURL" value="http://www.zhihu.com/people/wu-xin-sheng⑺"/> </appSettings> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/> </startup> </configuration></span>

第1步:通过Cinser.Common.HttpHelper.GetString(“www.zhihu.com/people/wu-xin-sheng⑺”)读取给定的1个网址的后台代码:

结果以下图所示:


如我空间的源码中就有这么1段:

<span class="name">伍新生</span>,<span class="bio" title="5颜6色的情感,我终生的寻求!">5颜6色的情感,我终生的寻求!</span> </div> </div> <div class="body clearfix"> <div class="zm-profile-header-avatar-container self"> <img alt="伍新生" src="http://pic4.zhimg.com/94cc60166_l.jpg" class="zm-profile-header-img zg-avatar-big zm-avatar-editor-preview"/> <span class="zm-entry-head-avatar-edit-button">修改头像</span>

第2步:通过调用方法private List<Question> GetQuestions(string source)截取关键数据。

原理很简单,所有知乎的问题都是以下的格式:<aclass="question_link"target="_blank"href="/question/27621722/answer/37636385">能利用爬虫技术做到哪些很酷很有趣很有用的事情?</a>

接下来那就是字符串截取呗:调用Cinser.Common.StringPlus.SubString(source, “<a class="question_link”, "</a>")等顺次截取问题的ID,名称等数据。

private List<Question> GetQuestions(string source) { List<Question> questions = new List<Question>(); string startStr = "<a class="question_link""; if (source.IndexOf(startStr) != ⑴) { Question q = new Question(); string content = Cinser.Common.StringPlus.SubString(source, startStr, "</a>"); q.Id = Cinser.Common.StringPlus.SubString(content, "question/", """); q.Title = content.Substring(content.IndexOf(">") + 1); q.Time = DateTime.Now; questions.Add(q); source = source.Substring(source.IndexOf(startStr) + startStr.Length); questions.AddRange(GetQuestions(source)); } return questions; }

第3步:将取到的问题写入数据库,并写入本地Ids.txt。

由于5分钟读取1次数据,肯定会读取到部份已度过的数据咯。就是通过取到id然后看Ids.txt里面这个id是否是已存在了,如果存在了就表示已抓去过啦。 

为何问题都已写到oracle数据库了还要往本地Ids.txt写1次呢,由于oracle数据库是部署在远程服务器上的啊。如果这台服务器突然出故障死机了,怎样办?程序还得要运行啊,所以程序往两个地方都写1次数据。如果oracle数据库不能访问,则通过读取和写入本地ids来记录问题。


数据抓取其实就这么简单。


以下是部份源码,也能够直接去下载源程序:http://download.csdn.net/detail/wuyidexinsheng/8382253

以下是程序主窗体源码:

public partial class Form1 : Form { DataProvider dal; string watchingURL = string.Empty; int LoopCount = 0; public Form1() { InitializeComponent(); dal = new DataProvider(); dal.AddLog("程序启动"); base.WindowState = FormWindowState.Minimized; base.Show(); base.Hide(); base.WindowState = FormWindowState.Normal; base.ShowInTaskbar = false; base.TopMost = false; base.MaximizeBox = false; base.MinimizeBox = false; base.ControlBox = false; //设置循环监测时间 int interval = int.Parse(Cinser.Common.ConfigurationHelper.GetAppSettingsValue("Interval")); timer1.Interval = interval * 1000; watchingURL = Cinser.Common.ConfigurationHelper.GetAppSettingsValue("WatchingURL"); RunWhenStart(true, "zhApp.exe", """ + Application.StartupPath + "zhApp.exe" AutoRun"); Run(); dal.AddLog("程序初始化成功"); LoopCount += 1; } //设置程序开机自启动 public void RunWhenStart(bool Started, string name, string path) { RegistryKey HKLM = Registry.CurrentUser; RegistryKey Run = HKLM.CreateSubKey(@"SOFTWAREMicrosoftWindowsCurrentVersionRun"); if (Started == true) { try { Run.SetValue(name, path); HKLM.Close(); } catch(Exception ex)//没有权限会异常 { throw ex; } } else { try { Run.DeleteValue(name); HKLM.Close(); } catch (Exception ex)//没有权限会异常 { throw ex; } } } /// <summary> /// 运行监测流程 /// </summary> private void Run() { List<Question> questions = GetQuestions(Cinser.Common.HttpHelper.GetString(watchingURL)); string ids = dal.GetExistIdsStr(); for (int i = 0; i < questions.Count; i++) { if (ids.IndexOf(questions[i].Id) != ⑴) { questions.Remove(questions[i]); i--; } else { if (ids == string.Empty) ids = questions[i].Id; else ids += "," + questions[i].Id; } } if (questions.Count > 0) { SendQuestions(questions); dal.WriteIdStrToTxt(ids); dal.Add(questions); dal.AddLog(string.Format("获得了{0}条新数据。", questions.Count)); } } /// <summary> /// 从监测站点源数据中抓取问题 /// </summary> private List<Question> GetQuestions(string source) { List<Question> questions = new List<Question>(); string startStr = "<a class="question_link""; if (source.IndexOf(startStr) != ⑴) { Question q = new Question(); string content = Cinser.Common.StringPlus.SubString(source, startStr, "</a>"); q.Id = Cinser.Common.StringPlus.SubString(content, "question/", """); q.Title = content.Substring(content.IndexOf(">") + 1); q.Time = DateTime.Now; questions.Add(q); source = source.Substring(source.IndexOf(startStr) + startStr.Length); questions.AddRange(GetQuestions(source)); } return questions; } private bool SendQuestions(List<Question> questions) { bool bSuccess = true; List<string> strMailAddressTo = Cinser.Common.ConfigurationHelper.GetAppSettingsValue("strMailAddressTo").Split(',').ToList(); string smtpAddress = Cinser.Common.ConfigurationHelper.GetAppSettingsValue("smtpAddress"); string sendEmailFrom = Cinser.Common.ConfigurationHelper.GetAppSettingsValue("sendEmailFrom"); string sendEmailFromPwd = Cinser.Common.ConfigurationHelper.GetAppSettingsValue("sendEmailFromPwd"); string emailName = Cinser.Common.ConfigurationHelper.GetAppSettingsValue("EmailName"); //密码解密,开源的话就去掉这步吧,这样配置的时候直接配置明文密码就行。 //sendEmailFromPwd = Cinser.Common.Security.DecryptDES(sendEmailFromPwd, "yuiophgf"); string msg = string.Empty; SendCompletedEventHandler s = new SendCompletedEventHandler(SendCompleted); string content = GetQustionsListStr(questions); content += " 信息来源于:" + watchingURL; Cinser.Common.SmtpEmailSend.SendEmail(strMailAddressTo, emailName + DateTime.Now.ToString(), content, smtpAddress, 0x19, sendEmailFrom, sendEmailFromPwd, "163测试邮箱", null, out msg, s); return bSuccess; } private void SendCompleted(object sender, AsyncCompletedEventArgs e) { } private string GetQustionsListStr(List<Question> questions) { string content = string.Empty; if (questions != null && questions.Count > 0) { content = string.Format("名称:{0},url:{1}", questions[0].Title, questions[0].Url); for (int i = 1; i < questions.Count; i++) { content += string.Format(" 名称:{0},url:{1}", questions[i].Title, questions[i].Url); } } return content; } private void timer1_Tick(object sender, EventArgs e) { int interval = int.Parse(Cinser.Common.ConfigurationHelper.GetAppSettingsValue("Interval")); timer1.Interval = interval * 1000; Run(); dal.AddLog(string.Format("程序循环次数:{0}", LoopCount++)); } }


知乎问题model:Question.cs

public class Question { string id, title, url, type, remark; DateTime time; public string Remark { get { return remark; } set { remark = value; } } public DateTime Time { get { return time; } set { time = value; } } public string Type { get { return type; } set { type = value; } } public string Url { get { if (string.IsNullOrEmpty(url)) { url = string.Format("http://www.zhihu.com/question/{0}", Id); } return url; } set { url = value; } } public string Title { get { return title; } set { title = value; } } public string Id { get { return id; } set { id = value; } } }

数据操作类DataProvider.cs

/// <summary> /// 知乎问题数据表操作Provider /// </summary> public class DataProvider { private string connStr = string.Empty; Cinser.DBUtility.DAL.OracleDALCommon dal; string txtPath = ""; string logPath = ""; string debugPath = string.Empty; public string DebugPath { get { if (debugPath == string.Empty) debugPath = System.AppDomain.CurrentDomain.BaseDirectory; if (debugPath.EndsWith("") == false) { debugPath += ""; } return debugPath; } set { debugPath = value; } } public string LogPath { get { if (logPath == string.Empty) { logPath = DebugPath + "Log.txt"; } return logPath; } } public DataProvider() { dal = new Cinser.DBUtility.DAL.OracleDALCommon(this.ConnStr); } public string TxtPath { get { if (txtPath == string.Empty) { txtPath = DebugPath + "Ids.txt"; } return txtPath; } } public string ConnStr { get { if (connStr == string.Empty) { connStr = Cinser.Common.ConfigurationHelper.GetAppSettingsValue("connStr"); } return connStr; } set { connStr = value; } } public bool CanConnectOracleServer { get { return dal.Open(); } } /// <summary> /// 将抓取到的问题写入oracle数据库中 /// </summary> /// <param name="questions"></param> /// <returns></returns> public bool Add(List<Question> questions) { bool bReturn = false; try { for (int i = 0; i < questions.Count; i++) { dal.Add("qustions", questions[i]); } bReturn = true; } catch { } return bReturn; } public DataTable GetQustions(string sqlWhere = "1=1") { try { DataTable dt = dal.GetDataList("qustions", sqlWhere); return dt; } catch { return null; } } public bool IsExist(string id) { try { string sqlWhere = "id='" + id + "'"; DataTable dt = dal.GetDataList("qustions", sqlWhere); return dt.Rows.Count > 0; } catch { return false; } } /// <summary> /// 获得已抓取过的问题ID字符串 /// </summary> /// <returns></returns> public string GetExistIdsStr() { string ids = string.Empty; //如果能连上远程的oracle服务器则从oracle数据库中取ID字符串 if (CanConnectOracleServer) { DataTable dt = GetQustions(); if (dt != null && ᨲ鱸Ç૴Ç⚶ﭺ嫭蛿윺ꕎ腄衐⬤첁ꍪ쪀ྤ㐉妐揉䂲▱䧅䈩∧䤄閵ᢙꈴᦉ‴आᤩ꘤䤂ꒁ级鿩俐헕掱롸羖껡㺵⎾ɂ꩒ሴ붤ﭽ粰髹}￳忺磩ꄲ쿚㭅吺꺤絞⨏痦襸鞘詨韠⩳㕌蝹䴒壉턼骢⎪勰標째죶俶섾゚䧸ﱛ瞴䯆ﱤ轧䭲ﳷ燱ﱺ쓻ᴗ﮿鴢╉ⴓ濴㑇튡玙䏿㉳躑짿槰过￟矲勞旙ꓩ瓱녽軿퇾俧ﲺ쯙閹습맵㙜宛熹䳄羡荷ꓔ턴籕ﯳ꧆䣫빱ꛯ㴿㽆鿼ம藹坢槨﹡慾Ꞵㄕஏ靣︽Ὠ봯䟱爖駙쏼ﲮ⹟뷻ﻋ잵楱籘⓰횿羕춝ꏾ喇Ꮋ콁쩺㷋맧訯圗娩䴖낭嚼뮘宜㑳煨굾歴鿞䢿穖䕨培拾⍋蓴댖줝㚶瞿缥맣滛ꇽ㐛展ⴛ῞Ꮄ렡⿤ꊾ渎쎛談콫ﺇ㦝耗얹୥㐠ﯷ䩺䏝犑ꈝ︉滼㬩迣팎଴緽觹뒧죓촂㼶騲ᵛ៷誖믐皁﫾繂糰緊優ጏ㳽뾕䵵濤寸ⓗ㍭ㇰ薀꓆朧ដﰖㇹꭧ럼᤯뵚︽꽴댿䃂绪샃忎䩾끫䝃哪毚髳䍉抝矟⧕폠뿃穷麝뉽躻흎備쾣⯨鉺見淽㪝伛櫬ྰむ껅ꁮ꥜걫횸ᡛ尟䟴靮䝺敨䞅秧厉貣菣뒽뙐ﻹ홰绯ᾕ쟮⳴킐煿땾㊰䎻佇ḵᅬ
------分隔线----------------------------
------分隔线----------------------------

最新技术推荐