一个简易的python 爬虫源码分析

爬虫流程

之前没了解过相关东西,觉得大体流程无非是发送http request, 然后把爬来的数据进行存储。

读了一个相关代码实现后,往深里钻,里面东西还特别多。核心流程还是一样,但是考虑到效率就会涉及到很多东西。流程方面可以参考这里

代码仓库

网上谁便找了个,代码量不大,适合学习使用这里

代码解读

类图

在这里插入图片描述

  • 其中WebSpider 是对外的门面类,其中聚合了抓取线程、解析线程、保存结果线程;还聚合了抓取任务队列、解析任务队列、保存任务队列。
  • 其中线程有反过来关联WebSpider 一次来操作队列; 线程中有关联了具体干活的Worker类。
  • Worker泛化具体的抓取、解析、保存操作。里面自定义具体操作。抓取里面进行http请求,解析里面进行正则匹配, 保存进行输出重定向。

流程图

维护了三个任务队列:Fetch, Parser, Saver 分别代表抓取队列、解析队列、保存队列。
初始

Fetch QueueParser QueueSaver Queue1、http request的结果2、解析html文档,提取需要的结果3、抓取当前页面中的其他链接Fetch QueueParser QueueSaver Queue
  • 其中,1、2、3步骤都在各自的线程中运行,初始时会在Fetch Queue 中塞入一个初始url,以此保证后续动作的进行。
  • 整个流程由队列中数据驱动进行,至于线程停止的时机,直到所有任务队列为空时,因为Parser会把解析到的次级url放入Fetch队列,那么队列如何保证可以有为空的条件呢,这里Paser干活类中有个控制抓取url层级的值,当抓取的url层级超过限制的层级后,就不会再往Fetch 队列中插入了。

关键知识

布隆过滤

  • python 引入pybloom_live 包来使用。可以理解为hashmap的替代,但是在数据量巨大时hashmap 会有空间占用巨大的问题。 但是布隆过滤器也有缺点,他可以告诉“某样东西一定不存在或者可能存在”,即无法保证确定的存。
  • 此程序中在Fetch 获取到新url后会更新过滤器中值。在数据量小时,看不出布隆过滤的优势。

Queue

此程序的关键代码理解我觉得主要是queue的使用,剖开具体的操作细节,代码运行流程可以通过Queue 来看明白。
可以看下面一个生产者–消费者问题,代码中的queue 的使用是一样的。


def test_multiply_thread_queue():
    num_fetch_threads = 5
    enclosure_queue = Queue()

    def consumer(i, q):
        while True:
            print("consumer begin")
            url = q.get(block=True)
            print('%s: consumer: %s end. %s' % (i, url, time.ctime()))
            # time.sleep(1)
            q.task_done()

    for i in range(num_fetch_threads):
        worker = Thread(target=consumer, args=(i, enclosure_queue))
        worker.setDaemon(True)
        worker.start()

    def producer(i, q):
        itm = 'url {}'.format(i)
        # print('producer: %s begin. %s' % (itm, time.ctime()))
        #time.sleep(i)
        q.put(itm)
        print('producer: %s end. %s' % (itm, time.ctime()))

    num_product_threads = 5
    for i in range(num_product_threads):
        worker = Thread(target=producer, args=(i, enclosure_queue))
        worker.setDaemon(True)
        worker.start()

    # Now wait for the queue to be empty, indicating that we have
    # processed all of the downloads.
    print('*** Main thread waiting')
    enclosure_queue.join()
    print('*** Done')
  • Queue 自身保证线程安全。这里join 为’Blocks until all items in the queue have been gotten and processed.’,等待所有任务被取出, 而后往下执行。这里join的效果可以通过调整consumer和producer中的sleep看出效果。
  • 当生产者执行较快,消费者线程肯定能消费完才结束程序;
    当生产者较慢,消费者较快时,生产者可能没生产几个就会退出程序,因为在enclosure_queue.join()时发现队列为空,主线程直接往下执行了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章