众所周知,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,那么在源码各个地方都应贯彻这个原则。
如果只是这样就算了,然而在官方教程中也埋了这样的坑: Scrapy Tutorial (备份)
在 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 一节中表示
Instead of implementing a
start_requests()
method that generatesscrapy.Request
objects from URLs, you can just define astart_urls
class attribute with a list of URLs. This list will then be used by the default implementation ofstart_requests()
to create the initial requests for your spider
可以把 urls 提取到 start_urls 然后直接 use the default implementation of start_requests()。让人误以为 start_requests 默认实现也没有设置 dont_filter=True。简直就是把全世界所有新手都坑了一遍……