Scrapy 效率瓶颈 - V2EX
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
caneman
V2EX    Python

Scrapy 效率瓶颈

  •  
  •   caneman 2019-05-05 14:53:07 +08:00 5403 次点击
    这是一个创建于 2358 天前的主题,其中的信息可能已经有所发展或是发生改变。

    现在在爬的一个站点,有一个起始链接,后续所有的链接都是通过上一链接返回的 response 来产生的, (可以理解为从返回的 response 里面提取到下一页的链接)

    现在的问题是这样写好的爬虫,感觉是不是硬生生把并发搞成了单线程一样 我并发和线程数都调的很大,但是仍感觉速度很慢,大概每秒处理 2-5 个页面,一天也就只能抓 10-15W 的样子 感觉明显有问题

    我想问一下怎么样我才能提高我的抓取效率呢?(单机的情况下)

    这是我的一些配置 RETRY_ENABLED = 1 RETRY_TIMES = 2 DOWNLOAD_TIMEOUT = 15

    DOWNLOAD_DELAY = 0 CONCURRENT_REQUESTS = 100 CONCURRENT_REQUESTS_PER_DOMAIN = 100 CONCURRENT_REQUESTS_PER_IP = 100

    27 条回复    2019-05-06 20:41:48 +08:00
    InternetExplorer
        1
    InternetExplorer  
       2019-05-05 15:00:41 +08:00
    要考虑对方服务器的性能的呀,网站服务器的性能可能还没你的爬虫机器高。。。。
    caneman
        2
    caneman  
    OP
       2019-05-05 15:11:07 +08:00
    @InternetExplorer 我试了调并发数并没有显著的影响到我的抓取速率,而且对方的站是绝对扛得住的(是个大站)。
    这里我虽然写的很大,但是其实抓取频率并没有很高(所以才一直没改),而且抓取到的页面都是按顺序的,我觉得是不是我的抓取逻辑有问题,应该怎么样改善呢?
    locoz
        3
    locoz  
       2019-05-05 15:27:27 +08:00
    如果别人网站原本的翻页逻辑就是 [要根据上一页内容来得到下一页内容] 的,那你并发再高都没有用,跟 Scrapy 没有关系,如果要并发你只能是从分类之类的地方入手。(不过一般不都是这样爬么,直接计算页数爬的话很容易出现漏数据的情况)
    locoz
        4
    locoz  
       2019-05-05 15:28:15 +08:00
    @locoz #3 想了想这么说不太准确,“如果要并发” 换成“如果要提高效率”
    caneman
        5
    caneman  
    OP
       2019-05-05 15:37:55 +08:00
    @locoz 总共大概有 4000W 页面,如果我找到了这 4000W 页面的列表,我要写在 start_urls 里面才能实现高并发吗?
    之前没有接触过 Scrapy-redis,目前的情况是,单机,带宽还可以,IP/Cookie 等所有反爬措施均已解决,可以理解为网站无反爬站措施,这样的话,我该怎么样实现日抓百万呢?

    想到的一种可行的方案是,把所有的 url 写入 redis,然后所有的请求从 redis 里面去取 url, 但是单机的情况下,如何实现并发?(就是不是一个请求结束后再去 redis 取下一个,而是多个线程(并发数)同时连接 redis 去取 url,然后这些个线程同时进行抓取)不太清楚 scrapy-redis 有没有解决这个问题。。。
    snal123
        6
    snal123  
       2019-05-05 15:42:07 +08:00 via iPhone
    timeout_delay 调小 改成 10s 左右,默认 180s 很多时间浪费可能是代理质量很低造成的。
    snal123
        7
    snal123  
       2019-05-05 15:44:07 +08:00 via iPhone
    @snal123 说错了 看到你已经是 15 了 你没用 redis 的话我觉得还是这里的问题
    tozp
        8
    tozp  
       2019-05-05 15:46:10 +08:00
    首先爬虫不必过于追求效率;其次 Scrapy 执行效率是个问题,我现在都是用 Go 框架爬的;最后,Scrapy 你再怎么改也就那样。
    dingyaguang117
        9
    dingyaguang117  
       2019-05-05 15:57:39 +08:00
    LZ 你自己不是很清楚吗,下一页的 URL 是上一个的 response 里面读到的,这个肯定是串行啊。你得改变这种串行获取 url 的方式才行
    caneman
        10
    caneman  
    OP
       2019-05-05 15:57:44 +08:00
    @tozp 现在日抓 10-20W 级,有点跟不上需求,需求大概是日抓百万,但是不能分布式。。。不是不能用,是现在的问题是单机的性能远远的浪费了,无论是带宽还是性能,都远远的没有用到。
    tozp
        11
    tozp  
       2019-05-05 16:00:21 +08:00
    @caneman 和我之前差不多,用 Go 之后,每天差不多提升了 10 倍,2M-
    caneman
        12
    caneman  
    OP
       2019-05-05 16:03:04 +08:00
    @dingyaguang117 改变串行后呢,怎么提高效率,我总不能把 4000W 页面连接都写道 start_urls 里面吧?

    其实我现在是有点不太明白 scrapy 是实现并发的原理,网上也没有找到很好的解释文档。按我的理解,它是通过 start_urls 来实现并发的,任何在 parse 里面写的 yield 都会存在上面的串行问题。

    我能想到的是把 scrapy 和 redis 对接(单机对接),然后多个线程同时去取 url,然后同时去抓,关键是我不知道 scrapy 支不支持这种操作,也不知道能不能实现或者有没有现成的解决方案,以免重复造轮子或者根本就此路不通。。。

    不过好像上面这种想法又回到了 scrapy 是如何实现并发的问题上了。。。。
    dingyaguang117
        13
    dingyaguang117  
       2019-05-05 16:14:06 +08:00
    @caneman 当然要保证 队列里有足够的 url 够下载器消费啦,你可以 按照某些固定的规则放进去,保证足够的数量就行了
    你现在是每次队列里只有 1 个,你 100 个并发下载啥?
    caneman
        14
    caneman  
    OP
       2019-05-05 16:18:45 +08:00
    @dingyaguang117 谢谢,我觉得问题在这儿,但是这个规则怎么建立没想好,4000W 级别,还要涉及到失效错误链接的处理,请问 scrapy-redis 是不是能解决我的问题?
    AlloVince
        15
    AlloVince  
       2019-05-05 17:02:54 +08:00   1
    Scrapy 底层是 Twisted,Twisted 通过事件循环+线程池来实现异步 IO 的效果,LZ 所说的“并发数”,在 Scrapy 中是 CONCURRENT_REQUESTS, 其实只是传给 Twisted 的 Deferred 对象数量。由于 Twisted 只适用于单机环境,如果要增大 LZ 所说的“并发”数,可以调大 CONCURRENT_REQUESTS, 但显然“并发”数不可能无限增大,因为 Twisted 本身也存在限制

    一方面 Twisted 本身有 Queue 和线程池,在 Scrapy 中可以通过设置 Twisted 的 REACTOR_THREADPOOL_MAXSIZE 增大线程池线程数。

    另外 Twisted 主线程是单线程的,主线程达到瓶颈的话,再扩大线程池也没有意义。

    因此你可以认为单机环境下 Scrapy 的瓶颈 == Twisted 主线程处理上限。
    AlloVince
        16
    AlloVince  
       2019-05-05 17:14:51 +08:00   4
    关于 4000W url 如何调用 scrapy 爬取的问题,简单说可以将已知的 url 构建为`Request`, 然后`Spider.parse_start_url()` 中 `yield Request` 即可,所有待处理的 Request 会存入 SchedulerScheduler 的数据都存在内存,可以提前评估一下内存是否够存放所有的 url。

    scrapy-redis 实现的是将 Scheduler 的数据从内存改为 Redis, 一方面 redis 在进程崩溃后数据不会丢失,另一方面可以突破单机的限制,理论上有足够多的机器的话,再多的 URL 也可以同时请求。此时的瓶颈在 url -> Scheduler 生产者的生产速度
    caneman
        17
    caneman  
    OP
       2019-05-05 17:26:03 +08:00
    @AlloVince 非常感谢!您的这一番讲解能让我少走很多弯路,再次感谢!
    renmu123
        18
    renmu123  
       2019-05-05 22:22:32 +08:00 via Android
    先把所有的 URL 爬到手,一些能看出规律的就手动构造,然后就多多进程 /多线程 /异步,随便玩
    NLL
        19
    NLL  
       2019-05-05 23:23:26 +08:00
    @caneman #10 其实,上分布式,可以把 worker 都放在一台机子上的
    caneman
        20
    caneman  
    OP
       2019-05-06 10:30:29 +08:00
    @zhijiansha 这个思路挺好的,谢谢啊
    caneman
        21
    caneman  
    OP
       2019-05-06 10:34:07 +08:00
    @renmu123 现在能得到所有的 url 了,我想着怎么能用 scrapy 高效抓取,scrapy 这么多年了 这样一个成熟的框架应该不至于解决不了这种问题。想先单机把 scrapy 性能发挥到极致,了解他的极限和瓶颈在哪里,然后再上分布式再接着进一步优化,计划的学习路线是这样的。
    rocketman13
        22
    rocketman13  
       2019-05-06 11:45:04 +08:00
    scrapy 的 CrawlSpider 类?
    cxh116
        23
    cxh116  
       2019-05-06 15:50:16 +08:00
    数据是怎么保存的? 用的是同步还是异步调用.在 pipline 用同步阻塞方式去保存数据的话,会阻塞整个抓取调度的.

    https://leehodgkinson.com/blog/scrapy-pipelines/
    caneman
        24
    caneman  
    OP
       2019-05-06 17:54:27 +08:00
    @cxh116 是采用的异步 MySQL 存储的,很多页面是空数据的,所以瓶颈不在存储这一块,下面是主要代码。


    def start_requests(self):
    url = 'https://www.xxxx.com/'
    longitude, latitude = get_next_coordinate( self.start_longitude, self.start_latitude)
    data = get_form(longitude, latitude)
    proxy = 'http://' + get_proxy()
    yield FormRequest(url, method='POST', formdata=data, callback=self.parse, dont_filter=True, meta={'proxy':proxy,'download_timeout':3,'longitude':data['longitude'], 'latitude':data['latitude']})

    def parse(self, response):
    info_list = json.loads(response.text)
    if info_list['Count']:
    for item in info_list['list']:
    item_loader = QiyeItemloader(item=QiyeItem())
    item_loader.add_value('hash', item['Key'])
    item_loader.add_value('name', item['Name'])
    item_loader.add_value('longitude', response.meta['longitude'])
    item_loader.add_value('latitude', response.meta['latitude'])
    qiye_item= item_loader.load_item()
    yield qiye_item
    longitude, latitude = get_next_coordinate(response.meta['longitude'], response.meta['latitude'])
    next_data = get_form(longitude, latitude)
    yield FormRequest(response.url, method='POST', formdata = next_data, callback=self.parse, dont_filter=True, meta={'proxy':response.meta['proxy'],'download_timeout':3,'longitude':next_data['longitude'], 'latitude':next_data['latitude']})

    我想的一种解决方案是把所有 URL 放在 redis 里面,然后在 start_requests 里面 while True:yield Request()
    这样的问题我不知道我这样一直写会不会时间长了我的电脑就崩了。
    我如何控制这个被 yield 的 Request 的数量?比如,在队列里面一直有 100 个 Request,每少一个就添一个,始终保持 Start_url 里面有 100 个待爬 URL,这样的情况下,我调 CONCURRENT_REQUESTS 的值,是不是就能真正的控制并发数了?
    caneman
        25
    caneman  
    OP
       2019-05-06 18:02:54 +08:00
    cxh116
        26
    cxh116  
       2019-05-06 20:14:19 +08:00
    @caneman 你得确认瓶颈在什么地方?
    假如网页通过代理访问,60 秒才返回一个页面.这样就算你 1000 个并发. 1000 / 60 = 16.6 .这样算每秒最多也就是 16 个而已.

    假如网页解析比较费时,这个问题就更加不好解决.因为毕竟这种类似于阻塞的调用.


    你可以登录 telnet 用 est() 查看一下状态,分析一下原因 https://docs.scrapy.org/en/latest/topics/telnetconsole.html
    可以看一下 engine.scraper.slot.queue 的实现,这里应该可以取到你要的队列大小值.

    你还可以尝试用你自己的 redis 这种方案,启用多个进程,看看有没有提升.
    caneman
        27
    caneman  
    OP
       2019-05-06 20:41:48 +08:00 via iPhone
    @cxh116 谢谢,代理好像不是瓶颈,不加代理提升的速率也非常有限(大概就是去除了代理延迟级别的速度提升) redis 的那种方案确实提高了速率,是我之前写法太蠢了,所有的下一个页面链接都得等我上一个页面请求完毕才能获取,生生的变成了同步。(可是书上和网上都是这样来写的啊,寻找下一页的链接然后 yield ),不知道是我的理解问题,还是这样写本身就存在这种问题,我再多尝试尝试改一改,谢谢啦。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2801 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 22ms UTC 07:08 PVG 15:08 LAX 00:08 JFK 03:08
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86