多线程与协程爬虫

mac2024-06-05  47

前言

网络爬虫是一种高IO密集型任务,所以传统的进程或者多进程并不适合网络爬虫。虽然由于CPython中全局解释器锁GIL的存在,无法真正意义上的实现多线程,但这种“不完美的多线程”依然可以大大提高爬虫效率,当然在提高爬虫效率方面还有大家所熟知的协程。

多线程与协程的简单介绍

比较官方的介绍我就不说了,毕竟瞅了一眼一大串,这个快节奏的时代相信也没有多少人看到下去,哈哈。 简单的讲,如果把一个进程看成一个空间,那么多进程就是多个可以互相隔离的并行空间,而多线程则是在一个进程空间中开辟出多个并行的线程,这也就意味着多个线程间是共享资源的,因此多线程相比于多进程的数据安全性更低,这也是CPython引入GIL的原因。那么如何理解协程呢,事实上,多线程的本质是在一个进程的基础上实现高并发,而协程则是在一个线程的基础上实现高并发,协程的调度是通过代码来实现的,而不是CPU。(还是写了一大串哈)

一言不合上代码

下面以访问百度关于"爬虫"关键字为例,获取其前6页并解析相关信息。抓取时间已在代码中展出。

from gevent import monkey;monkey.patch_all() from lxml import etree from concurrent.futures import ThreadPoolExecutor import requests import time import gevent def wrapper_statistics_time(func): """设置一个计算函数运行时间的装饰器""" def inner(*args, **kwargs): begin_time = time.time() ret = func(*args, **kwargs) end_time = time.time() print(end_time - begin_time) return ret return inner class BaiduSpider(object): """ 实现单进程/单线程爬虫类 """ def __init__(self): self.url = 'https://www.baidu.com/s?ie=utf-8&mod=1&isbd=1&isid=81bc974200085a24&wd=%E7%88%AC%E8%99%AB&pn={}&oq=%E7%88%AC%E8%99%AB&ie=utf-8&usm=3&rsv_pq=81bc974200085a24&rsv_t=4c40tETSrHHHq4V09jv2eAYXBkt9KoZkN29KphRvLK%2BDMzFC0x9jP1rWdG0&bs=%E7%88%AC%E8%99%AB&rsv_sid=undefined&_ss=1&clist=&hsug=&f4s=1&csor=0&_cr1=20697' self.headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.14 (KHTML, like Gecko) Chrome/24.0.1292.0 Safari/537.14' } def get_response(self, url): return requests.get(url, headers=self.headers) def get_content(self, content): html = etree.HTML(content.replace('<em>', '').replace('</em>', '')) element = html.xpath('//h3[@class="t"]') for detail_element in element: yield detail_element.xpath('./a/text()') def schedule(self, pn): response = self.get_response(self.url.format(pn)) data = response.content.decode() for i in self.get_content(data): print(i) @wrapper_statistics_time def run(self, page): for i in range(page): pn = i * 10 self.schedule(pn) class ThreadBaidu(BaiduSpider): """ 继承自父类BaiduSpider,实现多线程 """ def __init__(self): super().__init__() self.tpool = ThreadPoolExecutor(max_workers=20) # 设置线程池中允许通过的线程数量 @wrapper_statistics_time def run(self, page): t_list = [] for i in range(page): pn = i * 10 t = self.tpool.submit(self.schedule, pn) t_list.append(t) self.tpool.shutdown() class AsyncBaidu(BaiduSpider): """ 继承自父类BaiduSpider,实现多协程 """ def __init__(self): super().__init__() @wrapper_statistics_time def run(self, page): a_list = [] for i in range(page): pn = i * 10 g = gevent.spawn(self.schedule, pn) a_list.append(g) for i in a_list: i.join() if __name__ == '__main__': # a = BaiduSpider() # 单进程/单线程当前用时3.900386333465576 # a.run(6) # b = ThreadBaidu() # 多线程当前用时0.8708631992340088 # b.run(6) c = AsyncBaidu() # 多协程当前用时0.8858425617218018 c.run(6)
最新回复(0)