V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
lxy
V2EX  ›  Python

Scrapy 暗坑之 start_requests

  •  
  •   lxy · 2018-03-01 10:32:28 +08:00 · 9143 次点击
    这是一个创建于 2460 天前的主题,其中的信息可能已经有所发展或是发生改变。

    众所周知,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。简直就是把全世界所有新手都坑了一遍……

    6 条回复    2018-03-01 14:03:42 +08:00
    zachguo
        1
    zachguo  
       2018-03-01 11:07:58 +08:00 via Android
    scrapy 的源码质量。。。
    你可以 PR 和维护者讨论。
    不过我记得自己之前 PR 体验不好,维护者让我改代码去兼容一个几年前的依赖版本,原因是他们的 for profit 产品没升级依赖。。
    swirling
        2
    swirling  
       2018-03-01 11:51:30 +08:00
    想想要是 start_urls = ['a', 'b' , 'c', 'a'], 然后第二个被 filter 了也蛮爆炸的. 不过你要是实际用这种情况很少啦, 而且反正数据库也要做去重. 多抓一次其实影响不大.
    nicevar
        3
    nicevar  
       2018-03-01 13:11:27 +08:00
    没太看明白这种写法为啥你会触发两次抓取,执行一次之后 spider 应该 close 了,难道你的意思是执行过程中产生了同样的 url 没有正常过滤?
    我几个在跑的都是差不多写法,没有发现重复抓取现象
    chengxiao
        4
    chengxiao  
       2018-03-01 13:16:51 +08:00
    多谢楼主啊 我的几个爬虫经常出这个问题 我还纳闷这是什么情况
    Nick2VIPUser
        5
    Nick2VIPUser  
       2018-03-01 13:43:31 +08:00 via iPhone
    赞一个
    qsnow6
        6
    qsnow6  
       2018-03-01 14:03:42 +08:00
    有日志吗?看看日志不是清楚了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   916 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 22:35 · PVG 06:35 · LAX 14:35 · JFK 17:35
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.