被管理员和谐了的最高票答案“知乎数据抓取程序”(.net、c#数据挖掘)
来源:程序员人生 发布时间:2015-03-04 08:49:08 阅读次数:4286次
问:能利用爬虫技术做到哪些很酷很有趣很有用的事情?
准备学习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 CanConnect
OracleServer
{
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 (CanConnect
OracleServer)
{
DataTable dt = GetQustions();
if (dt != null && ᨲ鱸ÇÇ⚶ﭺ嫭蛿윺ꕎ腄衐⬤첁ꍪ쪀ྤ㐉妐揉䂲▱䧅䈩∧䤄閵ᢙꈴᦉ‴आᤩ꘤䤂ꒁ级鿩俐헕掱롸羖껡㺵⎾ɂ꩒ሴ붤ﭽ粰髹}忺磩ꄲ쿚㭅吺꺤絞⨏痦襸鞘詨韠⩳㕌蝹䴒壉턼骢⎪勰標째죶俶섾゚䧸ﱛ瞴䯆ﱤ轧䭲ﳷ燱ﱺ쓻ᴗ﮿鴢╉ⴓ濴㑇튡玙䏿㉳躑짿槰过矲勞旙ꓩ瓱녽軿퇾俧ﲺ쯙閹습맵㙜宛熹䳄羡荷ꓔ턴籕ﯳ꧆䣫빱ꛯ㴿㽆鿼ம藹坢槨﹡慾Ꞵㄕஏ靣︽Ὠ봯䟱爖駙쏼ﲮ뷻ﻋ잵楱籘⓰횿羕춝ꏾ喇Ꮋ콁쩺㷋맧訯圗娩䴖낭嚼뮘宜㑳煨굾歴鿞䢿穖䕨培拾⍋蓴댖줝㚶瞿缥맣滛ꇽ㐛展ⴛ῞Ꮄ렡ꊾ渎쎛談콫ﺇ㦝耗얹㐠ﯷ䩺䏝犑ꈝ︉滼㬩迣팎緽觹뒧죓촂㼶騲ᵛ៷誖믐皁繂糰緊優ጏ㳽뾕䵵濤寸ⓗ㍭ㇰ薀꓆朧ដﰖㇹꭧ럼뵚︽꽴댿䃂绪샃忎䩾끫䝃哪毚髳䍉抝矟⧕폠뿃穷麝뉽躻흎備쾣⯨鉺見淽㪝伛櫬ྰむ껅ꁮ걫횸ᡛ尟䟴靮䝺敨䞅秧厉貣菣뒽뙐ﻹ홰绯ᾕ쟮킐煿땾㊰䎻佇ḵᅬ
生活不易,码农辛苦
如果您觉得本网站对您的学习有所帮助,可以手机扫描二维码进行捐赠
------分隔线----------------------------
------分隔线----------------------------