• python爬虫入门(四)利用多线程爬虫


     多线程爬虫

     先回顾前面学过的一些知识

    1.一个cpu一次只能执行一个任务,多个cpu同时可以执行多个任务
    2.一个cpu一次只能执行一个进程,其它进程处于非运行状态
    3.进程里包含的执行单元叫线程,一个进程可以包含多个线程
    4.一个进程的内存空间是共享的,每个进程里的线程都可以使用这个共享空间
    5.一个线程在使用这个共享空间的时候,其它的线程必须等待(阻塞状态)
    6.互斥锁作用就是防止多个线程同时使用这块内存空间,先使用的线程会将空间上锁,其它的线程处于等待状态。等锁开了才能进
    7.进程:表示程序的一次执行
    8.线程:CPU运算的基本调度单位
    9.GIL(全局锁):python里的执行通行证,而且只有一个。拿到通行证的线程就可以进入CPU执行任务。没有GIL的线程就不能执行任务
    10.python的多线程适用于大量密集的I/O处理
    11.python的多进程适用于大量的密集并行计算

     多线程爬取糗事百科

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    
    # 使用了线程库
    import threading
    # 队列
    from Queue import Queue
    # 解析库
    from lxml import etree
    # 请求处理
    import requests
    # json处理
    import json
    import time
    
    class ThreadCrawl(threading.Thread):
        def __init__(self, threadName, pageQueue, dataQueue):
            #threading.Thread.__init__(self)
            # 调用父类初始化方法
            super(ThreadCrawl, self).__init__()
            # 线程名
            self.threadName = threadName
            # 页码队列
            self.pageQueue = pageQueue
            # 数据队列
            self.dataQueue = dataQueue
            # 请求报头
            self.headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36'}
    
        def run(self):
            print "启动 " + self.threadName
            while not CRAWL_EXIT:
                try:
                    # 取出一个数字,先进先出
                    # 可选参数block,默认值为True
                    #1. 如果对列为空,block为True的话,不会结束,会进入阻塞状态,直到队列有新的数据
                    #2. 如果队列为空,block为False的话,就弹出一个Queue.empty()异常,
                    page = self.pageQueue.get(False)
                    url = "http://www.qiushibaike.com/8hr/page/" + str(page) +"/"
                    #print url
                    content = requests.get(url, headers = self.headers).text
                    time.sleep(1)
                    self.dataQueue.put(content)
                    #print len(content)
                except:
                    pass
            print "结束 " + self.threadName
    
    class ThreadParse(threading.Thread):
        def __init__(self, threadName, dataQueue, filename, lock):
            super(ThreadParse, self).__init__()
            # 线程名
            self.threadName = threadName
            # 数据队列
            self.dataQueue = dataQueue
            # 保存解析后数据的文件名
            self.filename = filename
            #
            self.lock = lock
    
        def run(self):
            print "启动" + self.threadName
            while not PARSE_EXIT:
                try:
                    html = self.dataQueue.get(False)
                    self.parse(html)
                except:
                    pass
            print "退出" + self.threadName
    
        def parse(self, html):
            # 解析为HTML DOM
            html = etree.HTML(html)
    
            node_list = html.xpath('//div[contains(@id, "qiushi_tag")]')
    
            for node in node_list:
                # xpath返回的列表,这个列表就这一个参数,用索引方式取出来,用户名
                username = node.xpath('./div/a/@title')[0]
                # 图片连接
                image = node.xpath('.//div[@class="thumb"]//@src')#[0]
                # 取出标签下的内容,段子内容
                content = node.xpath('.//div[@class="content"]/span')[0].text
                # 取出标签里包含的内容,点赞
                zan = node.xpath('.//i')[0].text
                # 评论
                comments = node.xpath('.//i')[1].text
    
                items = {
                    "username" : username,
                    "image" : image,
                    "content" : content,
                    "zan" : zan,
                    "comments" : comments
                }
    
                # with 后面有两个必须执行的操作:__enter__ 和 _exit__
                # 不管里面的操作结果如何,都会执行打开、关闭
                # 打开锁、处理内容、释放锁
                with self.lock:
                    # 写入存储的解析后的数据
                    self.filename.write(json.dumps(items, ensure_ascii = False).encode("utf-8") + "
    ")
    
    CRAWL_EXIT = False
    PARSE_EXIT = False
    
    
    def main():
        # 页码的队列,表示20个页面
        pageQueue = Queue(20)
        # 放入1~10的数字,先进先出
        for i in range(1, 21):
            pageQueue.put(i)
    
        # 采集结果(每页的HTML源码)的数据队列,参数为空表示不限制
        dataQueue = Queue()
    
        filename = open("duanzi.json", "a")
        # 创建锁
        lock = threading.Lock()
    
        # 三个采集线程的名字
        crawlList = ["采集线程1号", "采集线程2号", "采集线程3号"]
        # 存储三个采集线程的列表集合
        threadcrawl = []
        for threadName in crawlList:
            thread = ThreadCrawl(threadName, pageQueue, dataQueue)
            thread.start()
            threadcrawl.append(thread)
    
    
        # 三个解析线程的名字
        parseList = ["解析线程1号","解析线程2号","解析线程3号"]
        # 存储三个解析线程
        threadparse = []
        for threadName in parseList:
            thread = ThreadParse(threadName, dataQueue, filename, lock)
            thread.start()
            threadparse.append(thread)
    
        # 等待pageQueue队列为空,也就是等待之前的操作执行完毕
        while not pageQueue.empty():
            pass
    
        # 如果pageQueue为空,采集线程退出循环
        global CRAWL_EXIT
        CRAWL_EXIT = True
    
        print "pageQueue为空"
    
        for thread in threadcrawl:
            thread.join()
            print "1"
    
        while not dataQueue.empty():
            pass
    
        global PARSE_EXIT
        PARSE_EXIT = True
    
        for thread in threadparse:
            thread.join()
            print "2"
    
        with lock:
            # 关闭文件
            filename.close()
        print "谢谢使用!"
    
    if __name__ == "__main__":
        main()
  • 相关阅读:
    多线程--ThreadLocal类
    常用开发类库支持--UUID及空值处理Optional
    国际化的程序实现及其原理
    浅析java设计模式(一)----异构容器,可以存储任何对象类型为其他类提供该对象
    使用批处理命令注册运行mysql数据库,无需注册mysql服务,可以在任意电脑登录使用
    计算机中位、字长、字的区别
    SQL Server用户自定义数据类型
    简单的回合制小游戏
    单链表创建、删除、查找、插入之C语言实现
    LeetCode-905 Sort Array By Parity Solution (with Java)
  • 原文地址:https://www.cnblogs.com/derek1184405959/p/8449923.html
Copyright © 2020-2023  润新知