C#實現(xiàn)百度網(wǎng)站收錄和排名查詢功能思路及實例
一、前言
偶然一次在vs2012默認的項目文件夾里發(fā)現(xiàn)了以前自己做的一個關(guān)于SEO的類庫,主要是用來查詢某個網(wǎng)址的收錄次數(shù)還有網(wǎng)站的排行數(shù),后來重構(gòu)了下,今天拿出來寫篇文章,說說自己是如何思考的并完成的。
二、問題描述
首先需要考慮的是能夠支持哪些搜索引擎的查詢,首先是百度,然后是必應、搜狗、搜搜、360。本來想支持Google但是一想不對,根本不好訪問的,所以暫時不算在內(nèi)。而我們實際要做的就是根據(jù)一個網(wǎng)址能夠檢索出這個網(wǎng)址的在各個搜索引擎的收錄次數(shù)以及在不同關(guān)鍵詞下的網(wǎng)址排行,這里出入的只有網(wǎng)址還有若干的關(guān)鍵詞,而輸出則是該網(wǎng)址在不同搜索引擎下的收錄次數(shù)以及在各個關(guān)鍵詞下的排行數(shù)。
但是這里有個問題,就是排行數(shù),如果檢索的網(wǎng)址在前100還好,如果排名很后面,那么問題就來了,那樣會讓用戶等待很長時間才能看到結(jié)果,但是用戶可能只想知道排行前100的具體排名,而那些超過的則只要顯示100以后就可以了,而這些就需要我們前期考慮好,這樣后面的程序才好做。
三、解決思路
相信很多人都能夠想到,就是利用WebClient將將需要的頁面下載下來,然后用正則從中獲取我們感興趣的部分,然后利用程序去處理。而關(guān)鍵難度就是在這個正則的編寫,首先我們先從簡單的開始。
四、收錄次數(shù)
首先是網(wǎng)站的收錄次數(shù),我們可以在百度中輸入site:www.cnblogs.com/然后我們就可以看到如下的頁面:
而我們所需要的收錄次數(shù)就是 5,280,000 這段數(shù)字,我們接著查看頁面元素:
接著我們再觀察其他的搜索引擎可以發(fā)現(xiàn)都是類似的,所以我們的思路這個時候應該就得出了,最后就是如何組織網(wǎng)址,這部分我們看地址欄?wd=site%3Awww.cnblogs.com%2F這段就知道怎么寫了。
稍等這個時候我們可能心急一個一個實現(xiàn),這樣后面我們就沒法集中的調(diào)用,同時也會影響以后的新增,所以我們要規(guī)定一個要實現(xiàn)收錄數(shù)功能的抽象類,這樣就能夠在不知曉具體實現(xiàn)的情況統(tǒng)一使用,并且還能夠在以后輕松的新增新的搜索引擎,而這種方式屬于策略模式(Stategry),下面我們來慢慢分析出這個抽象類的具體內(nèi)容。
首先每個實現(xiàn)這個抽象類的具體類都應該是對應某個搜索引擎,那么就需要有一個基本網(wǎng)址,同時還要留下占位符,比如根據(jù)上面百度的這個我們就得出這樣一個字符串
http://www.baidu.com/s?wd=site%3A{0}
其中{0}就是為真正需要檢索網(wǎng)址的占位符,獲取下載頁面的路徑是所有具體類都需要的所以我們直接將實現(xiàn)放在抽象類中,比如下面的代碼:
/// <summary>
/// 服務提供者
/// </summary>
protected String SearchProvider { get; set; }
/// <summary>
/// 需要檢索的網(wǎng)址
/// </summary>
protected String SiteUrl { get; set; }
/// <summary>
/// 搜索服務提供網(wǎng)址
/// </summary>
protected String BaseUrl { get; set; }
/// <summary>
/// 后頁面網(wǎng)址
/// </summary>
/// <param name="site">需要查詢的網(wǎng)址</param>
/// <returns>拼接后的網(wǎng)址</returns>
protected String GetDownUrl(string site)
{
return string.Format(BaseUrl, HttpUtility.UrlEncode(site));
}
其中SiteUrl和SearchProvider是用來保存檢索網(wǎng)址和搜索引擎名稱。
上面我們說了將會利用WebClient來下載頁面,所以初始化WebClient的工作也在抽象類中完成,盡可能的減少重復代碼,而為了防止阻塞當前線程所以我們采用了Async方法。
具體代碼如下所示:
/// <summary>
/// 查詢在該搜索引擎中的收錄次數(shù)
/// </summary>
/// <param name="siteurl">網(wǎng)站URL</param>
public void SearchIncludeCount(string siteurl)
{
SiteUrl = siteurl;
WebClient client = new WebClient();
client.Encoding = Encoding.UTF8;
client.DownloadStringCompleted += DownloadStringCompleted;
client.DownloadStringAsync(new Uri(GetDownUrl(siteurl)));
}
/// <summary>
/// 檢索收錄次數(shù)的具體實現(xiàn)
/// 子類必須要實現(xiàn)該方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected abstract void DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e);
當WebClient完成下載后將會回調(diào)DownloadStringCompleted方法,而這個方法的是抽象方法也就意味著具體類必須要實現(xiàn)這個方法。
雖然我們內(nèi)部的實現(xiàn)是異步的但是對于其他開發(fā)者調(diào)用這個方法還是同步的,所以我們就需要借助委托因此我們還要新建一個委托類型:
/// <summary>
/// 當完成一個網(wǎng)站的收錄查詢后回調(diào)
/// </summary>
public Action<SiteIncludeCountResult> OnComplatedOneSite { get; set; }
其中SiteIncludeCountResult的結(jié)構(gòu)如下所示:
/// <summary>
/// 用于網(wǎng)站收錄中委托的參數(shù)
/// </summary>
public class SiteIncludeCountResult
{
/// <summary>
/// 收錄次數(shù)
/// </summary>
public long IncludeCount { get; set; }
/// <summary>
/// 搜索引擎類型
/// </summary>
public String SearchType { get; set; }
/// <summary>
/// 網(wǎng)站URL
/// </summary>
public String SiteUrl { get; set; }
}
最后還有一個方法用于DownloadStringCompleted完成后回調(diào)OnComplatedOneSite委托:
/// <summary>
/// 完成處理后調(diào)用該方法將結(jié)果返回
/// </summary>
/// <param name="result">網(wǎng)址的收錄數(shù)結(jié)果</param>
protected void SetCompleted(SiteIncludeCountResult result)
{
if (OnComplatedOneSite != null)
OnComplatedOneSite(result);
}
這樣我們需要的抽象類就完成了,下面我們就可以開始實現(xiàn)第一個了,通過上面的截圖我們可以發(fā)現(xiàn)要匹配這段字符串的正則表達式很簡單:
百度為您找到相關(guān)結(jié)果約([\w,]+?)個
最后再將獲取的字符串去掉逗號就可以強制轉(zhuǎn)換了,這樣結(jié)果就出來了,具體實現(xiàn)就像下面這樣:
/// <summary>
/// 百度網(wǎng)站收錄次數(shù)查詢
/// </summary>
public class BaiDuSiteIncludeCount : SiteIncludeCountBase
{
public BaiDuSiteIncludeCount()
{
BaseUrl = "http://www.baidu.com/s?wd=site%3A{0}";
SearchProvider = "百度";
}
protected override void DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
var result = new SiteIncludeCountResult();
result.SiteUrl = SiteUrl;
result.SearchType = SearchProvider;
result.IncludeCount = 0;
Regex reg = new Regex(@"百度為您找到相關(guān)結(jié)果約([\w,]+?)個", RegexOptions.IgnoreCase | RegexOptions.Singleline);
var matchs = reg.Matches(e.Result);
if (matchs.Count > 0)
{
string count = matchs[0].Groups[1].Value.Replace(",", "");
result.IncludeCount = long.Parse(count);
}
SetCompleted(result);
}
}
以此類推,其他的都是按照這種就可以了,有興趣的可以下載我的源碼查看。
五、關(guān)鍵詞排名
我們按照之前的思路,還是要先規(guī)定一個抽象類,但是其結(jié)構(gòu)跟上面的抽象類很相似,所以筆者這里直接給出具體的代碼:
/// <summary>
/// 實現(xiàn)關(guān)鍵詞查詢必須繼承該類
/// </summary>
public abstract class KeyWordsSeoBase
{
protected String BaseUrl { get; set; }
protected String SearchProvider { get; set; }
protected String GetDownUrl(string keyword, string site, long current)
{
return String.Format(BaseUrl, HttpUtility.UrlEncode(keyword), current);
}
protected void SetCompleted(KeyWordsSeoResult result)
{
if (OnComplatedOneKeyWord != null)
{
OnComplatedOneKeyWord(result);
}
}
/// <summary>
/// 完成一個關(guān)鍵詞的查詢后回調(diào)該委托
/// </summary>
public Action<KeyWordsSeoResult> OnComplatedOneKeyWord { get; set; }
/// <summary>
/// 查詢指定關(guān)鍵詞和網(wǎng)站在該搜索引擎中的排行
/// 子類需要重寫該方法
/// </summary>
/// <param name="keywords">關(guān)鍵詞</param>
/// <param name="site">網(wǎng)站URL</param>
public abstract void SearchRanking(IEnumerable<string> keywords, string site,long count);
}
最大的區(qū)別在于具體的實現(xiàn)全部集中在SearchRanking中,通過keywords參數(shù)可以看出我們會支持多個關(guān)鍵詞的查詢,最后不同的就是下載路徑的組織,因為涉及到翻頁所以多了一個參數(shù)。
其中KeyWordsSeoResult的結(jié)構(gòu)如下所示:
/// <summary>
/// 用于關(guān)鍵詞排行查詢的委托參數(shù)
/// </summary>
public class KeyWordsSeoResult
{
/// <summary>
/// 搜索引擎類型
/// </summary>
public String SearchType { get; set; }
/// <summary>
/// 關(guān)鍵詞
/// </summary>
public String KeyWord { get; set; }
/// <summary>
/// 排行
/// </summary>
public long Ranking { get; set; }
}
廢話不多說,我們來看百度的搜索結(jié)果頁:
以上是筆者在百度中搜索程序員的排名第九個的html結(jié)構(gòu),或許你會覺得很簡單只要獲取div的id以及網(wǎng)址就可以了,但是很多搜索引擎的路徑并不是直接的路徑,而是會先鏈到百度然后重定向的,如果非要匹配我們就需要多做一件事就是訪問這個路徑得到真實的路徑,那樣就會加大這中間的等待時間,所以筆者采用的是直接截取上圖中的<span class=”g”>后面的內(nèi)容,這樣就避免了一次請求。(不知道當初筆者怎么想的,實現(xiàn)的時候并沒有采用id那個值而是在內(nèi)部遞增,估計這個id的序號在翻頁后會出現(xiàn)問題吧),最后亮出我們神圣的正則表達式:
<span\s+class=""(?:g|c-showurl)"">([^/&]*)
以為這樣就大公告成了?錯了,在某些結(jié)果里面百度會給這個網(wǎng)址加上b標簽,而筆者則采用全部趕盡殺絕的方式,利用正則全部刪掉(反正又不看頁面,只要拿到我想要的就OK了),實現(xiàn)的時候我們可不能直接實現(xiàn)多個關(guān)鍵詞的判明,應該是實現(xiàn)一個關(guān)鍵詞的,然后循環(huán)調(diào)用即可了,下面是筆者的單個關(guān)鍵詞的實現(xiàn):
protected KeyWordsSeoResult SearchFunc(string key, string siteurl, long total)
{
var result = new KeyWordsSeoResult();
result.KeyWord = key;
result.Ranking = total + 1;
var reg = new Regex(@"<span\s+class=""(?:g|c-showurl)"">([^/&]*)", RegexOptions.IgnoreCase | RegexOptions.Singleline);
var replace = new Regex("</?b>", RegexOptions.IgnoreCase | RegexOptions.Singleline);
var client = new WebClient();
long current = 0;
long pos = 0;
for (; ; )
{
String url = GetDownUrl(key, siteurl, current);
String downstr = client.DownloadString(url);
downstr = replace.Replace(downstr, "");
var matchs = reg.Matches(downstr);
foreach (Match match in matchs)
{
pos++;
string suburl = match.Groups[1].Value;
try
{
if (suburl.ToLower() == siteurl.ToLower())
{
result.Ranking = pos;
return result;
}
}
catch
{
continue;
}
}
current += 10;
if (current > total)
{
current -= 10;
if (current >= total)
{
break;
}
current = total;
}
}
return result;
}
注意for循環(huán)的結(jié)束部分,這里是用來處理分頁的,以翻到下一頁繼續(xù)檢索。其他的大體部分都跟筆者說的一樣,下載頁面->正則匹配->根據(jù)匹配結(jié)果判斷。剩下的就是SearchRanking的實現(xiàn),就是循環(huán)關(guān)鍵詞,只是這里筆者為每個搜索引擎新建線程來實現(xiàn),當然這不怎么好,所以讀者可以改用更好的方式來做:
public override void SearchRanking(IEnumerable<string> keywords, string site, long count)
{
new Thread(() =>
{
foreach (string key in keywords)
{
KeyWordsSeoResult result = SearchFunc(key, site, count);
result.SearchType = SearchProvider;
SetCompleted(result);
}
}).Start();
}
六、統(tǒng)一管理
有了這些我們就可以寫出一個簡潔的類來負責管理,筆者這里直接給出代碼:
/// <summary>
/// 查詢網(wǎng)站的收錄次數(shù)以及排行
/// </summary>
public class RankingAndIncludeSeo
{
/// <summary>
/// 關(guān)鍵詞列表
/// </summary>
public IList<KeyWordsSeoBase> KeyWordsSeoList { get; private set; }
/// <summary>
/// 收錄次數(shù)列表
/// </summary>
public IList<SiteIncludeCountBase> SiteIncludeCountList { get; private set; }
public RankingAndIncludeSeo()
{
KeyWordsSeoList = new List<KeyWordsSeoBase>();
SiteIncludeCountList = new List<SiteIncludeCountBase>();
}
/// <summary>
/// 當完成一個關(guān)鍵詞的查詢后回調(diào)該委托
/// </summary>
public Action<KeyWordsSeoResult> OnComplatedAnyKeyWordsSearch { get; set; }
/// <summary>
/// 當完成一個網(wǎng)站的收錄次數(shù)查詢后回調(diào)該委托
/// </summary>
public Action<SiteIncludeCountResult> OnComplatedAnySiteIncludeSearch { get; set; }
/// <summary>
/// 查詢網(wǎng)址的排行
/// </summary>
/// <param name="keywords">關(guān)鍵詞組</param>
/// <param name="siteurl">查詢的網(wǎng)址</param>
/// <param name="count">最大限制排行數(shù)</param>
public void SearchKeyWordsRanking(IEnumerable<string> keywords, string siteurl, long count = 100)
{
if (keywords == null)
throw new ArgumentNullException("keywords", "必須存在關(guān)鍵詞");
if (siteurl == null)
throw new ArgumentNullException("siteurl", "必須存在網(wǎng)站URL");
foreach (KeyWordsSeoBase kwsb in KeyWordsSeoList)
{
kwsb.OnComplatedOneKeyWord = kwsb.OnComplatedOneKeyWord ?? OnComplatedAnyKeyWordsSearch;
kwsb.SearchRanking(keywords, siteurl, count);
}
}
/// <summary>
/// 查詢網(wǎng)址的收錄次數(shù)
/// </summary>
/// <param name="siteurl">查詢的網(wǎng)址</param>
public void SearchSiteIncludeCount(string siteurl)
{
if (siteurl == null)
throw new ArgumentNullException("siteurl", "必須指定網(wǎng)站");
foreach (SiteIncludeCountBase sicb in SiteIncludeCountList)
{
sicb.OnComplatedOneSite = sicb.OnComplatedOneSite ?? OnComplatedAnySiteIncludeSearch;
sicb.SearchIncludeCount(siteurl);
}
}
}
RankingAndIncludeSeo中提供了公共的委托,如果單個搜索引擎沒有提供委托那么就采用這個公共的,如果已經(jīng)指定了單獨的委托就不會被賦值了,而其他開發(fā)者調(diào)用的時候只要向KeyWordsSeoList和SiteIncludeCountList中添加已經(jīng)實現(xiàn)的類就可以了,方面其他開發(fā)者開發(fā)出自己的實現(xiàn)并加入其中。
七、小節(jié)
這篇隨筆總的來說并不是講述什么高端技術(shù)的,僅僅只是提供一種大致的思路以及結(jié)構(gòu)上的設(shè)計,如果讀者需要應用于實際開發(fā)中,最好加以驗證,筆者并不能保證關(guān)鍵詞的排名沒有任何誤差,因為搜索的結(jié)果會由于任何因素發(fā)生改變。
- C#微信小程序服務端獲取用戶解密信息實例代碼
- C#微信開發(fā)之獲取接口調(diào)用憑據(jù)
- C#微信公眾平臺開發(fā)之a(chǎn)ccess_token的獲取存儲與更新
- C#微信公眾號開發(fā)之接收事件推送與消息排重的方法
- C#開發(fā)微信公眾號接口開發(fā)
- C#微信公眾平臺開發(fā)之高級群發(fā)接口
- C#.net 微信公眾賬號接口開發(fā)
- c#封裝百度web服務geocoding api 、百度坐標轉(zhuǎn)換示例
- C#實現(xiàn)百度ping推送功能的方法
- 如何根據(jù)百度地圖計算出兩地之間的駕駛距離(兩種語言js和C#)
- C#實現(xiàn)解析百度天氣數(shù)據(jù),Rss解析百度新聞以及根據(jù)IP獲取所在城市的方法
- C#實現(xiàn)微信結(jié)合百度api獲取當前用戶地理位置的方法
相關(guān)文章
C# 批量生成隨機密碼必須包含數(shù)字和字母并用加密算法加密
這篇文章主要介紹了C# 批量生成隨機密碼必須包含數(shù)字和字母并用加密算法加密,需要的朋友參考下2017-01-01C#用RabbitMQ實現(xiàn)消息訂閱與發(fā)布
在消息隊列模型中,如何將消息廣播到所有的消費者,這種模式成為“發(fā)布/訂閱”。本文主要以一個簡單的小例子,簡述通過fanout交換機,實現(xiàn)消息的發(fā)布與訂閱,僅供學習分享使用,如有不足之處,還請指正。2021-05-05DevExpress之ChartControl的SeriesTemplate實例
這篇文章主要介紹了DevExpress之ChartControl的SeriesTemplate用法實例,實現(xiàn)了餅狀Series百分比顯示的效果,具有一定的參考借鑒價值,需要的朋友可以參考下2014-10-10