多线程与协程爬虫有很大区别吗?多线程转协程能优化多少?

2020-08-06 15:38:56 +08:00
 Te11UA
目前一个爬虫项目运行在 4c8g 下的机器,requests 运行在 4 进程* 400 线程下,单页面 200k 左右。
最近发现 CPU 和内存都有一点瓶颈,如果转成协程的话是否能优化爬取速率呢?
(不讨论反爬的情况,单纯从爬虫效率上看)
7311 次点击
所在节点    Python
54 条回复
chazyu1996
2020-08-07 11:36:14 +08:00
没有 sleep 的爬虫没有灵魂,那么快有啥用
est
2020-08-07 11:46:17 +08:00
@chazyu1996 真相了。
tairan2006
2020-08-07 13:25:39 +08:00
你如果是 4 核的话,400 个线程有点过分啊…虽然 IO 密集型,也不能这么夸张吧
binux
2020-08-07 13:29:43 +08:00
Cloutain
2020-08-07 15:37:28 +08:00
在多线程下再实现协程,这样避免过多的用户态内核态切换。
hakono
2020-08-07 16:01:21 +08:00
@wysnylc #14 如果你有什么高见还请你说清楚。不过我反倒觉得你在这楼的回复让人看得尬死了。
skinny
2020-08-07 16:26:52 +08:00
瓶颈难道不是在反爬、被爬网络带宽和服务质量、爬虫网络带宽吗?
老说协程提高效率节省资源什么的,一些时候确实是这样,但是在一般爬虫项目里线程协程真的没什么差别,我同意 12 楼的看法。
wysnylc
2020-08-07 16:36:28 +08:00
@hakono #46 "现在一般普通主流计算机,直接开几万协程确实没问题。以前开发写测试时,不小心开了几十万协程,还能跑。但是线程却不行,资源占用和上下文切换决定了它不可能开启同样多的数量时还能保证可用。"
协程也是跑在线程之上,如果真的同时运行几十万线程而不挂只有一种可能,活跃的线程其实没几个,我后面贴的为什么 Java 坚持多线程不选择协程?里面有这样一句话:上面的讨论简化了 RSS 和 VM 的区别。实际上一个线程启动后只会在虚拟地址上占位置那么多的内存。除非实际用上,是不会真的消耗物理内存的。
所以所谓的"几万"根本就是扯淡,多协程本质上和多线程没有区别,多线程该有的问题多协程一样有,而单个协程是跑在单线程上单线程可能跑几万请求?用脚指头想也知道答案

"另外你可以找下百万 Go TCP, 百万 websocket 同时连接的文章。有每个连接使用 go 程,和 reactor epoll 的性能对比。
"
老 Go 吹了,上手就是百万 233333 教程一大把就是没啥公司用
sss495088732
2020-08-07 16:43:53 +08:00
python 同一个业务内全使用 aio 家族
bytesmith
2020-08-07 16:56:29 +08:00
别想啦,你这估计差不多到极限了,你费劲巴拉搞的协程,还不如直接加硬件来的实在。
bytesmith
2020-08-07 17:01:00 +08:00
或者用 工具看看 cpu 的瓶颈在哪里, 是都用在切换上下文了还是说在跑 计算密集的任务,然后在决定怎么优化,别无脑优化
sunriz
2020-08-08 00:57:57 +08:00
爬虫应该是 Io 密集的吧
black11black
2020-08-08 09:55:22 +08:00
楼上一大堆人不知道说的啥。IO 复用不光是解决内核态开销问题,还有一个大问题是解决线程切片时间的问题啊。

你以为的多线程+GIL:一个线程执行完了释放 GIL,切换到下一个线程,申请 GIL,开始业务逻辑
实际的多线程+GIL:一个线程执行完了释放 GIL,等待很长时间,系统 call 你了才能切换
ClericPy
2020-08-08 19:03:25 +08:00
看到有几个回复挺反常识的, 提醒几个 Python 并发编程的常识问题吧

1. 线程开的越多, 执行起来就越快吗?

并不会.

一方面, 线程开太多, CPU 切换的成本会变高, 也就相对降低了 CPU 利用率, CPU 很多时间浪费在调度上而不是计算上. 有关怎么切换的, 可以随处找找 GIL 的文章, 不过还是不建议自己修改对应参数 setswitchinterval (旧版本的 setcheckinterval )
另一方面, 对爬虫来说, 如果连接速度靠谱的话, 有可能一个线程就跑满了带宽, 那开多线程除了让所有任务一起抢资源, 并不会降低总时长, 也就是常见场景: 为什么我开 5 线程比开 100 线程还快(或者差不多). 与普通程序不同, 爬虫程序传输数据一方面看你的带宽, 另一方面还特别看重目标服务器的负载能力.

2. 有一个比较合理的并发数量吗?

参考:
Python3 里面 ThreadPoolExecutor 的 max_workers 默认值是 (os.cpu_count() or 1) * 5
可以根据带宽使用率适当调整这个数值.
(另: 多进程 ProcessPoolExecutor 默认 max_workers 就是 os.cpu_count() or 1)


3. 不计带宽和 CPU 能力的情况下, 是不是线程开的越多, 速度越快?

也不完全是.

拿 Requests 库来举例, 它的 Session 默认连接池大小取决于 HTTPAdapter 对象的 pool_connections, 这个默认值 DEFAULT_POOLSIZE = 10
简单的说, 如果不修改 HTTPAdapter 连接池的大小, 那可能瓶颈基本限定在这里了. 至于有些人选择不用 Session 复用连接, 我举个例子算了: 之前抓某东的某数据, 复用连接的情况下速度比每次新建连接大概快了十几倍.

4. 是不是用协程就比线程快, 节省 CPU?

不一定.

协程提高的是 CPU 效率, 遇到高并发的抓取, 你会发现协程 CPU 一直 100%, 因为它真的很忙, 而多线程反而可能在 80~100% 波动. 至于速度, 简单的提个例子, falcon 那是相当擅长 Benchmark.

协程中最使我受益的并不是性能, 而是它属于随时可以 Cancel 的, 一个已经执行的线程, 想在外部杀死它简直太费劲了.

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/696184

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX