1. 了解需求
公司負責運營的同事找到我,說需要搭建一個新聞站群,用于引入流量。每個站的新聞數據都去別的新聞站抓取,每天晚上10點更新數據,每個站還需要單獨配置SEO(首頁、頻道頁、詳情頁)、友情鏈接內容。
拆分需求
- 新聞抓取,每個新聞站的抓取數據源都不一致,所以抓取我們需要靈活配置。
- 每天定時,所以我們需要一個Windows 服務,定時完成抓取任務。
- 新聞站群,意味著會有很多站,如果每個站單獨一個數據庫,那么后期程序維護工作將會很龐大,所以我們需要做到一個庫對應n個站
2. 功能實現
拆分需求后,接下來我們要挨個實現每個需求對應的功能。
新聞抓取
看到抓取時,首先想到的是HtmlAgilityPack,Github鏈接是https://github.com/zzzprojects/html-agility-pack,HtmlAgilityPack可以加載html,并且提供了函數SelectNodes,可以非常方便我們定位到需要抓取的DOM節點。下面看看這個函數的示例(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;'
這里需要重點關注的是SelectNodes的參數//td/input,這個參數名叫XPath,那么我們怎么取到一個網站某個節點的XPath呢,可以通過Chrome瀏覽器直接獲取。首先打開我們的目標網站:http://www.southcn.com/pc2016/yw/node_346416.htm,我們要抓取的是列表部分,如下圖:
確定好目標后,我們可以通過Chrome直接復制出XPath,如下圖:
Copy完XPath之后,我們可以貼出來看看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')
我們看看能得到什么:
只有一個a鏈接,可是我們要獲取的是整個列表,這與我們的需求不符,那么如何理解這句XPath代表的含義呢,我們需要查看下XPath的語法:http://www.w3school.com.cn/xpath/xpath_syntax.asp。
了解語法后,我們了解到XPath路勁(//[@id="content"]/div[1]/div[1]/div[1]/div/h3/a)指定了具體的某個div,我們只要修改下就好:'//[@id="content"]/div[1]/div[1]/div/div/h3/a',重新在Console里面輸入:
$x('//*[@id="content"]/div[1]/div[1]/div/div/h3/a')
這時候得到的就是整個列表的a鏈接了:
拿到詳情頁的鏈接后,接下來我們要抓取正文內容,打開詳情頁:http://news.southcn.com/china/content/2017-12/26/content_179881431.htm
和之前列表一樣,獲取這幾個內容的XPath
- 標題://*[@id="article_title"]
- 時間://*[@id="pubtime_baidu"]
- 來源://*[@id="source_baidu"]
- 正文://*[@id="content"]
ok,現在我們可以抓取新聞了。
問題點匯總
抓取思路沒有問題,而在實際抓取的過程中總是會遇到一些細節問題,這里匯總下
HtmlAgilityPack
HtmlAgilityPack 提供了一個Load函數,可以直接加載網頁:
var url = "http://html-agility-pack.net/";
var web = new HtmlWeb();
var doc = web.Load(url);
但是實際使用中
我們發現很多網頁加載下來后,竟然是亂碼,而導致亂碼的原因是不同的網站,采用的編碼不一樣,而Load函數并沒有設置編碼的地方……
所以果斷放棄Load,自己寫代碼加載網頁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;
}
}
同一個站點的XPath也會不一樣
很多網站的新聞詳情頁會采用不同的模板來顯示,比如說視頻+正文、圖片幻燈片+正文、正文等不同的組合方式。而這個時候要正確抓取數據,就需要同時記錄多個XPath,做好非空判斷,挨次抓取。
正文中的腳本處理
有些網站會在正文中嵌入廣告腳本,而這個時候抓取這些腳本顯然對我們沒什么幫助,所以要對正文內容過濾下,去除所有的腳本:
var articleContent = contentDoc.InnerHtml;
//移除正文中的script腳本
Regex rRemScript = new Regex(@"<script[^>]*>[\s\S]*?</script>");
articleContent = rRemScript.Replace(articleContent, "");