问:能利用爬虫技术做到哪些很酷很有趣很有用的事情?
今天突然想玩玩爬虫,就提了这个问题。跟着YouTube上的一个tutor写了个简单的程序,爬了一点豆瓣的数据。主要用到request和bs4(BeautifulSoup)模块。虽然简陋,毕竟是人生中的第一只爬虫啊……以示纪念,代码写在博客里了:我的第一只爬虫:爬取豆瓣读书
没想到第一次答题居然登上了知乎互联网专栏的榜首了,也上了知乎首页,可惜被和谐了。
答:
监测她(他)的知乎,她关注、回答、赞了某个问题立马电脑和手机都弹出提示是不是很酷!先上两张图:
我 是个.NET程序猿,有一天女神告诉我有一个很不错的社区叫“知乎”,我经常一过来就看到她在看知乎,但每次我想看她都看了啥啊,她就遮住屏幕不让我看。 于是乎,在我心里埋下了一颗强烈的好奇心。知乎中搜了下她的名字,经过各种筛选知道了她的知乎空间。第一时间出现的想法是我要写个监测程序,她关注的所有 问题我都想知道。
连 续奋战5小时,至凌晨3点程序终于写出来了。主要HttpWebRequest加正则表达式来抓取数据,程序开机自动运行,数据库设在一台24小时开机的 服务器上。多个监测客户端同时运行,公司的,家里的,远程服务器上的。每隔5分钟自动循环读取一次数据,如果检测到关注了新的问题,立马将它们发送至我的 QQ邮箱和我的163邮箱,大家都知道QQ邮箱有提醒功能,一发过来,立马会弹出一个窗体告诉你有新的邮件。手机qq客户端也有,所以不管我是在上班的路 上,还是在电脑旁,只要她有新动态我立马就知道了。是不是很酷?
监 测程序已经运行三个多月了,收集了他二三百个关注的问题,我知道她一般都是吃中饭或者晚饭前喜欢看一下知乎,晚上睡前会看会,她睡得早但偶尔凌晨1点多还 看知乎。她关注情感类的问题最多,而且那段时间我一直在追她,所以我能根据她关注的问题来推测她的一些想法,包括约会聊天时我可以聊一些她感兴趣的话题。 所以实用性还是比较强的。
假如某一天凌晨1点,手机突然响了一下,发现她关注了某个问题。立马给她发一条短信过去,你是不是还没睡啊? 是啊,你怎么知道我没睡的? 凭感觉! 嘿嘿。 然后慢慢靠近她关注的那个话题去聊,这是不是会让她感觉到你特别懂她。好奇你居然知道她睡没睡,好奇你和她聊的话那么符合她的心声。
如此利器,有谁想要的吗? 赞超过一百,程序和源代码都放知乎上共享。
====21日9:37更新=====
没想到第一次答题就上榜了,好不开心,来来来,别停哈。怒上榜首,我开源12306抢票源码。我先赴约去开源我的知乎监测程序吧,一会把链接发过来。谢谢大家的赞!
===============
====21日11:50更新=====
《关于隐私》
先说明下,很多人都说我这样做侵犯隐私?没有吧,这些数据都是公开的啊,她也知道我关注了她呀,但蛋疼的知乎客户端没有这么细致的提醒功能,我甚至在客户端上找不到我都关注了哪些人。知乎手机app开发团队弱爆了,这么强有力的需求居然没有满足? 而且,知乎!你怎么就没有订阅功能呢?邮箱订阅! 我提出来啦哈,采纳了给我大V可好?
《关于匿不匿名》
FK, 男子汉大丈夫,匿啥名啊。女神知道了就知道了,又不是做什么伤天害理的事,敢作敢当!之所以写这么一个程序,也并不是完全的偷窥心理。对于一个程序员来 说,写出一个新鲜的程序是能给程序员一种很大的乐趣的,这一般人难以理解,想当年在学校时,通宵写俄罗斯方块,白天上课不听课在那研究一个方块当按左键是 什么样子什么逻辑,右键又怎样。这是非常有意思的事,编程其实是一个艺术活,好的框架和优质的代码让人一看就感觉特别享受。 所以我写这么个程序同样也是满足自己的一种乐趣。不必匿!
《关于女神追到没?》
追 到啦,哈哈!好爽啊,9月份去骑了趟川藏线,路遇佛像及经轮,我就祈祷我要娶她做老婆。且出发前找牛逼大神算了一卦(中国易学协会副会长),说我10月份 很有姻缘缘分。于是,我在世界三大冰川之一的-来古冰川的河床上找了一下午的石头,终于找到一颗天然红色心形的爱情石,回来后我就拿着石头跟她表白了,然 后就成了!虽然她说我表白像检讨一样,但也很感人!
可惜,我们在一起没多久就分手了。原因一两句话说不完,总之不管以后怎样。都祝福她,虽然在一起不长时间,但那是很美的回忆。我会珍藏!
===================
=====2015-1-21 上午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>
- <!--数据有两种存储方式,一种存储于本地程序目录下的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-7"/>
- </appSettings>
- <startup>
- <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
- </startup>
- </configuration></span>
第一步:通过Cinser.Common.HttpHelper.GetString(“www.zhihu.com/people/wu-xin-sheng-7”)读取给定的一个网址的后台代码:
结果如下图所示:
如我空间的源码中就有这么一段:
- <span class="name">伍新生</span>,<span class="bio" title="五颜六色的情感,我毕生的追求!">五颜六色的情感,我毕生的追求!</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>
第二步:通过调用方法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) != -1)
- {
- 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;
- }
第三步:将取到的问题写入数据库,并写入本地Ids.txt。
因为5分钟读取一次数据,肯定会读取到部分已经度过的数据咯。就是通过取到id然后看Ids.txt里面这个id是不是已经存在了,如果存在了就表示已经抓去过啦。
为什么问题都已经写到oracle数据库了还要往本地Ids.txt写一次呢,因为oracle数据库是部署在远程服务器上的啊。如果这台服务器突然出故障死机了,怎么办?程序还得要运行啊,所以程序往两个地方都写一次数据。如果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(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run");
- 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) != -1)
- {
- 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) != -1)
- {
- 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 += "\n\n信息来源于:" + 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("\n 名称:{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 && dt.Rows.Count > 0)
- {
- ids = dt.Rows[0]["id"].ToString();
- for (int i = 1; i < dt.Rows.Count; i++)
- {
- ids += "," + dt.Rows[i]["id"].ToString();
- }
- }
- }
- else
- {//如果能连上远程的oracle服务器关机了,连不上则从本地Ids.txt中取ID字符串
- if (File.Exists(TxtPath))
- ids = File.ReadAllText(TxtPath);
- }
- return ids;
- }
- /// <summary>
- /// 将最新取到的问题记录至ids.txt中,以此标记这些问题为已读问题
- /// </summary>
- /// <param name="ids"></param>
- public void WriteIdStrToTxt(string ids)
- {
- if (File.Exists(this.TxtPath) == false)
- File.Create(TxtPath);
- File.WriteAllText(this.TxtPath, ids);
- }
- /// <summary>
- /// 写程序log,方便错误追踪。
- /// </summary>
- /// <param name="LogMsg"></param>
- public void AddLog(string LogMsg)
- {
- string logStr = string.Format("{0}:{1}.\n", DateTime.Now.ToString(), LogMsg);
- if (File.Exists(this.LogPath) == false)
- File.Create(LogPath);
- string[] logs = File.ReadAllLines(LogPath);
- if (logs.Length >= 520)
- File.WriteAllText(LogPath, logStr);
- else
- {
- StreamWriter sw = File.AppendText(LogPath);
- sw.WriteLine(logStr);
- sw.Close();
- }
- }
- }
- 原文链接:http://blog.csdn.net/wuyidexinsheng/article/details/42964707
