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
MyFaith
V2EX  ›  Python

有没有易懂的 Python 多线程爬虫代码?

  •  
  •   MyFaith ·
    MyFaith · 2016-07-17 12:14:14 +08:00 · 7705 次点击
    这是一个创建于 3042 天前的主题,其中的信息可能已经有所发展或是发生改变。

    看了很多范例,但是还是没有理解,比如要爬取 10 页内容,每页有 30 条数据,那么开启 5 个线程的话,我自己尝试写过,不过这 5 个线程都单独爬取 300 条数据,如何才能做到一个线程爬取两页这样?

    26 条回复    2016-07-18 16:22:14 +08:00
    hard2reg
        1
    hard2reg  
       2016-07-17 12:15:38 +08:00
    一个线程爬取两页循环啊
    lecher
        2
    lecher  
       2016-07-17 12:27:35 +08:00 via Android
    需要一个任务队列,抓取线程从任务队列里面取任务抓取。

    又或者把抓取功能封装成一个独立的任务,主线程通过直接调用的形式直接分配任务。
    bigtan
        3
    bigtan  
       2016-07-17 13:02:39 +08:00   ❤️ 1
    wenyu1001
        4
    wenyu1001  
       2016-07-17 14:10:05 +08:00
    deadEgg
        5
    deadEgg  
       2016-07-17 15:17:32 +08:00
    python 单解释器 无多线程,只有多进程和协程
    deadEgg
        6
    deadEgg  
       2016-07-17 15:18:10 +08:00
    推荐用 celery,比较适合你的需求
    neoblackcap
        7
    neoblackcap  
       2016-07-17 15:23:02 +08:00
    @deadEgg 爬虫明显是 IO 密集型应用,用线程是合适的,推荐协程的,你知道协程就是一种特殊的线程么?怎么就没有线程呢?
    beordle
        8
    beordle  
       2016-07-17 15:33:53 +08:00
    @deadEgg =.= 这个....其实严格来说 python2 是没有协程的,只有多线程和多进程。你正好讲反了。
    deadEgg
        9
    deadEgg  
       2016-07-17 15:45:07 +08:00
    @neoblackcap


    因为 Python 有 GIL,当 CPU 密集,多个线程的代码很有可能是线性执行的。不能任意的切换 context ,所以一般用协程或者进程代替,linux 进程开销是非常小的效率某种程度肯定比线程高

    “无线程”,只是想说线程比较鸡肋


    @beordle
    py2 有 gevent,3 中有 asyncio 。 不知道你说的没有协程依据在哪?
    InkStone
        10
    InkStone  
       2016-07-17 15:49:06 +08:00
    就是当你获得一个 url 的时候,不要直接爬取,而是把它加入一个任务队列。
    然后每个线程从任务队列获取要下载的 url
    deadEgg
        11
    deadEgg  
       2016-07-17 15:55:00 +08:00
    @neoblackcap
    还有就是 “程就是一种特殊的线程” , 我认为这不是不对的,因为协程和线程概念上可能等同,但是实现上是差异非常大的
    协程应该来说是 用户空间的线程
    MyFaith
        12
    MyFaith  
    OP
       2016-07-17 15:56:27 +08:00
    @bigtan 这个在使用参数的时候要如何处理呢?
    MyFaith
        13
    MyFaith  
    OP
       2016-07-17 15:56:48 +08:00
    @InkStone 感谢,你这么说我大概理解了。
    neoblackcap
        14
    neoblackcap  
       2016-07-17 16:16:56 +08:00 via iPhone
    @deadEgg 爬虫这种东西我真看不出有什么需要线性执行,而且线性执行的话,你用进程不上锁,不同步?
    Linux 的进程是轻量化,但线程的创建成本更低。实在搞不懂你所说的进程成本更低在何处。
    在 90%都是网络 IO 的情况下,搞不懂为什么不用线程?这里又不需要大量的计算,进行一次上下文切换会比动不动就上百毫秒的网络请求成本更高?
    neoblackcap
        15
    neoblackcap  
       2016-07-17 16:25:47 +08:00 via iPhone
    @deadEgg 我承认协程更应该是用户态线程,但 Python 的实现真的是用户态线程么?目前各类实现中大多还是用 1:1 模型,你说的协程在这里比线程优秀多少我还是很怀疑的
    jugelizi
        16
    jugelizi  
       2016-07-17 16:31:54 +08:00   ❤️ 1
    多进程吧
    可以四核一起跑
    deadEgg
        17
    deadEgg  
       2016-07-17 16:38:47 +08:00
    @neoblackcap 你讲的是悖论,非用户态线程就不存在 GIL 问题
    neoblackcap
        18
    neoblackcap  
       2016-07-17 16:47:31 +08:00 via iPhone
    @deadEgg 你为什么这么执着 GIL ?你在网络请求的时候不等待吗?等待的时候上下文切换,有没有 GIL 又有什么问题?
    em70
        19
    em70  
       2016-07-17 16:56:38 +08:00
    要做多线程,先把任务分成多个子任务啊,让每个线程负责一个,你不分配子任务,线程又不是智慧生物,它又不会自己协作
    justou
        20
    justou  
       2016-07-17 17:01:36 +08:00
    windows 下 IO 密集型任务(不单只网络 IO)优先考虑线程, 用进程太浪费, 每个进程都是一个单独的解释器(1000 个线程跟 1000 个进程区别应该还是较大的, 内存方面)

    用队列传输消息, 设计好程序结构, Process 跟 Thread 的切换也就改改几个 import, 具体问题 profile 看下, 谁的效率高(内存使用, 执行时间)就用谁

    协程是在一个线程中切换的, 协程切换比线程切换更流畅, 花销也更小, 这是听来的, 没在实际中用过(错了请纠正我), 习惯了队列传输模式, 也方便线程改进程

    Unix 下不清楚, 不乱说
    hard2reg
        21
    hard2reg  
       2016-07-17 17:31:32 +08:00
    好吧。。。是我理解错了
    wizardforcel
        22
    wizardforcel  
       2016-07-17 17:40:45 +08:00
    每个线程拿到自己的编号,然后根据编号排数据。考虑你说的按页面编排,每个线程从“起始页+线程编号*2 ”开始抓,步长为“线程总数*2 ”。不要用一个全局的变量来记录当前位置,锁的开销很大。
    tumb8r
        23
    tumb8r  
       2016-07-17 18:07:16 +08:00   ❤️ 1
    @bigtan multiprocessing.dummy 的 Pool.map 容易假死不动
    practicer
        24
    practicer  
       2016-07-17 19:29:49 +08:00   ❤️ 6
    最近一个月一直在理清爬虫多任务化的问题,结论是在 python 爬虫领域,实现多任务的正确姿势是单线程异步 IO 模型。
    在写出同时能爬取多个链接的代码前,楼主必须先理解这个模型的原理:单线程异步 IO 模型的基础---- [事件循环+回调函数] 模型。

    先说事件循环,它是一个系统,这个系统内由以下函数组成:
    1.连接服务器的函数,
    2.发送 GET 请求到服务器的函数,
    3.接收并读取服务器响应的函数,
    4.最后是解析响应内容用来获取数据的函数;

    可以看到这几个函数基本就是我们写普通爬虫代码的一个流程:函数与函数之间都需要等待,也就是说只有函数 1 返回结果后才能执行函数 2 ,函数 2 返回结果后才能执行函数 3 。。。

    那么,用事件循环来控制这些函数和写普通爬虫代码有什么不同呢?

    答案就是,事件循环可以由程序员手动控制多个爬虫(任务),而不是像多线程那般把分配权交给操作系统随机分配。
    当一号爬虫(任务)在执行函数 1 时,一执行完就立即返回(意思就是不等待最后获取值),并将控制权交还给事件循环,交给它之后,开始执行二号爬虫(任务);二号爬虫开始执行函数 1 ,同样,一执行完就立即返回,并将控制权交给事件循环;交给它之后,开始执行三号爬虫(任务),三号爬虫开始执行函数 1 ,同样,一执行完就立即返回。。。以此类推。。

    问题来了,当一号爬虫(任务)的函数 1 处理完并返回值后该如何处理这个值?这个时候,回调函数就能派上用场了,回调函数起到通知的作用,告知循环系统在咱们这一号爬虫(任务)有个函数处理完了,要用它返回的结果来执行函数 2 。循环机制听到通知后,便开始执行一号爬虫(任务)的函数 2 。执行函数 2 和执行函数 1 的机制完全相同,也是一执行完就返回,并立即将分配权交给循环机制,这样让循环机制同时地、不停地处理二号、三号、四号。。。爬虫(任务)。

    最后,直到一号爬虫获取最终想要爬取的数据,同时,二号、三号、四号。。。爬虫仍在同时工作,没有停止,然后二号爬虫也执行完了所有函数并得到数据,然后是三号、四号。。。

    以上就是基于事件循环+回调函数的异步 IO 爬虫模型,虽然是单线程但是效率非常高,像 twisted , tornado 这些流行的异步 IO 库基本都是基于这个模型。但是这种模型也有很多弊端,最令人不爽的两个地方是, 1.调试起来非常恼火,根本看不到 traceback 。 2.一旦事件循环内的函数数量变多,代码逻辑也变的复杂。

    So.python 3.4 在基于事件循环+回调函数模型的基础上利用生成器的特性,搞了一套改良版的异步 IO 模型,完美解决了以上两个问题。在 python3.5 进一步迭代,推出了 asyncio 库,再次优化了 python 异步 IO 性能。

    目前我会写简单的基于事件循环+回调函数的异步 IO 爬虫,仍在理清基于生成器的异步 IO 模型,如果楼主要深入了解,请参考: http://aosabook.org/en/500L/a-web-crawler-with-asyncio-coroutines.html ( python 之父写的异步爬虫教程)
    menc
        25
    menc  
       2016-07-18 15:56:46 +08:00
    @deadEgg
    1. IO 密集型多线程是可用的。
    2 多进程的额外开销太大

    爬虫还是应该尽量利用多线程
    bobuick
        26
    bobuick  
       2016-07-18 16:22:14 +08:00
    爬虫直接线程模式啊。辣么多 IO 。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5192 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 03:48 · PVG 11:48 · LAX 19:48 · JFK 22:48
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.