zoukankan      html  css  js  c++  java
  • 汽车之家店铺商品详情数据抓取 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日

  • 相关阅读:
    Java实现 蓝桥杯VIP 算法提高 P0404
    Java实现 蓝桥杯VIP 算法提高 P0404
    Java实现 蓝桥杯VIP 算法提高 P0404
    Java实现 蓝桥杯VIP 算法提高 P0404
    Java实现 蓝桥杯VIP 算法提高 P0404
    Java实现 蓝桥杯VIP 算法训练 排列问题
    Java实现 蓝桥杯VIP 算法训练 排列问题
    Java实现 蓝桥杯VIP 算法训练 排列问题
    Java实现 蓝桥杯VIP 算法训练 排列问题
    关于模态/非模态对话框不响应菜单的UPDATE_COMMAND_UI消息(对对WM_INITMENUPOPUP消息的处理)
  • 原文地址:https://www.cnblogs.com/FunnyBoy/p/9029937.html
Copyright © 2011-2022 走看看