• 汽车之家店铺商品详情数据抓取 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日

  • 相关阅读:
    C#中关于DBNULL的处理方法
    html 点击复制
    AJAX的简洁写法
    PHP 数组模糊查询
    PHP二维数组搜索返回数组
    php 数组排序得方法
    PHPExcel的使用
    使用PHPword中文乱码并且下载的方法
    关于多图上传的修改的操作
    把一个表里的两列或者三列合并为一行
  • 原文地址:https://www.cnblogs.com/FunnyBoy/p/9029937.html
Copyright © 2020-2023  润新知