第十二节段 -- 爬虫04:【进程;线程;协程】

  • 优先使用多线程,到后来可以使用多进程套用多线程;其中协程最快

1. 线程

1. 简介

  1. 导入:我们之前写的爬虫都是单个线程的?这怎么够?一旦一个地方卡到不动了,那不就永远等待下去了?为此我们可以使用多线程或者多进程来处理。

    不建议你用这个,不过还是介绍下了,如果想看可以看看下面,不想浪费时间直接看。

  2. 如何使用:爬虫使用多线程来处理网络请求,使用线程来处理URL队列中的url,然后将url返回的结果保存在另一个队列中,其它线程在读取这个队列中的数据,然后写到文件中去。

2.主要组成成分

2.1. URL 队列和结果队列

  • 将将要爬去的url放在一个队列中,这里使用标准库Queue。访问url后的结果保存在结果队列中。

    初始化一个URL队列:

from queue import Queue
urls_queue = Queue()
out_queue = Queue()

2.2. 包装类 – 实现多线程

  • 使用多个线程,不停的取URL队列中的url,并进行处理:
import threading

class ThreadCrawl(threading.Thread):
    def __init__(self, queue, out_queue):
        threading.Thread.__init__(self)
        self.queue = queue
        self.out_queue = out_queue
	
    # 需要重写run()方法
    def run(self):
        while True:
            item = self.queue.get()
            
  • 如果队列为空,线程就会被阻塞,直到队列不为空。处理队列中的一条数据后,就需要通知队列已经处理完该条数据。

2.3. 函数包装 – 实现多线程

from threading import Thread
def func(args)
    pass
if __name__ == '__main__':
    info_html = Queue()
    t1 = Thread(target=func,args=(info_html,))
    

2.4. 进程池

# 简单往队列中传输线程数
import threading
import time
import queue

class Threadingpool():
    def __init__(self,max_num = 10):
        self.queue = queue.Queue(max_num)
        for i in range(max_num):
            self.queue.put(threading.Thread)

    def getthreading(self):
        return self.queue.get()

    def addthreading(self):
        self.queue.put(threading.Thread)

def func(p,i):
    time.sleep(1)
    print(i)
    p.addthreading()

if __name__ == "__main__":
    p = Threadingpool()
    for i in range(20):
        thread = p.getthreading()
        t = thread(target = func, args = (p,i))
        t.start()
        

3. Queue 模块中的常用方法

Python的 Queue 模块中提供了同步的、线程安全的队列类,包括 FIFO(先入先出)队列 Queue,LIFO(后入先出)队列 LifoQueue,和优先级队列 PriorityQueue。这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步:

  1. Queue.qsize() 返回队列的大小

  2. Queue.empty() 如果队列为空,返回True,反之False

  3. Queue.full() 如果队列满了,返回True,反之False

  4. Queue.fullmaxsize 大小对应

  5. Queue.get([block[, timeout]]) 获取队列,timeout 等待时间

  6. Queue.get_nowait() 相当 Queue.get(False)

  7. Queue.put(item) 写入队列,timeout 等待时间

  8. Queue.put_nowait(item) 相当 Queue.put(item, False)

  9. Queue.task_done() 在完成一项工作之后,Queue.task_done() 函数向任务已经完成的队列发送一个信号

  10. Queue.join() 实际上意味着等到队列为空,再执行别的操作

4. 实例

爬取糗事百科1-13页

  1. 方案01:通过函数
from fake_useragent import UserAgent
import requests
from lxml import etree
from threading import Thread
from queue import Queue
from time import time

def get_data(url_queue):
    while not url_queue.empty():
        url = url_queue.get()
        headers = {'User-Agent': UserAgent().chrome}
        resp = requests.get(url, headers=headers)
        e = etree.HTML(resp.text)

        # infos_span = e.xpath('//div[@class="content"]/span[1]')
        # for span in infos_span:
        #    info = span.xpath('string(.)')
        infos = [span.xpath('string(.)') for span in e.xpath('//div[@class="content"]/span[1]')]
        with open('duanzi.txt', 'a', encoding='utf-8') as f:
            for info in infos:
                f.write(info + '\n')

if __name__ == '__main__':
    start = time()
    base_url = 'https://www.qiushibaike.com/text/page/{}/'
    url_queue = Queue()
    for i in range(1, 11):
        url = base_url.format(i)
        url_queue.put(url)

    thread_list = []
    for i in range(2):
        t1 = Thread(target=get_data, args=(url_queue,))
        thread_list.append(t1)
        t1.start()
		# t1.join() # 这样写会变成串行!
    for t in thread_list:
        t.join()

    end = time()
    print('{}:{}----{}'.format(end,start,end-start))
    
  1. 方案02:通过类
from fake_useragent import UserAgent
import requests
from lxml import etree
from threading import Thread
from queue import Queue
import time

class Spider1(Thread):
    # 线程之间是可以通信的,所以不需要这么写!
	# def __init__(self, url_queue):
    #     Thread.__init__(self)
    #     self.url_queue = url_queue
        
    def run(self):
        while not url_queue.empty():
            url = url_queue.get()
            print('get:{}'.format(url))
            headers = {'User-Agent': UserAgent().chrome}
            resp = requests.get(url, headers=headers)
            e = etree.HTML(resp.text)

            # infos_span = e.xpath('//div[@class="content"]/span[1]')
            # for span in infos_span:
            #    info = span.xpath('string(.)')
            infos = [span.xpath('string(.)') for span in e.xpath('//div[@class="content"]/span[1]')]
            with open('duanzi.txt', 'a', encoding='utf-8') as f:
                for info in infos:
                    f.write(info + '\n')

if __name__ == '__main__':
    base_url = 'https://www.qiushibaike.com/text/page/{}/'
    url_queue = Queue()
    for i in range(1, 11):
        url = base_url.format(i)
        url_queue.put(url)

    thread_obj = []
    for i in range(2):
        t = Spider1()
        t.start()
        thread_obj.append(t)

    for t in thread_obj:
        t.join()

2. 进程

multiprocessing 是 python 的多进程管理包,和 threading.Thread 类似

1. multiprocessing模块

  • 直接从侧面用 subprocesses 替换线程使用 GIL 的方式,由于这一点,multiprocessing 模块可以让程序员在给定的机器上充分的利用 CPU。在 multiprocessing 中,通过创建 Process 对象生成进程,然后调用它的 start() 方法。
from multiprocessing import Process

def func(name):
    print('hello', name)

if __name__ == "__main__":
    p = Process(target=func,args=('sxt',))
    p.start()
    p.join()  # 等待进程执行完毕
    

2. Manager类,实现数据共享

  • 在使用并发设计的时候最好尽可能的避免共享数据,尤其是在使用多进程的时候。 如果你真有需要 要共享数据,可以使用由 Manager() 返回的 manager 提供 list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array 类型的支持
from multiprocessing import Process,Manager,Lock

def print_num(info_queue,l,lo):
    with lo:
        for n in l:
            info_queue.put(n)

def updata_num(info_queue,lo):
    with lo:
        while not info_queue.empty():
            print(info_queue.get())

if __name__ == '__main__':
        manager = Manager()
        into_html = manager.Queue()
        lock = Lock()
        a = [1, 2, 3, 4, 5]
        b = [11, 12, 13, 14, 15]

        p1 = Process(target=print_num,args=(into_html,a,lock))
        p1.start()
        p2 = Process(target=print_num,args=(into_html,b,lock))
        p2.start()
        p3 = Process(target=updata_num,args=(into_html,lock))
        p3.start()
        p1.join()
        p2.join()
        p3.join()
        

3. 进程池

  • 进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。
  • 进程池中有两个方法:
    • apply同步执行-串行
    • apply_async异步执行-并行
from multiprocessing import Pool,Manager

def print_num(info_queue,l):
    for n in l:
        info_queue.put(n)

def updata_num(info_queue):
    while not info_queue.empty():
        print(info_queue.get())

if __name__ == '__main__':
    html_queue =Manager().Queue()
    a=[11,12,13,14,15]
    b=[1,2,3,4,5]
    pool = Pool(3) # 进程池有3个进程
    pool.apply_async(func=print_num,args=(html_queue,a))
    pool.apply_async(func=print_num,args=(html_queue,b))
    pool.apply_async(func=updata_num,args=(html_queue,))
    # 注意线程不同,进程中,close和join方法反过来写
    pool.close() # 这里join一定是在close之后,且必须要加join,否则主进程不等待创建的子进程执行完毕
    pool.join() # 进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭
    

4. 实例

from multiprocessing import Process
from fake_useragent import UserAgent
import requests
from lxml import etree
# from queue import Queue
from multiprocessing import Manager
from time import time

def get_data(url_queue):
    while not url_queue.empty():
        url = url_queue.get()
        print('get:{}'.format(url))
        headers = {'User-Agent': UserAgent().chrome}
        resp = requests.get(url, headers=headers)
        e = etree.HTML(resp.text)
        infos = [span.xpath('string(.)') for span in e.xpath('//div[@class="content"]/span[1]')]
        with open('duanzi.txt', 'a', encoding='utf-8') as f:
            for info in infos:
                f.write(info + '\n')

if __name__ == '__main__':
    start =time()
    base_url = 'https://www.qiushibaike.com/text/page/{}/'
    url_queue = Manager().Queue()
    for i in range(1, 11):
        url = base_url.format(i)
        url_queue.put(url)

    process_list = []
    for i in range(2):
        p1 = Process(target=get_data, args=(url_queue,))
        process_list.append(p1)
        p1.start()

    for p in process_list:
        p.join()
    end = time()
    print('{}:{}----{}'.format(end,start,end-start))
    

3. 协程 Gevent

  1. Python通过 yield提供了对协程的基本支持,但是不完全。而第三方的 gevent 为Python提供了比较完善的协程支持。

    gevent 是第三方库,通过 greenlet 实现协程,其基本思想是:

    当一个 greenlet 遇到IO操作时,比如访问网络,就自动切换到其他的 greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了 gevent 为我们自动切换协程,就保证总有 greenlet 在运行,而不是等待IO。

    由于切换是在IO操作时自动完成,所以 gevent 需要修改Python自带的一些标准库,这一过程在启动时通过 monkey.patch 完成:

from gevent import monkey; monkey.patch_socket()
import gevent

def f(n):
    for i in range(n):
        print(gevent.getcurrent(), i)

g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()

  1. 如需将协程应用到爬虫,如下:
from gevent import monkey
import gevent
import requests

def f(url):
    print('GET: %s' % url)
    resp = requests.get(url)
    data = requests.text
    print('%d bytes received from %s.' % (len(data), url))

# 同时启动线程
gevent.joinall([
        gevent.spawn(f, 'https://www.python.org/'),
        gevent.spawn(f, 'https://www.163.com/'),
        gevent.spawn(f, 'https://www.baidu.com/'),
])
  • 示例:
import gevent
from gevent import monkey

monkey.patch_all()
import requests
from fake_useragent import UserAgent
from lxml import etree
from queue import Queue
from time import time

def get_data(url_queue):
    while not url_queue.empty():
        url = url_queue.get()
        print('get:{}'.format(url))
        headers = {'User-Agent': UserAgent().chrome}
        resp = requests.get(url, headers=headers)
        e = etree.HTML(resp.text)
        infos = [span.xpath('string(.)') for span in e.xpath('//div[@class="content"]/span[1]')]
        with open('duanzi.txt', 'a', encoding='utf-8') as f:
            for info in infos:
                f.write(info + '\n')
        print('success:{}'.format(url))

if __name__ == '__main__':
    start = time()
    base_url = 'https://www.qiushibaike.com/text/page/{}/'
    url_queue = Queue()
    for i in range(1, 11):
        url = base_url.format(i)
        url_queue.put(url)

    gevent.joinall([
        gevent.spawn(get_data, url_queue),
        gevent.spawn(get_data, url_queue)

    ])
    end = time()
    print('{}:{}----{}'.format(end, start, end - start))

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值