网络采集软件核心技术剖析系列(1)---如何使用C#语言获取博客园某个博主的全部随笔链接及标题

简介:

一 本系列随笔概览及产生的背景

自己开发的豆约翰博客备份专家软件工具问世3年多以来,深受广大博客写作和阅读爱好者的喜爱。同时也不乏一些技术爱好者咨询我,这个软件里面各种实用的功能是如何实现的。

该软件使用.NET技术开发,为回馈社区,现将该软件中用到的核心技术,开辟一个专栏,写一个系列文章,以飨广大技术爱好者。

本系列文章除了讲解网络采编发用到的各种重要技术之外,也提供了不少问题的解决思路和界面开发的编程经验,非常适合.NET开发的初级,中级读者,希望大家多多支持。

很多初学者常有此类困惑,“为什么我书也看了,C#相关的各个方面的知识都有所了解,但就是没法写出一个像样的应用呢?”,

这其实还是没有学会综合运用所学知识,锻炼出编程思维,建立起学习兴趣,我想该系列文章也许会帮到您,但愿如此。

开发环境:VS2008

源码位置:https://github.com/songboriceboy/NetworkGatherEditPublish

源码下载办法:安装SVN客户端(本文最后提供下载地址),然后checkout以下的地址:https://github.com/songboriceboy/NetworkGatherEditPublish

系列文章提纲拟定如下:

1.如何使用C#语言获取博客园某个博主的全部随笔链接及标题;
2.如何使用C#语言获得博文的内容;
3.使用C#语言如何将html网页转换成pdf(html2pdf)
4.如何使用C#语言下载博文中的全部图片到本地并可以离线浏览
5.如何使用C#语言合成多个单个的pdf文件到一个pdf中,并生成目录
6.网易博客的链接如何使用C#语言获取到,网易博客的特殊性;
7.微信公众号文章如何使用C#语言下载;
8.如何获取任意一篇文章的全部图文
9.如何使用C#语言去掉html中的全部标签获取纯文本(html2txt)
10.如何使用C#语言将多个html文件编译成chm(html2chm)
11.如何使用C#语言远程发布文章到新浪博客
12.如何使用C#语言开发静态站点生成器
13.如何使用C#语言搭建程序框架(经典Winform界面,顶部菜单栏,工具栏,左边树形列表,右边多Tab界面)
14.如何使用C#语言实现网页编辑器(Winform)

......

二 第一节主要内容简介(如何使用C#语言获取博客园某个博主的全部随笔链接及标题)

获取某个博主的全部博文链接及标题的解决方案,演示demo如下图所示:可执行文件下载

三 基本原理

 要想采集的某个博主的全部博文网页地址,需要分2步:

1.通过分页链接获取到网页源代码;

2.从获取到的网页源代码中解析出文章地址和标题;

第一步,首先找到分页链接,比如我的博客

第一页 http://www.cnblogs.com/ice-river/default.html?page=1

第二页 http://www.cnblogs.com/ice-river/default.html?page=2

 我们可以写个函数把这些分页地址字符串保存至一个队列中,如下代码所示,

下面的代码中,我们默认保存了500页,500页*20篇=10000篇博文,一般够用了,除非对于特别高产的博主。

还有一点,有心的朋友们可能会问,500页是不是太多了,有的博主只有2,3页,我们有必要去采集500个分页来获取全部博文链接么?

这里因为我们不知道某个博主到底写了多少篇博文(分成几页),所以,我们先默认取500页

,后面会讲到一种判断已经获取到全部文章链接的办法,其实我们并不会每个博主都访问500个分页。

 protected void GatherInitCnblogsFirstUrls()
        {            string strPagePre = "http://www.cnblogs.com/";            string strPagePost = "/default.html?page={0}&OnlyTitle=1";            string strPage = strPagePre + this.txtBoxCnblogsBlogID.Text + strPagePost;            for (int i = 500; i > 0; i--)
            {                string strTemp = string.Format(strPage, i);
                m_wd.AddUrlQueue(strTemp);

            }
        }

 至于获取某个网页的源文件(就是你在浏览器中,对某个网页右键---查看网页源代码功能)

C#语言已经为我们提供了一个现成的HttpWebRequest类,我将其封装成了一个WebDownloader类,具体细节大家可以参考源代码,主要函数实现如下:

     public string GetPageByHttpWebRequest(string url, Encoding encoding, string strRefer)
        {            string result = null;
   
            WebResponse response = null;
            StreamReader reader = null;            try
            {
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
                request.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)";
                request.Accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*";                if (!string.IsNullOrEmpty(strRefer))
                {
                    Uri u = new Uri(strRefer);
                    request.Referer = u.Host;
                }                else
                {
                    request.Referer = strRefer;
                }
                request.Method = "GET";
                response = request.GetResponse();
                reader = new StreamReader(response.GetResponseStream(), encoding);
                result = reader.ReadToEnd();
                
            }            catch (Exception ex)
            {
                result = "";
            }            finally
            {                if (reader != null)
                    reader.Close();                if (response != null)
                    response.Close();
                
            }            return result;
        }

第一个参数传入的就是,我们上面形成的500个分页地址,函数的返回值就是网页的源代码(我们想要的文章地址和标题就在其中,接下来我们要把它们解析出来)。

第二步:从获取到的网页源代码中解析出文章地址和标题

我们要利用大名鼎鼎的HtmlAgilityPack类库,HtmlAgilityPack是一个HTML文档的解析利器,通过它我们可以方便的获得网页的标题,正文,分类,日期等等,理论上任何元素,相关的文档网上有很多,这里就不多说了。这里我们给HtmlAgilityPack增加了一个扩展方法以提取出任意网页源文件的全部超级链接GetReferences和链接对应的文本GetReferencesText。

    private void GetReferences()
        {
            HtmlNodeCollection hrefs = m_Doc.DocumentNode.SelectNodes("//a[@href]");            if (Equals(hrefs, null))
            {
                References = new string[0];                return;
            }

            References = hrefs.
                Select(href => href.Attributes["href"].Value).
                Distinct().
                ToArray();
        }
private void GetReferencesText()
        {            try
            {
                m_dicLink2Text.Clear();
                HtmlNodeCollection hrefs = m_Doc.DocumentNode.SelectNodes("//a[@href]");                if (Equals(hrefs, null))
                {                    return;
                }                foreach (HtmlNode node in hrefs)
                {                    if (!m_dicLink2Text.Keys.Contains(node.Attributes["href"].Value.ToString()))                        if(!HttpUtility.HtmlDecode(node.InnerHtml).Contains("img src")                            && !HttpUtility.HtmlDecode(node.InnerHtml).Contains("img ")                            && !HttpUtility.HtmlDecode(node.InnerHtml).Contains(" src"))
                         m_dicLink2Text.Add(node.Attributes["href"].Value.ToString(), HttpUtility.HtmlDecode(node.InnerHtml));
                }                int a = 0;
            }            catch (System.Exception e)
            {
                System.Console.WriteLine(e.ToString());
            }

        }

但是注意到,到此为止我们是获取到了某个网页中的全部链接地址,这其实距离我们想要的还差点,所以我们需要在这些链接地址集合中过滤出我们真正想要的博文地址。

这时我们需要用到强大的正则表达式工具,同样C#中提供了现成的支持类,但是需要我们对正则表达式有所了解,这里就不讲解正则表达式的相关知识了,不懂的请自行百度之。

首先我们需要观察博文链接地址的格式:

随便找几篇博文:

http://www.cnblogs.com/ice-river/p/3475041.html

http://www.cnblogs.com/zhijianliutang/p/4042770.html

我们发现链接和博主ID有关,所以博主ID我们需要有个变量( this.txtBoxCnblogsBlogID.Text)进行记录,

上面的链接模式用正则表达式可以表示如下:

"www\.cnblogs\.com/" + this.txtBoxCnblogsBlogID.Text + "/p/.*?\.html$";

简单解释一下:\代表转义,因为.在正则表达式中有重要含义;$代表结尾,html$的意思就是以html结尾。.*?是什么,很重要且不太好理解

 

正则有两种模式,一种为贪婪模式(默认),另外一种为懒惰模式,以下为例:
(abc)dfe(gh)
对上面这个字符串使用(.*)将会匹配整个字符串,因为正则默认是尽可能多的匹配。
虽然(abc)满足我们的表达式,但是(abc)dfe(gh)也同样满足,所以正则会匹配多的那个。
如果我们只想匹配(abc)和(gh)就需要用到以下的表达式
(.*?)
在重复元字符*或者+后面跟一个?,作用就是在满足的条件下尽可能少匹配。

 

所以,上面的正则表达式的意思就是“含有www.cnblogs.com/接着博主ID然后再接着/p/然后再接着任意多个字符直到遇到html结尾为止”。

然后,我们就可以通过C#代码来过滤符合这个模式的全部链接了,主要代码如下:

   MatchCollection matchs = Regex.Matches(normalizedLink, m_strCnblogsUrlFilterRule, RegexOptions.Singleline);                if (matchs.Count > 0)
                {                    string strLinkText = "";                    if (links.m_dicLink2Text.Keys.Contains(normalizedLink))
                        strLinkText = links.m_dicLink2Text[normalizedLink];                    if (strLinkText == "")
                    {                        if (links.m_dicLink2Text.Keys.Contains(link))
                            strLinkText = links.m_dicLink2Text[link].TrimEnd().TrimStart();
                    }

                    PrintLog(strLinkText + "\n");
                    PrintLog(normalizedLink + "\n");
                    

                    lstThisTimesUrls.Add(normalizedLink);
                }

 判断全部文章链接获取完成:之前,我们是计划采集500个分页地址,但是有可能该博主的全部博文只有几页,那么我们该如何判断全部文章都下载完成了呢?

办法其实很简单,就是我们使用2个集合,一个是当前下载的全部文章集合,一个是本次下载到的文章集合,如果本次下载的全部文章,之前下载的全部集合中都有了,那么说明全部文章都下载完成了。

程序中,我将这个判断封装成了一个函数,代码如下:

  private bool CheckArticles(List<string> lstThisTimesUrls)
        {            bool bRet = true;            foreach (string strTemp in lstThisTimesUrls)
            {                if (!m_lstUrls.Contains(strTemp))
                {
                    bRet = false;                    break;
                }
            }            foreach (string strTemp in lstThisTimesUrls)
            {                if (!m_lstUrls.Contains(strTemp))
                    m_lstUrls.Add(strTemp);
            }         
            return bRet;
        }

 

四 其他比较重要的知识

1.BackgroundWorker工作者线程的使用,因为我们的采集任务是一个比较耗时的工作,所以我们不应该放到界面主线程去做,我们应该启动一个后台线程,c#中最方便的后台线程使用方法就是利用BackgroundWorker类。

2.由于我们需要在解析出每一篇文章的地址及标题后,在界面上打印出来,同时因为我们不能在工作者线程中去修改界面控件,所以这里我们需要使用C#中的代理delegate技术,通过回调的方式来实现在界面上输出信息。

 

        TaskDelegate deles = new TaskDelegate(new ccTaskDelegate(RefreshTask));        
        public void RefreshTask(DelegatePara dp)
        {            //如果需要在安全的线程上下文中执行            if (this.InvokeRequired)
            {                this.Invoke(new ccTaskDelegate(RefreshTask), dp);                return;
            }          
            //转换参数            string strLog = (string)dp.strLog;
            WriteLog(strLog);

        }        protected void PrintLog(string strLog)
        {
            DelegatePara dp = new DelegatePara();

            dp.strLog = strLog;
            deles.Refresh(dp);
        }        public void WriteLog(string strLog)
        {            try
            {
                strLog = System.DateTime.Now.ToLongTimeString() + " : " + strLog;           
                this.richTextBoxLog.AppendText(strLog);                this.richTextBoxLog.SelectionStart = int.MaxValue;                this.richTextBoxLog.ScrollToCaret();
            }            catch
            {
            }
        }









本文转自 xchsp 51CTO博客,原文链接:http://blog.51cto.com/freebird/1586526,如需转载请自行联系原作者

目录
相关文章
|
3天前
|
SQL 安全 算法
网络安全与信息安全:攻防之间的技术博弈
【4月更文挑战第20天】在数字化时代,网络安全与信息安全已成为维护国家安全、企业利益和个人隐私的重要屏障。本文深入探讨了网络安全漏洞的成因、加密技术的进展以及提升安全意识的必要性,旨在为读者提供全面的网络安全知识框架,同时分享最新的防御策略和技术手段。通过对网络攻防技术的分析,本文揭示了安全防御的复杂性,并强调了持续教育和技术创新在网络安全领域的重要性。
15 6
|
20天前
|
SQL 安全 算法
网络安全与信息安全:防御前线的关键技术与意识
【4月更文挑战第3天】在数字化时代,网络安全与信息安全已成为维护信息完整性、确保数据私密性和保障系统可用性的基石。本文深入探讨了网络安全漏洞的概念、加密技术的应用以及提升安全意识的重要性,旨在为读者提供全面的网络安全知识框架,以应对日益复杂的网络威胁。
|
14天前
|
数据采集 大数据 数据安全/隐私保护
掌握网络抓取技术:利用RobotRules库的Perl下载器一览小红书的世界
本文探讨了使用Perl和RobotRules库在遵循robots.txt规则下抓取小红书数据的方法。通过分析小红书的robots.txt文件,配合亿牛云爬虫代理隐藏真实IP,以及实现多线程抓取,提高了数据采集效率。示例代码展示了如何创建一个尊重网站规则的数据下载器,并强调了代理IP稳定性和抓取频率控制的重要性。
掌握网络抓取技术:利用RobotRules库的Perl下载器一览小红书的世界
|
3天前
|
安全 物联网 网络安全
网络安全与信息安全:防御前线的关键技术与策略
【4月更文挑战第21天】在数字化时代,数据成为了新的货币,而网络安全则是保护这些资产不受威胁的盾牌。本文深入探讨了网络安全漏洞的本质、加密技术的进展以及提升个人和企业安全意识的方法。通过分析当前网络攻击的模式和动机,我们提出了一系列切实可行的防御措施,旨在帮助读者构建更加安全的网络环境。
|
5天前
|
存储 监控 安全
网络安全与信息安全:防御前线的关键技术与意识
【4月更文挑战第18天】在数字化时代,数据成为了新的货币,而网络安全则是保护这些宝贵资产不受威胁的盾牌。本文深入探讨了网络安全的核心议题—漏洞管理、加密技术以及安全意识的重要性。通过分析当前网络环境中普遍存在的安全挑战,我们阐述了如何通过持续监控、定期更新和强化员工培训来构建一个更加坚固的防线。
|
7天前
|
运维 网络架构
软件体系结构 - 网络拓扑结构
【4月更文挑战第14天】软件体系结构 - 网络拓扑结构
10 0
|
13天前
|
存储 安全 网络安全
未来云计算与网络安全:技术创新与挑战
随着数字化时代的来临,云计算与网络安全成为了当今科技领域的焦点。本文从技术创新和挑战两个方面探讨了未来云计算与网络安全的发展趋势。在技术创新方面,人工智能、区块链和量子计算等新兴技术将为云计算和网络安全带来前所未有的发展机遇;而在挑战方面,隐私保护、数据泄露和网络攻击等问题也将不断考验着技术研究者和行业从业者的智慧和勇气。未来,只有不断创新,同时加强安全防护,才能实现云计算与网络安全的良性发展。
15 1
|
15天前
|
传感器 监控 安全
|
15天前
|
安全 SDN 数据中心
|
18天前
|
SQL 监控 安全
网络安全与信息安全:防御前线的关键技术与策略
【4月更文挑战第6天】 在数字化时代,数据成为了新的货币,而网络安全则是保护这些数据的金库。本文深入分析了网络安全漏洞的形成机理、加密技术的最新进展以及提升个人和企业安全意识的实践方法。通过对网络威胁的剖析和防御技术的探讨,文章为读者提供了一系列的信息安全防护策略,旨在帮助个人和组织构建更为坚固的数字防线。