scrapy爬取大众点评并解析??

目录

爬取大众点评

需求

第一关:大众点评爬取遇到403

第二关:scrapy的信息传递

 第三关:DNS域名解析错误

第四关:报错'ItemMeta' object does not support item assignment. 

第五关:中间件过滤信息问题:Filtered duplicate request或者是Filtered offsite request to 域名

解析原理:

下面是代码(都在DianPing类里面):

第一部分:

第二部分:

第三部分:

第四部分:

 第五部分:

 


爬取大众点评

谢谢俊文学长的意见我已经把大白话都删了,以后也会注意语言的规范,严谨。果然公开处刑还是很必要的。

需求

    1、包括店名、评分(最低3分,后期item要转成int类型)。

    2、点评数(利用点评数做排名,后期需要转成int类型)。

    3、人均消费也是做排名(值可能为空,也要转成int类型)


你可能会遇到以下的错误或者是异常

第一关:大众点评爬取遇到403

第一步将会对python的User-Agent进行筛查,必须把User-Agent添加到headers中才不会遇到403错误,根据网上的资料显示添加了headers,仍然是显示403,那是因为headers并没有正确地添加到第一个发起的Request。正确的方式是改写start_requests()方法。

资料补充:

1、User-Agent:本来是用于检验不同的浏览器,规避浏览器差异性,避免用户因为浏览器版本、类型不同而产生一些无法正常显示页面的问题。服务器端回去维护一个它允许的User-Agent的列表,你的User-Agent不符合要求就会给你返回500或者是403的错误。

2、利用nginx做负载均衡时候就把非法ua过滤掉。

Nginx:很多时候都会和django一起配合使用,它是web服务器。

负载均衡:举个例子:有一个服务器集群,里面的一台服务器充当的角色是调度器,负责把繁重的任务分配对空闲的服务器进行多分配,任务繁重的服务器进行少分配或者不分配。

具体操作:

Nginx可以直接配置agent_deny.conf的文件进行禁止User-Agent及User-Agent为空的访问的访问。

class DianpingSpider(scrapy.Spider):
    name = 'dianping'
    allowed_domains = ['www.dianping.com', 's3plus.meituan.net']
    start_urls = ['https://www.dianping.com/qiqihaer/ch0']
    headers = {
        'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36",
    }

    def start_requests(self):
        return [Request(url=self.start_urls[0], 
                        headers=self.headers, 
                        callback=self.get_css_link
                        )]

分析:scrapy爬取的第一步把start_urll传给start_request(),start_request()产生一个最开始的scrapy.Request对象(不是requests.Request对象),所以你必须重写这个方法,把headers放到最开始的scrapy.Request里面。也有资料提出要添加'Accept': 'application/json, text/javascript'到headers里面,没有实质的作用。

第二关:scrapy的信息传递

下图显示的是scrapy的信息传输机制,Engine,Downloader,Scheduler是不需要进行方法的重写操作,重点是实现spider→item pipeline的过程。

 分析:(暂时不提中间件,只完成最基本的爬取)spider.py(1、具体爬取的流程以及特定的爬取操作2、页面解析)→yield/return→item.py(进行数据的处理过滤,例如正则匹配,数据类型转换等)→yield/return→pipelines.py(传到数据库保存、图片保存、以json格式保存等)。每一个环节都需要有返回值,spider需要返回item或者是request对象,不返回item.py就不能进一步处理。

 第三关:DNS域名解析错误

检查start_url 你会发现多了一个‘http://’,这是利用命令新建spider的时候自动生成多一个“http://”就是说有两个‘http://’。

第四关:报错'ItemMeta' object does not support item assignment. 

分析:Itemloader构造函数的实参必须支持赋值。换句话来说,就是赋值出错了。生成itemloader对象的时候传入的是对象而不是引用。(我这样写可能会误导读者下文解析)

象和引用的区别:举一个例子,在最上面的代码块中start_request返回的Request的参数里面有一个callback我传进来假如是parse就会调用名为parse的函数,执行parse函数的逻辑,假如写成parse()那就会变成callback=pasrse()函数返回的结果。因为python万物皆对象,不论是函数还是数值类型(int类型的1,2,3都是对象,只是它们不可变而已,因此上文说的传入的是对象这样很含糊)

item_loader = ItemLoader(item=ShopItem(), response=response)

item提供了抓取数据的容器,而itemloader提供了填充该容器的机制,这种机制通过spider和覆盖不同的字段解析规则,而不像单纯的item一样要维护css选择器的表达式或者是xpath的表达式。

其实我也不是分不清传引用和传对象的区别,我只是不知道它需要传的是对象。这种错误以后就要点进去源码看一看: 当没有item的时候,新建一个default_item_class的对象,再点进去看看default_item_class是一个类,那就是说是实参是个对象。

第五关:中间件过滤信息问题:Filtered duplicate request或者是Filtered offsite request to 域名

分析:引发这个异常的原因分别是:

1、Filtered offsite request to域名:当前将要请求的request的url与域名冲突了。

中间件过滤的必要性:举个例子假如大众点评里面有链接到汽车之家的网站,在你深度遍历的时候不需要把汽车之家的信息也下载。

2、Filtered duplicate request:当前请求的request的url已经请求过了。

中间件过滤的必要性:举一个例子,正如爬虫陷阱,假如我的博客有强哥带你学编程的链接,强哥的博客也有我文章的链接,你的爬虫访问强哥的链接,通过强哥的链接又访问我的链接,陷入了死循环。还有一种爬虫陷阱是日历,一天连着下一天,也会陷入无限的循环,因此过滤重复和爬取的深度的控制是十分必要的。

解决方法,1、你在allow_domain里面加新的域名。2、在返回的request添加dont_filter=True,Request(url,callback=self.detail,dont_filter=True)


解析原理:

点击这个链接!!

详细的解析了css图文混排的解析思路。

文章缺点:

1、class的属性是会变的class不一定是固定开头,正则被写死。

2、没有解析为什么要除以12,12其实是font_size

3、没有解析y轴是怎么选取的

4、跟我一样大白话


下面是代码(都在DianPing类里面):

第一部分:

上文已经进行叙述。

class DianpingSpider(scrapy.Spider):
    name = 'dianping'
    allowed_domains = ['www.dianping.com', 's3plus.meituan.net']
    start_urls = ['https://www.dianping.com/qiqihaer/ch0']
    headers = {
        'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36",

    }

第二部分:

访问下图的页面

    def start_requests(self):
        return [Request(url=self.start_urls[0], 
                        headers=self.headers, 
                        callback=self.get_css_link
                        )]

 第三部分:

主要有两个任务:

1、找到所有跟数字有关的class的值,就0,2-9,1没有加密,相同数字的class的值是一样的,这个class是会随着时间变的,频率是一天一变,所以我决定在这里解析response用靓汤提取span标签里面的class值2、找到css的链接然后访问它,这就是我会遇到报‘Filtered offsite request to域名’的异常的原因。下图显示的是将要爬取的评论数,可以看见span里面为空。

 def get_css_link(self, response):
        css_links = response.css("link::attr(href)").extract()
        soup = BeautifulSoup(response.text, 'lxml')
        num_set = set()
        b_tags = soup.find_all(name='b')
        for tag in b_tags:
            ts = tag.find_all(name='span')
            for t in ts:
                num_set.add(t['class'][0])
        meta = response.meta
        meta['num_set'] = num_set
        for css_link in css_links:
            if "s3plus.meituan.net" in css_link:
                return Request(url="http:" + css_link,
                               headers=self.headers,
                               callback=self.get_location,
                               meta=meta,
                               dont_filter=True
                               )

 补充:你想parse1()处理完的东西在下一个parse2()用就把parse1处理完的结果放到meta里面,meta是用于结果的传递,meta是一个字典,__len__和以download开头的几个参数都是自带的。

第四部分:

1、提取刚刚访问网页中的class对应的坐标,background:第一个值为x,第二个值为y,然后通过提取,转变成字典,格式为:{'class_name':(x, y)}。

2、这个网页里面有4个与众不同的值,里面是存放url的,有一个是有important的,另外一个是有两个important的,有两个important的正是存放‘数字答案’的链接。

    def get_location(self, response):
        meta = response.meta
        num_set = meta['num_set']
        # {“类名”:(x, y)}
        for num_key in num_set:
            reg = "." + num_key + "{background:-(.*?).0px -(.*?).0px;}"
            x = int(re.search(reg, response.text).group(1))
            y = int(re.search(reg, response.text).group(2))
            meta[num_key] = (x, y)
        meta.pop('num_set')
        reg = "{width.*important.*url\((.*?)\).*important}"
        secreat = re.search(reg, response.text).group(1)
        return Request(url="http:" + secreat,
                       headers=self.headers,
                       callback=self.get_secreat,
                       dont_filter=True,
                       meta=meta
                       )

debug信息:

 第五部分:

1、我要在‘钥匙’页面里面提取我要的数字

2、我要我的字典由{‘class_name’:(x,  y)}蜕变成{‘class_name’:解密数字}

    def get_secreat(self, response):
        meta = response.meta
        reg_num = "font-size:(\d+)px"
        font_size = int(re.search(reg_num, response.text).group(1))
        rex = """<text.*y="(\d+)">(.*?)</text>"""
        secreats = re.findall(rex, response.text)
        # index2str 是一个以y为键,坐标为值的字典
        index2str = {}
        for y, answer_str in secreats:
            y = int(y)
            index2str[y] = answer_str
        for key, value in meta.items():
            if type(value) is tuple:
                x, y = value
                x = int((x + 7) / font_size) - 1
                for m in index2str.keys():
                    if y < m:
                        y = m
                        break
                answer = index2str[y]
                result = answer[x]
                meta[key] = result
        return Request(url=self.start_urls[0],
                       headers=self.headers,
                       callback=self.parse,
                       dont_filter=True,
                       meta=meta
                       )

这就把大众点评加密的内容解析出来了。最后通过遍历join()整合即可。

有错就改就是好同志。

看完也不给个赞就走了

 

发布了46 篇原创文章 · 获赞 75 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_38875300/article/details/88769333