众所周知,Scrapy 默认会过滤重复的 URL,不会重复抓取相同的 URL,除非显式指定。
于是随便写了一个爬图片地址的小虫,然而不知道为什么总会爬两次 baidu 首页,你能看出错在哪里吗?
class ImageSpider(scrapy.Spider):
name = "images"
allowed_domains = ["www.baidu.com"]
start_urls = ['https://www.baidu.com/']
def parse(self, response):
images = response.xpath('//img/@src').extract()
for image in images:
image_item = ImageItem()
image_item['img_url'] = response.urljoin(image.strip())
yield image_item
urls = response.xpath('//a/@href').extract()
for url in urls:
next_url = response.urljoin(url.strip())
yield Request(next_url)
我想了半天都不明白为什么,以为是过滤器的问题,查了半天资料仍没解决。 后来偶然看了 Spider 源码,才发现坑爹之处。
原来源码的 start_requests 是这样写的(已忽略无关代码)
def start_requests(self):
for url in self.start_urls:
yield Request(url, dont_filter=True)
也就是说,因显式指定了 dont_filter=True,start_urls 中的 URL 在首次请求时不会加入过滤列表中,这样相同的 URL 第二次请求时由于不存在于过滤列表中,导致了二次抓取。
我实在不明白为什么会有这种矛盾,既然默认过滤重复 URL,那么在源码各个地方都应贯彻这个原则。
如果只是这样就算了,然而在官方教程中也埋了这样的坑: https://doc.scrapy.org/en/latest/intro/tutorial.html
在 Our first Spider 这一节中,是这样写 start_requests 的
def start_requests(self):
urls = [
'http://quotes.toscrape.com/page/1/',
'http://quotes.toscrape.com/page/2/',
]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
然后在 A shortcut to the start_requests method 一节中表示可以把 urls 提取到 start_urls 然后直接 use the default implementation of start_requests()。让人误以为 start_requests 默认实现也没有设置 dont_filter=True。简直就是把全世界所有新手都坑了一遍……
1
zachguo 2018-03-01 11:07:58 +08:00 via Android
scrapy 的源码质量。。。
你可以 PR 和维护者讨论。 不过我记得自己之前 PR 体验不好,维护者让我改代码去兼容一个几年前的依赖版本,原因是他们的 for profit 产品没升级依赖。。 |
2
swirling 2018-03-01 11:51:30 +08:00
想想要是 start_urls = ['a', 'b' , 'c', 'a'], 然后第二个被 filter 了也蛮爆炸的. 不过你要是实际用这种情况很少啦, 而且反正数据库也要做去重. 多抓一次其实影响不大.
|
3
nicevar 2018-03-01 13:11:27 +08:00
没太看明白这种写法为啥你会触发两次抓取,执行一次之后 spider 应该 close 了,难道你的意思是执行过程中产生了同样的 url 没有正常过滤?
我几个在跑的都是差不多写法,没有发现重复抓取现象 |
4
chengxiao 2018-03-01 13:16:51 +08:00
多谢楼主啊 我的几个爬虫经常出这个问题 我还纳闷这是什么情况
|
5
Nick2VIPUser 2018-03-01 13:43:31 +08:00 via iPhone
赞一个
|
6
qsnow6 2018-03-01 14:03:42 +08:00
有日志吗?看看日志不是清楚了
|