推荐学习书目
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 · Jul 17, 2016 · 8496 views
    This topic created in 3617 days ago, the information mentioned may be changed or developed.

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

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

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


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

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


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

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

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

    Unix 下不清楚, 不乱说
    hard2reg
        21
    hard2reg  
       Jul 17, 2016
    好吧。。。是我理解错了
    wizardforcel
        22
    wizardforcel  
       Jul 17, 2016
    每个线程拿到自己的编号,然后根据编号排数据。考虑你说的按页面编排,每个线程从“起始页+线程编号*2 ”开始抓,步长为“线程总数*2 ”。不要用一个全局的变量来记录当前位置,锁的开销很大。
    tumb8r
        23
    tumb8r  
       Jul 17, 2016   ❤️ 1
    @bigtan multiprocessing.dummy 的 Pool.map 容易假死不动
    practicer
        24
    practicer  
       Jul 17, 2016   ❤️ 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  
       Jul 18, 2016
    @deadEgg
    1. IO 密集型多线程是可用的。
    2 多进程的额外开销太大

    爬虫还是应该尽量利用多线程
    bobuick
        26
    bobuick  
       Jul 18, 2016
    爬虫直接线程模式啊。辣么多 IO 。
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   3977 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 199ms · UTC 05:15 · PVG 13:15 · LAX 22:15 · JFK 01:15
    ♥ Do have faith in what you're doing.