目录
第四关:报错'ItemMeta' object does not support item assignment.
第五关:中间件过滤信息问题:Filtered duplicate request或者是Filtered offsite request to 域名
爬取大众点评
谢谢俊文学长的意见我已经把大白话都删了,以后也会注意语言的规范,严谨。果然公开处刑还是很必要的。
需求
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()整合即可。
有错就改就是好同志。
看完也不给个赞就走了