Argo


  我的地盘Ajax做主
posts - 4, comments - 27, trackbacks - 0, articles - 2
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

Request.Browser.Crawler 属性的工作原理分析。

Posted on 2008-01-12 12:47 Argo 阅读(1471) 评论(10)  编辑 收藏 网摘 所属分类: ASP.NET

       昨天同事问我如何判断一个请求是否为搜索引擎,我的回答是通过UserAgent字符串自己去判断,而他告诉我.net提供了Crawler属性。我还真没有使用过这个属性,但是我第一反应是微软做的这东西怎么可能把所有搜索引擎都能判断出来呢?这是不可能的阿,至少据我所知网络中还有很多“特殊的搜索引擎”比如说Email的爬虫。我想研究一下什么样特征的搜索引擎能够让Crawler属性为真。

       在Web项目中大家都知道有一个Request这个对象,这个对象下面还有一个Browser属性,是描述浏览器的一些属性的类实例,这个Browser下面还有一个布尔值属性Crawler,下面是MSND的解释:

Crawler

让我们看看它的值是如何得到的,以下代码均通过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。

Feedback

#1楼    回复  引用  查看    

2008-01-12 12:52 by 阿不      
如果对这个属性有提供对Spider的UserAgent特征的配置那就有用了。如果没有,这个属性基本就废了。

#2楼 [楼主]   回复  引用  查看    

2008-01-12 13:09 by Argo      
@阿不

同意。我看代码的时候发现默认工厂BrowserCapabilitiesFactory中还有一个虚方法protected virtual void CrawlerProcessGateways(NameValueCollection headers, HttpBrowserCapabilities browserCaps) 我想还是有办法的。还没有时间研究,也许这里还是可以扩展一下的。


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;
}




#3楼    回复  引用  查看    

2008-01-12 16:21 by 武眉博<活靶子.Net>      
自己判断UserAgentMS这个连百度都没有更不要说sina的、有道的等等了
这个方法简直是比鸡肋还鸡肋。

#4楼    回复  引用  查看    

2008-01-12 18:26 by Klesh Wong      
"根据一个私有静态变量中的类型字符串创建出来的"
呵,很奇怪设计呀,要是把这个串可以优先读取配置文件的设置就可以用自己的类去替换掉了呢。

#5楼    回复  引用  查看    

2008-01-12 19:23 by Yoshow      
什么时候加上 Baiduspider 这个判断, 就是我用这个属性的时候, ^_^

#6楼 [楼主]   回复  引用  查看    

2008-01-12 22:27 by Argo      
@Klesh Wong

是啊,这里的代码还没有时间仔细看。等有时间仔细看看那个字符串,也许那里我们可以鸡肋属性,扩展出来。

#7楼 [楼主]   回复  引用  查看    

2008-01-12 22:28 by Argo      
@Yoshow

我也是,百度的行为还是非常流氓地,不好判断阿。他的蜘蛛有匿名的。

#8楼    回复  引用  查看    

2008-01-12 22:50 by Edwin Liu      
关注

#9楼    回复  引用    

2008-01-15 09:26 by niuniu [未注册用户]
扩展crawler 支持的搜索引擎方法
The IIS uses the data in the <browsercaps> section in machine.config or web.config to determine the client browser is a crawler or not. Currently the crawler filter information is all blank, that's why you'd always get false.
To fix this problem, you should make change to machine.config by adding the following crawler filters in the <browsercaps> section. If you just want to apply the the change to a specific website, you should add these crawler filters into the <system.web> section in web.config.

<browserCaps>
<filter>
<!-- SEARCH ENGINES GROUP -->
<!-- check Google (Yahoo uses this as well) -->
<case match="^Googlebot(\-Image)?/(?'version'(?'major'\d+)(?'minor'\.\d+)).*">
browser=Google
version=${version}
majorversion=${major}
minorversion=${minor}
crawler=true
</case>
<!-- check Google -->
<case match="Googlebot">
browser=Googlebot
crawler=true
</case>

<!-- check Alta Vista (Scooter) -->
<case match="^Scooter(/|-)(?'version'(?'major'\d+)(?'minor'\.\d+)).*">
browser=AltaVista
version=${version}
majorversion=${major}
minorversion=${minor}
crawler=true
</case>

<!-- check Alta Vista (Mercator) -->
<case match="Mercator">
browser=AltaVista
crawler=true
</case>

<!-- check Slurp (Yahoo uses this as well) -->
<case match="Slurp">
browser=Slurp
crawler=true
</case>

<!-- check MSN -->
<case match="MSNBOT">
browser=MSN
crawler=true
</case>
<!-- check Northern Light -->
<case match="^Gulliver/(?'version'(?'major'\d+)(?'minor'\.\d+)).*">
browser=NorthernLight
version=${version}
majorversion=${major}
minorversion=${minor}
crawler=true
</case>

<!-- check Excite -->
<case match="ArchitextSpider">
browser=Excite
crawler=true
</case>

<!-- Lycos -->
<case match="Lycos_Spider">
browser=Lycos
crawler=true
</case>

<!-- Ask Jeeves -->
<case match="Ask Jeeves">
browser=AskJeaves
crawler=true
</case>

<!-- check Fast -->
<case match="^FAST-WebCrawler/(?'version'(?'major'\d+)(?'minor'\.\d+)).*">
browser=Fast
version=${version}
majorversion=${major}
minorversion=${minor}
crawler=true
</case>

<!-- IBM Research Web Crawler -->
<case match="http\:\/\/www\.almaden.ibm.com\/cs\/crawler">
browser=IBMResearchWebCrawler
crawler=true
</case>

</filter>
</browserCaps>

#10楼 [楼主]   回复  引用  查看    

2008-01-15 11:12 by Argo      
@niuniu

非常棒,果然是可以配置的。

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2008-01-14 11:02 编辑过
Google站内搜索

China-pub 计算机图书网上专卖店!6.5万品种 2-8折!
近千种 9-95 新二手计算图书火热销售中!
开发者征途系统新作:《设计模式——基于C#的工程化实现及扩展》



相关文章:

相关链接: