汽車之家店鋪商品詳情數據抓取 DotnetSpider實戰[二]


一、遲到的下期預告

自從上一篇文章發布到現在,大約差不多有3個月的樣子,其實一直想把這個實戰入門系列的教程寫完,一個是為了支持DotnetSpider,二個是為了.Net 社區發展獻出一份綿薄之力,這個開源項目作者一直都在更新,相對來說還是很不錯的,上次教程的版本還是2.4.4,今天瀏覽了一下這個項目,最近一次更新是在3天前,已經更新到了2.5.0,而且項目star也已經超過1000了,還是挺受大家所喜愛的,也在這感謝作者們不斷的努力。

之所以中間這么長一段時間沒有好好寫文章,是因為筆者為參加3月份PMP考試備考了,然后考完差不多就到4月份,到了4月份,項目催的急,所以一直拖到了現在才有一點空余時間,希望我的文章完成之后,會對大家有一定的幫助。

二、更新DotnetSpider庫

 剛剛提到DotnetSpider升級到了2.5.0,所以我們更新一下庫,使用最新的版本,技術要與時俱進嘛,將下圖中兩個類庫添加進去就行了。

三、分析汽車之家商品詳情頁面

3.1分析商品詳情頁數據

 ①上次我們發現,當點擊參數配置的時候,頁面會發送兩個ajax請求,分別去獲取車型的基本參數,和配置參數,返回的數據是JSON格式。

 

 

 ②通過Chrome的Network抓包可以發現,這兩個請求有一個共同點,提交的參數都有data[specid]:28762,我猜測這應該是skuid,大家可以試着在瀏覽器直接打開這兩個地址,直接就可以返回出車型的相關信息,所以問題的關鍵就是要解決如何獲取skuid的問題,獲取到了這些車型的數據那不就是手到擒來了。

3.2如何獲取商品的sku

其實這個確實是讓我很苦惱的一個問題,因為我們打開頁面的時候頁面的鏈接中並未包含skuid,[https://mall.autohome.com.cn/detail/284641-0-0.html],所以從URL中獲取是不太現實的了,所以我用Element去頁面中搜索這個skuid的值,結果發現兩個地方有這個值,一個存在於HTML元素中,一個是存在js全局變量中,相比較而言,我認為HTML元素中的相對來說會比較好處理一點,直接獲取元素的屬性就行了。

 

四、開發

4.1准備Processor

這次為什么要單獨的寫一段准備Processor呢,因為此次准備了三個Processor,分別用來處理3個數據,第一個用於獲取skuid,第二個用於獲取車型基本參數,第三個用於獲取車型配置,細心的小伙伴們肯定會發現,這次我們每個Processor都使用了構造函數,里面可以清晰的看到有我們熟悉的正則表達式(PS正則表達式寫的很爛,自己有更好的正則寫法可以回復在評論里面,讓我膜拜一下,嘻嘻),肯定會有人問為什么要這么寫呢?

相比上次的教程,這次的抓取流程更為的復雜了,上次我們只是抓了一個列表頁,一個接口完全可以搞定,此次我們的流程變成了,第一步,我們需要獲取車型詳情頁的頁面,從頁面中找到skuid,第二部,將獲取的skuid拼接好request放入爬蟲的請求集合中,通過新構造的請求去獲取數據,那么我們怎么知道哪個請求用哪個Processor進行處理呢,DotnetSpider提供了對url進行校驗的進行判斷,使用哪個Processor對數據進行處理。 

        private class GetSkuProcessor : BasePageProcessor //獲取skuid
        {
            public GetSkuProcessor()
            {
                TargetUrlsExtractor = new RegionAndPatternTargetUrlsExtractor(".", @"^https:\/\/mall.autohome.com.cn\/detail\/*[0-9]+-[0-9]+-[0-9]+\.html$");
            }
            protected override void Handle(Page page)
            {
                string skuid = string.Empty;
                skuid = page.Selectable.XPath(".//a[@class='carbox-compare_detail']/@link").GetValue();
                page.AddResultItem("skuid", skuid);
                page.AddTargetRequest(@"https://mall.autohome.com.cn/http/data.html?data[_host]=//car.api.autohome.com.cn/v1/carprice/spec_paramsinglebyspecid.ashx&data[_appid]=mall&data[specid]=" + skuid);
                page.AddTargetRequest(@"https://mall.autohome.com.cn/http/data.html?data[_host]=//car.api.autohome.com.cn/v2/carprice/Config_GetListBySpecId.ashx&data[_appid]=mall&data[specid]=" + skuid);
            }

        }
        private class GetBasicInfoProcessor : BasePageProcessor //獲取車型基本參數
        {
            public GetBasicInfoProcessor()
            {
                TargetUrlsExtractor = new RegionAndPatternTargetUrlsExtractor(".", @"^https://mall\.autohome\.com\.cn/http/data\.html\?data\[_host\]=//car\.api\.autohome\.com\.cn/v1/carprice/spec_paramsinglebyspecid\.ashx*");
            }
            protected override void Handle(Page page)
            {
                page.AddResultItem("BaseInfo", page.Content);
            }
        }

        private class GetExtInfoProcessor : BasePageProcessor //獲取車型配置
        {
            public GetExtInfoProcessor()
            {
                TargetUrlsExtractor = new RegionAndPatternTargetUrlsExtractor(".", @"^https://mall\.autohome\.com\.cn\/http\/data\.html\?data\[_host\]=//car\.api\.autohome\.com\.cn/v2/carprice/Config_GetListBySpecId\.ashx*");
            }
            protected override void Handle(Page page)
            {
                page.AddResultItem("ExtInfo", page.Content);
            }
        }

4.2、創建Pipeline

 Pipeline基本上變化不大,稍微改造了一下,so easy。

        private class PrintSkuPipe : BasePipeline
        {

            public override void Process(IEnumerable<ResultItems> resultItems, ISpider spider)
            {
                foreach (var resultItem in resultItems)
                {
                    if (resultItem.GetResultItem("skuid") != null)
                    {
                        Console.WriteLine(resultItem.Results["skuid"] as string);
                    }
                    if (resultItem.GetResultItem("BaseInfo") != null)
                    {
                        var t = JsonConvert.DeserializeObject<AutoCarParam>(resultItem.Results["BaseInfo"]);
                        //Console.WriteLine(resultItem.Results["BaseInfo"]);
                    }
                    if (resultItem.GetResultItem("ExtInfo") != null)
                    {
                        var t = JsonConvert.DeserializeObject<AutoCarConfig>(resultItem.Results["ExtInfo"]);
                        //Console.WriteLine(resultItem.Results["ExtInfo"]);
                    }

                }
                
            }
        }

新增兩個實體類AutoCarParam,AutoCarConfig,其實有重復的,子項可以再進行抽象一下,代碼可以減少,還可以節省一點硬盤空間

    public class AutoCarConfig
    {
        public string message { get; set; }
        public ConfigResult result { get; set; }
        public string returncode { get; set; }
    }
    public class ConfigResult
    {
        public string specid { get; set; }

        public List<configtypeitem> configtypeitems { get; set; }
    }

    public class configtypeitem
    {
        public string name { get; set; }
        public List<configitem> configitems { get; set; }
    }
    public class configitem
    {
        public string name { get; set; }
        public string value { get; set; }
    }

    public class AutoCarParam
    {
        public string message { get; set; }
        public ParamResult result { get; set; }
        public string returncode { get; set; }
    }

    public class ParamResult
    {
        public string specid { get; set; }

        public List<paramtypeitem> paramtypeitems { get; set; }
    }

    public class paramtypeitem
    {
        public string name { get; set; }
        public List<paramitem> paramitems { get;set;}
    }
    public class paramitem
    {
        public string name { get; set; }
        public string value { get; set; }
    }

 

 4.3、構造爬蟲

這一塊變化也不是很大,變化的的地方看我的注釋,因為我們需要有多個Processor,把這幾個都添加進去就行。

            var site = new Site
            {
                CycleRetryTimes = 1,
                SleepTime = 200,
                Headers = new Dictionary<string, string>()
                {
                    { "Accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8" },
                    { "Cache-Control","no-cache" },
                    { "Connection","keep-alive" },
                    { "Content-Type","application/x-www-form-urlencoded; charset=UTF-8" },
                    { "User-Agent","Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36"}
                }

            };
            List<Request> resList = new List<Request>();
            Request res = new Request();
            res.Url = "https://mall.autohome.com.cn/detail/284641-0-0.html";
            res.Method = System.Net.Http.HttpMethod.Get;
            resList.Add(res);
            var spider = Spider.Create(site, new QueueDuplicateRemovedScheduler(), new GetSkuProcessor(),new GetBasicInfoProcessor(),new GetExtInfoProcessor()) //因為我們有多個Processor,所以都要添加進來
                .AddStartRequests(resList.ToArray())
                .AddPipeline(new PrintSkuPipe());
            spider.ThreadNum = 1;
            spider.Run();
            Console.Read();

五、執行結果

六、總結

這次憋了那么久才寫第二篇文章的,實屬慚愧,本來2月底這篇文章就要出來的,一直拖到現在,上一篇文章的閱讀量整體來說還是不錯的超出了我的預期,下面評論也有小伙伴希望我快點出這篇文章,整體來說還是不錯的。這次的文章我希望就是說不僅僅是大家學會了如何去使用DotnetSpider,並且能夠讓大家了解一下如何爬數據,給大家提供一點點思路,所以我會結合實際場景來寫這篇文章,不然的話,感覺會太過於枯燥了。

希望大家多多拍磚

七、下期預告

下次文章會涉及文件抓取

 

 

順便說一句,有需要參加PMP或者高項考試的可以聯系我,我有一些資料可以提供

2018年5月13日


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM