1. 了解需求
公司負(fù)責(zé)運(yùn)營(yíng)的同事找到我蘑辑,說需要搭建一個(gè)新聞?wù)救海糜谝肓髁孔寡纭C總€(gè)站的新聞數(shù)據(jù)都去別的新聞?wù)咀ト⊙蠡辏刻焱砩?0點(diǎn)更新數(shù)據(jù),每個(gè)站還需要單獨(dú)配置SEO(首頁(yè)喜鼓、頻道頁(yè)副砍、詳情頁(yè))、友情鏈接內(nèi)容豁翎。
拆分需求
- 新聞抓取心剥,每個(gè)新聞?wù)镜淖ト?shù)據(jù)源都不一致蝉揍,所以抓取我們需要靈活配置又沾。
- 每天定時(shí),所以我們需要一個(gè)Windows 服務(wù),定時(shí)完成抓取任務(wù)不瓶。
- 新聞?wù)救?/strong>蚊丐,意味著會(huì)有很多站昭娩,如果每個(gè)站單獨(dú)一個(gè)數(shù)據(jù)庫(kù)呛梆,那么后期程序維護(hù)工作將會(huì)很龐大,所以我們需要做到一個(gè)庫(kù)對(duì)應(yīng)n個(gè)站
2. 功能實(shí)現(xiàn)
拆分需求后滞磺,接下來(lái)我們要挨個(gè)實(shí)現(xiàn)每個(gè)需求對(duì)應(yīng)的功能保礼。
新聞抓取
看到抓取時(shí)目派,首先想到的是HtmlAgilityPack,Github鏈接是https://github.com/zzzprojects/html-agility-pack谅摄,HtmlAgilityPack可以加載html,并且提供了函數(shù)SelectNodes闽寡,可以非常方便我們定位到需要抓取的DOM節(jié)點(diǎn)。下面看看這個(gè)函數(shù)的示例(http://html-agility-pack.net/select-nodes):
var htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(html);
string name = htmlDoc.DocumentNode
.SelectNodes("http://td/input")
.First()
.Attributes["value"].Value;'
這里需要重點(diǎn)關(guān)注的是SelectNodes的參數(shù)//td/input,這個(gè)參數(shù)名叫XPath,那么我們?cè)趺慈〉揭粋€(gè)網(wǎng)站某個(gè)節(jié)點(diǎn)的XPath呢,可以通過Chrome瀏覽器直接獲取。首先打開我們的目標(biāo)網(wǎng)站:http://www.southcn.com/pc2016/yw/node_346416.htm结洼,我們要抓取的是列表部分,如下圖:
確定好目標(biāo)后叉跛,我們可以通過Chrome直接復(fù)制出XPath松忍,如下圖:
Copy完XPath之后,我們可以貼出來(lái)看看XPath://*[@id="content"]/div[1]/div[1]/div[1]/div/h3/a
然后在Chrome的Console里面輸入:
$x('//*[@id="content"]/div[1]/div[1]/div[1]/div/h3/a')
我們看看能得到什么:
只有一個(gè)a鏈接筷厘,可是我們要獲取的是整個(gè)列表鸣峭,這與我們的需求不符宏所,那么如何理解這句XPath代表的含義呢,我們需要查看下XPath的語(yǔ)法:http://www.w3school.com.cn/xpath/xpath_syntax.asp摊溶。
了解語(yǔ)法后爬骤,我們了解到XPath路勁(//[@id="content"]/div[1]/div[1]/div[1]/div/h3/a)指定了具體的某個(gè)div,我們只要修改下就好:'//[@id="content"]/div[1]/div[1]/div/div/h3/a'莫换,重新在Console里面輸入:
$x('//*[@id="content"]/div[1]/div[1]/div/div/h3/a')
這時(shí)候得到的就是整個(gè)列表的a鏈接了:
拿到詳情頁(yè)的鏈接后霞玄,接下來(lái)我們要抓取正文內(nèi)容,打開詳情頁(yè):http://news.southcn.com/china/content/2017-12/26/content_179881431.htm
和之前列表一樣拉岁,獲取這幾個(gè)內(nèi)容的XPath
- 標(biāo)題://*[@id="article_title"]
- 時(shí)間://*[@id="pubtime_baidu"]
- 來(lái)源://*[@id="source_baidu"]
- 正文://*[@id="content"]
ok坷剧,現(xiàn)在我們可以抓取新聞了。
問題點(diǎn)匯總
抓取思路沒有問題喊暖,而在實(shí)際抓取的過程中總是會(huì)遇到一些細(xì)節(jié)問題惫企,這里匯總下
HtmlAgilityPack
HtmlAgilityPack 提供了一個(gè)Load函數(shù),可以直接加載網(wǎng)頁(yè):
var url = "http://html-agility-pack.net/";
var web = new HtmlWeb();
var doc = web.Load(url);
但是實(shí)際使用中
我們發(fā)現(xiàn)很多網(wǎng)頁(yè)加載下來(lái)后陵叽,竟然是亂碼雅任,而導(dǎo)致亂碼的原因是不同的網(wǎng)站,采用的編碼不一樣咨跌,而Load函數(shù)并沒有設(shè)置編碼的地方……
所以果斷放棄Load沪么,自己寫代碼加載網(wǎng)頁(yè)Html:
private string GetHtml(string url,string encoding)
{
using (var client = new WebClient())
{
client.Encoding = Encoding.GetEncoding(encoding);
var html = client.DownloadString(url);
return html;
}
}
同一個(gè)站點(diǎn)的XPath也會(huì)不一樣
很多網(wǎng)站的新聞詳情頁(yè)會(huì)采用不同的模板來(lái)顯示,比如說視頻+正文锌半、圖片幻燈片+正文禽车、正文等不同的組合方式。而這個(gè)時(shí)候要正確抓取數(shù)據(jù)刊殉,就需要同時(shí)記錄多個(gè)XPath殉摔,做好非空判斷,挨次抓取记焊。
正文中的腳本處理
有些網(wǎng)站會(huì)在正文中嵌入廣告腳本逸月,而這個(gè)時(shí)候抓取這些腳本顯然對(duì)我們沒什么幫助,所以要對(duì)正文內(nèi)容過濾下遍膜,去除所有的腳本:
var articleContent = contentDoc.InnerHtml;
//移除正文中的script腳本
Regex rRemScript = new Regex(@"<script[^>]*>[\s\S]*?</script>");
articleContent = rRemScript.Replace(articleContent, "");