现在在爬的一个站点,有一个起始链接,后续所有的链接都是通过上一链接返回的 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
1
InternetExplorer 2019-05-05 15:00:41 +08:00
要考虑对方服务器的性能的呀,网站服务器的性能可能还没你的爬虫机器高。。。。
|
2
caneman OP @InternetExplorer 我试了调并发数并没有显著的影响到我的抓取速率,而且对方的站是绝对扛得住的(是个大站)。
这里我虽然写的很大,但是其实抓取频率并没有很高(所以才一直没改),而且抓取到的页面都是按顺序的,我觉得是不是我的抓取逻辑有问题,应该怎么样改善呢? |
3
locoz 2019-05-05 15:27:27 +08:00
如果别人网站原本的翻页逻辑就是 [要根据上一页内容来得到下一页内容] 的,那你并发再高都没有用,跟 Scrapy 没有关系,如果要并发你只能是从分类之类的地方入手。(不过一般不都是这样爬么,直接计算页数爬的话很容易出现漏数据的情况)
|
5
caneman OP @locoz 总共大概有 4000W 页面,如果我找到了这 4000W 页面的列表,我要写在 start_urls 里面才能实现高并发吗?
之前没有接触过 Scrapy-redis,目前的情况是,单机,带宽还可以,IP/Cookie 等所有反爬措施均已解决,可以理解为网站无反爬站措施,这样的话,我该怎么样实现日抓百万呢? 想到的一种可行的方案是,把所有的 url 写入 redis,然后所有的请求从 redis 里面去取 url, 但是单机的情况下,如何实现并发?(就是不是一个请求结束后再去 redis 取下一个,而是多个线程(并发数)同时连接 redis 去取 url,然后这些个线程同时进行抓取)不太清楚 scrapy-redis 有没有解决这个问题。。。 |
6
snal123 2019-05-05 15:42:07 +08:00 via iPhone
timeout_delay 调小 改成 10s 左右,默认 180s 很多时间浪费可能是代理质量很低造成的。
|
8
tozp 2019-05-05 15:46:10 +08:00
首先爬虫不必过于追求效率;其次 Scrapy 执行效率是个问题,我现在都是用 Go 框架爬的;最后,Scrapy 你再怎么改也就那样。
|
9
dingyaguang117 2019-05-05 15:57:39 +08:00
LZ 你自己不是很清楚吗,下一页的 URL 是上一个的 response 里面读到的,这个肯定是串行啊。你得改变这种串行获取 url 的方式才行
|
10
caneman OP @tozp 现在日抓 10-20W 级,有点跟不上需求,需求大概是日抓百万,但是不能分布式。。。不是不能用,是现在的问题是单机的性能远远的浪费了,无论是带宽还是性能,都远远的没有用到。
|
12
caneman OP @dingyaguang117 改变串行后呢,怎么提高效率,我总不能把 4000W 页面连接都写道 start_urls 里面吧?
其实我现在是有点不太明白 scrapy 是实现并发的原理,网上也没有找到很好的解释文档。按我的理解,它是通过 start_urls 来实现并发的,任何在 parse 里面写的 yield 都会存在上面的串行问题。 我能想到的是把 scrapy 和 redis 对接(单机对接),然后多个线程同时去取 url,然后同时去抓,关键是我不知道 scrapy 支不支持这种操作,也不知道能不能实现或者有没有现成的解决方案,以免重复造轮子或者根本就此路不通。。。 不过好像上面这种想法又回到了 scrapy 是如何实现并发的问题上了。。。。 |
13
dingyaguang117 2019-05-05 16:14:06 +08:00
@caneman 当然要保证 队列里有足够的 url 够下载器消费啦,你可以 按照某些固定的规则放进去,保证足够的数量就行了
你现在是每次队列里只有 1 个,你 100 个并发下载啥? |
14
caneman OP @dingyaguang117 谢谢,我觉得问题在这儿,但是这个规则怎么建立没想好,4000W 级别,还要涉及到失效错误链接的处理,请问 scrapy-redis 是不是能解决我的问题?
|
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 主线程处理上限。 |
16
AlloVince 2019-05-05 17:14:51 +08:00 4
关于 4000W url 如何调用 scrapy 爬取的问题,简单说可以将已知的 url 构建为`Request`, 然后`Spider.parse_start_url()` 中 `yield Request` 即可,所有待处理的 Request 会存入 Scheduler,Scheduler 的数据都存在内存,可以提前评估一下内存是否够存放所有的 url。
scrapy-redis 实现的是将 Scheduler 的数据从内存改为 Redis, 一方面 redis 在进程崩溃后数据不会丢失,另一方面可以突破单机的限制,理论上有足够多的机器的话,再多的 URL 也可以同时请求。此时的瓶颈在 url -> Scheduler 生产者的生产速度 |
18
renmu123 2019-05-05 22:22:32 +08:00 via Android
先把所有的 URL 爬到手,一些能看出规律的就手动构造,然后就多多进程 /多线程 /异步,随便玩
|
19
zhijiansha 2019-05-05 23:23:26 +08:00
@caneman #10 其实,上分布式,可以把 worker 都放在一台机子上的
|
20
caneman OP @zhijiansha 这个思路挺好的,谢谢啊
|
21
caneman OP @renmu123 现在能得到所有的 url 了,我想着怎么能用 scrapy 高效抓取,scrapy 这么多年了 这样一个成熟的框架应该不至于解决不了这种问题。想先单机把 scrapy 性能发挥到极致,了解他的极限和瓶颈在哪里,然后再上分布式再接着进一步优化,计划的学习路线是这样的。
|
22
rocketman13 2019-05-06 11:45:04 +08:00
scrapy 的 CrawlSpider 类?
|
23
cxh116 2019-05-06 15:50:16 +08:00
数据是怎么保存的? 用的是同步还是异步调用.在 pipline 用同步阻塞方式去保存数据的话,会阻塞整个抓取调度的.
https://leehodgkinson.com/blog/scrapy-pipelines/ |
24
caneman OP @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 的值,是不是就能真正的控制并发数了? |
25
caneman OP |
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 这种方案,启用多个进程,看看有没有提升. |
27
caneman OP @cxh116 谢谢,代理好像不是瓶颈,不加代理提升的速率也非常有限(大概就是去除了代理延迟级别的速度提升) redis 的那种方案确实提高了速率,是我之前写法太蠢了,所有的下一个页面链接都得等我上一个页面请求完毕才能获取,生生的变成了同步。(可是书上和网上都是这样来写的啊,寻找下一页的链接然后 yield ),不知道是我的理解问题,还是这样写本身就存在这种问题,我再多尝试尝试改一改,谢谢啦🙏。
|