昨天同事问我如何判断一个请求是否为搜索引擎,我的回答是通过UserAgent字符串自己去判断,而他告诉我.net提供了Crawler属性。我还真没有使用过这个属性,但是我第一反应是微软做的这东西怎么可能把所有搜索引擎都能判断出来呢?这是不可能的阿,至少据我所知网络中还有很多“特殊的搜索引擎”比如说Email的爬虫。我想研究一下什么样特征的搜索引擎能够让Crawler属性为真。
在Web项目中大家都知道有一个Request这个对象,这个对象下面还有一个Browser属性,是描述浏览器的一些属性的类实例,这个Browser下面还有一个布尔值属性Crawler,下面是MSND的解释:
让我们看看它的值是如何得到的,以下代码均通过Reflector查看System.web.dll获得。
public bool Crawler { get { if (!this._havecrawler) { this._crawler = this.CapsParseBool("crawler"); this._havecrawler = true; } return this._crawler; } }
很明显这里的CapsParseBool("crawler")是关键地方所在,我们继续看这个方法中如何实现的。
private bool CapsParseBool(string capsKey) { bool flag; try { flag = bool.Parse(this[capsKey]); } catch (FormatException exception) { throw this.BuildParseError(exception, capsKey); } return flag; }
我们看到这里就有些明白了,这里的flag就是存储当前请求是否为搜索引擎的变量。而这个值真正存储在this[capsKey]中,我们继续我们的探索。
public virtual string this[string key] { get { return (string) this._items[key]; } }
我们又进了一步,真正的值被存储在_items这个集合中,我们来看看它是如何定义的。
private IDictionary _items;
现在我们的问题来了:
这是一个字典接口,它是如何初始化并且得到这个crawler Key/Value键值对的呢?目前我们好像没有什么头绪继续向下查找这个Clawler键对应的值是如何得到的了。
我们回头理一下思路:
Crawler属性是Browser对象的,Browser又是Request类的一个属性。所以初始化Crawler有可能在Browser初始化的时候。Request这个对象是客户端发出请求达到服务器端后,服务器端负责初始化的。Browser对象一定会根据当前请求的Request来初始化自己,所以我们有必要看看Browser这个属性是如何实现的了。
public HttpBrowserCapabilities Browser { get { if (this._browsercaps == null) { if (!s_browserCapsEvaled) { lock (s_browserLock) { if (!s_browserCapsEvaled) { HttpCapabilitiesBase.GetBrowserCapabilities(this); } s_browserCapsEvaled = true; } } this._browsercaps = HttpCapabilitiesBase.GetBrowserCapabilities(this); } return this._browsercaps; } set { this._browsercaps = value; } }
this._browsercaps = HttpCapabilitiesBase.GetBrowserCapabilities(this);
正是我们要找到的,我们继续向下看。
internal static HttpBrowserCapabilities GetBrowserCapabilities(HttpRequest request) { HttpCapabilitiesBase base2 = null; HttpCapabilitiesEvaluator browserCaps = RuntimeConfig.GetConfig(request.Context).BrowserCaps; if (browserCaps != null) { base2 = browserCaps.Evaluate(request); } return (HttpBrowserCapabilities) base2; }
我们又前进了一大步,代码印证了前面我们的假象,它的确是通过当前Request来初始化自己的,让我们来查看browserCaps.Evaluate(request);
这个方法的代码还是有一点多的,为了版面就先部全部放上来了。代码虽然多,但是还是容易看懂的,其中有很多都是缓存判断和处理的。因为这个方法返回值就是我们一直关注的Browser属性对应的HttpCapabilitiesBase类,我的注意力全都放在这个返回值result上了,通过阅读代码得到result共有两个途径:缓存、EvaluateFinal方法。让我们来看看这个EvaluateFinal方法吧,我要看到胜利的曙光了。
internal HttpCapabilitiesBase EvaluateFinal(HttpRequest request, bool onlyEvaluateUserAgent) { HttpBrowserCapabilities httpBrowserCapabilities = this.BrowserCapFactory.GetHttpBrowserCapabilities(request); CapabilitiesState state = new CapabilitiesState(request, httpBrowserCapabilities.Capabilities); if (onlyEvaluateUserAgent) { state.EvaluateOnlyUserAgent = true; } if (this._rule != null) { string str = httpBrowserCapabilities["isMobileDevice"]; httpBrowserCapabilities.Capabilities["isMobileDevice"] = null; this._rule.Evaluate(state); string str2 = httpBrowserCapabilities["isMobileDevice"]; if (str2 == null) { httpBrowserCapabilities.Capabilities["isMobileDevice"] = str; } else if (str2.Equals("true")) { httpBrowserCapabilities.DisableOptimizedCacheKey(); } } HttpCapabilitiesBase base2 = (HttpCapabilitiesBase) HttpRuntime.CreateNonPublicInstance(this._resultType); base2.InitInternal(httpBrowserCapabilities); return base2; }
太棒了,第一句话就告诉我们Browser属性的初始化是在BrowserCapFactory工厂中完成的。那我们到工厂中看看吧!
internal BrowserCapabilitiesFactoryBase BrowserCapFactory { get { return BrowserCapabilitiesCompiler.BrowserCapabilitiesFactory; } }
这里微软用了抽象工厂设计模式,所以真正的工厂类是根据一个私有静态变量中的类型字符串创建出来的。
internal static BrowserCapabilitiesFactoryBase BrowserCapabilitiesFactory { get { if (_browserCapabilitiesFactoryBaseInstance == null) { lock (_lockObject) { if (_browserCapabilitiesFactoryBaseInstance == null) { _browserCapabilitiesFactoryType = GetBrowserCapabilitiesType(); if (_browserCapabilitiesFactoryType != null) { _browserCapabilitiesFactoryBaseInstance = (BrowserCapabilitiesFactoryBase) Activator.CreateInstance(_browserCapabilitiesFactoryType); } } } } return _browserCapabilitiesFactoryBaseInstance; } }
这难不倒我们,我们看一下只有BrowserCapabilitiesFactory继承了BrowserCapabilitiesFactoryBase工厂了,所以真正的代码应该是在这个工厂类中了,我们继续我们的探索,来查看一下工厂类的GetHttpBrowserCapabilities方法。
internal HttpBrowserCapabilities GetHttpBrowserCapabilities(HttpRequest request) { if (request == null) { throw new ArgumentNullException("request"); } NameValueCollection headers = request.Headers; HttpBrowserCapabilities browserCaps = new HttpBrowserCapabilities(); Hashtable hashtable = new Hashtable(180, StringComparer.OrdinalIgnoreCase); hashtable[string.Empty] = HttpCapabilitiesEvaluator.GetUserAgent(request); browserCaps.Capabilities = hashtable; this.ConfigureBrowserCapabilities(headers, browserCaps); return browserCaps; }
最终我们走到了ConfigureBrowserCapabilities这个虚方法上了。
public override void ConfigureBrowserCapabilities(NameValueCollection headers, HttpBrowserCapabilities browserCaps) { this.DefaultProcess(headers, browserCaps); if (base.IsBrowserUnknown(browserCaps)) { this.DefaultDefaultProcess(headers, browserCaps); } } private bool DefaultProcess(NameValueCollection headers, HttpBrowserCapabilities browserCaps) { IDictionary capabilities = browserCaps.Capabilities; .... capabilities["crawler"] = "false"; .... this.CrawlerProcess(headers, browserCaps); return true; } private bool CrawlerProcess(NameValueCollection headers, HttpBrowserCapabilities browserCaps) { IDictionary capabilities = browserCaps.Capabilities; string target = browserCaps[string.Empty]; RegexWorker worker = new RegexWorker(browserCaps); if (!worker.ProcessRegex(target, "crawler|Crawler|Googlebot|msnbot")) { return false; } capabilities["crawler"] = "true"; this.CrawlerProcessGateways(headers, browserCaps); bool ignoreApplicationBrowsers = false; this.CrawlerProcessBrowsers(ignoreApplicationBrowsers, headers, browserCaps); return true; } public bool ProcessRegex(string target, string regexExpression) { if (target == null) { target = string.Empty; } Regex regex = new Regex(regexExpression, RegexOptions.ExplicitCapture); Match match = regex.Match(target); if (!match.Success) { return false; } string[] groupNames = regex.GetGroupNames(); if (groupNames.Length > 0) { if (this._groups == null) { this._groups = new Hashtable(); } for (int i = 0; i < groupNames.Length; i++) { this._groups[groupNames[i]] = match.Groups[i].Value; } } return true; }
最终的代码终于被我们找到了,我们可以肯定地说Crawler属性是通过匹配正则表达式来实现,并且目前能判断出的搜索引擎只有UserAgent字符串中包含crawler|Crawler|Googlebot|msnbot的。比如说百度就不能被判断出来,因为百度的是Baiduspider。