使用gevent实现高并发爬虫

简介: 现在给定这么一个场景,有一千个url需要采集,请大家思考下,如何能高效完成采集任务?

现在给定这么一个场景,有一千个url需要采集,请大家思考下,如何能高效完成采集任务?


在上一节课中,我们学到了requests这个强大的第三方库,有的同学会说,我们直接遍历采集就好了,那么会写出如下代码

import time 
import requests
start_time = time.time()
# 为了演示方便使用相同url
urls = ['https://www.zyte.com/blog'] * 1000
headers = {'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'}
for url in urls:
    response = requests.get(url, headers=headers)
    print(response.status_code)
print(f'耗时 {time.time() - start_time} 秒')

经过测试得到耗时 390 秒,可见单个请求去跑效率是很低的,那么我们就要考虑多请求一起跑。 想让计算机实现并发,那就要引入多进程,多线程,协程的概念了,我简单介绍下这三种

多进程

多进程是指在一个应用程序中同时执行多个独立的进程,每个进程有自己独立的内存空间,相互之间完全独立,不会影响彼此。

Python 的 multiprocessing 模块提供了创建和管理进程的功能,可以使用 Process 类来创建进程,使用 Queue 或 Pipe 实现进程间通信。


优点:

  • 能够充分利用多核 CPU,适用于 CPU 密集型任务。
  • 进程之间相对独立:每个进程都有自己的内存空间,避免了多个线程之间的资源竞争问题。
  • 可以充分利用操作系统的调度算法:操作系统可以在不同进程之间进行调度,提高了并发性。


缺点:

  • 进程之间切换开销较大,进程间通信相对复杂。
  • 进程间通信比较复杂:在进程之间进行通信需要使用特殊的机制,如队列、管道等。

多线程

多线程是指在同一进程中执行多个线程,共享进程的内存空间,但每个线程有自己独立的执行流程。 Python 的 threading 模块提供了创建和管理线程的功能,可以使用 Thread 类来创建线程,使用 Lock 或 Semaphore 实现线程间同步。


优点:

  • 相比多进程,线程之间切换开销较小,适用于 IO 密集型任务。
  • 数据共享更方便:线程之间可以直接共享内存,可以方便地进行数据传递和共享。
  • 线程间通信较简单:线程之间可以直接通过共享内存进行通信。


缺点:

  • 由于 GIL(Global Interpreter Lock)的存在,Python 中的多线程并不能实现真正的并行,只能在单个 CPU 核心上执行。


PS. 对于爬虫工程师来说,最简单的选用方法就是,cpu计算密集型就选用多进程,io密集型,如网络请求等就选多线程

协程

协程是一种轻量级的线程,可以在同一线程内实现多个任务之间的切换,而不需要进行线程上下文切换。 Python 3.5 引入了 asyncio 模块,提供了原生的协程支持,使用 async 和 await 关键字定义协程。


优点:

  • 协程可以有效地提高 IO 密集型任务的并发性能,同时减少了线程和进程的资源消耗。

缺点:

由于需要显式地在代码中标记异步操作点,对现有代码的改造较大。


从以上来看,对于爬虫来说,多进程过于重,多线程和协程更适合爬虫,下边分别看下这两种的区别

Demo

多线程

import time
import requests
from concurrent.futures import ThreadPoolExecutor
def download(url):
    response = requests.get(url)
    return response.content
if __name__ == '__main__':
    start_time = time.time()
    urls = ['https://www.zyte.com/blog'] * 1000
    with ThreadPoolExecutor(max_workers=10) as executor:
        results = list(executor.map(download, urls))
        for result in results:
            print(len(result))
    print(f'耗时 {time.time() - start_time} 秒')

在不同的线程数下得到以下数据

协程

import time
import asyncio
import aiohttp
async def download(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.read()
if __name__ == '__main__':
    start_time = time.time()
    urls = ['https://www.zyte.com/blog'] * 1000
    loop = asyncio.get_event_loop()
    tasks = [download(url) for url in urls]
    results = loop.run_until_complete(asyncio.gather(*tasks))
    for result in results:
        print(len(result))
    print(f'耗时 {time.time() - start_time} 秒')

协程耗时 20 秒


由上可见,线程池扩大以后消耗的时间和协程几乎相同,但是在实际的开发中,逻辑往往比这复杂,会涉及到通信等问题,多线程编程需要考虑线程间的同步和竞态条件,需要使用锁、信号量等同步机制来避免多线程之间的冲突,会让代码看起来臃肿,而协程用async 和 await 关键字实现异步操作,写起来更像同步代码,不需要显式的处理线程同步问题,对开发者更友好。


有的同学会想,async 和 await还是麻烦,有没有更简单的实现方法?当然是有的,那就是gevent

gevent

介绍:

gevent是一个基于协程的Python网络库,它使用greenlet在libev或libuv事件循环之上提供高级同步API。


简单来说,可以理解成一个协程框架,让我们快速开发出协程代码


由于是第三方库,需要使用命令安装:pip install gevent

Demo

下边来看代码

from gevent import monkey
import gevent, time, requests
monkey.patch_all(ssl=False)
start_time = time.time()
url_list = ['https://www.zyte.com/blog'] * 1000
def crawler(url):
    response = requests.get(url)
    print(len(response.content))
tasks_list = []
for url in url_list:
    task = gevent.spawn(crawler, url)
    tasks_list.append(task)
gevent.joinall(tasks_list)
print(f'耗时 {time.time() - start_time} 秒')

是不是看起来简洁多了,这也是gevent的一个优点,侵入性小,可以将历史代码很容易的改成协程,实现异步运行。


相信你还对monkey.patch_all(ssl=False)感到好奇, 这是一个猴子补丁,补丁顾名思义是有修补的作用,这个也不例外,可以替换网络请求库的部分行为,从而支持阻塞式请求,可以试下,如果不加这个补丁,程序耗时还是和单个请求一样,注意ssl=False是不对ssl打补丁,解决报requests超过最大递归深度的问题。


带宽问题

在生产环境中,往往一台服务器上有多个任务,所以我们不能让自己的代码把带宽都占满,那会导致线上事故,所以我们需要对并发需要限制,在gevevt中,可以创建一个信号量,限制同时进行的 greenlet 数量,比如我想同时最多5个请求在跑,则可以在代码中加上:semaphore = Semaphore(5),再去运行代码就能防止带宽被占满了

相关文章
|
1月前
|
数据采集 Web App开发 文字识别
高并发数据采集:Ebay商家信息多进程爬虫的进阶实践
高并发数据采集:Ebay商家信息多进程爬虫的进阶实践
|
11月前
|
Web App开发 数据采集 Java
使用asyncio库和多线程实现高并发的异步IO操作的爬虫
使用asyncio库和多线程实现高并发的异步IO操作的爬虫
|
1月前
|
数据采集 XML 数据处理
使用Python实现简单的Web爬虫
本文将介绍如何使用Python编写一个简单的Web爬虫,用于抓取网页内容并进行简单的数据处理。通过学习本文,读者将了解Web爬虫的基本原理和Python爬虫库的使用方法。
|
9天前
|
数据采集 存储 Web App开发
Python爬虫实战:从入门到精通
Python是开发网络爬虫的首选语言,因其简洁语法和丰富库如requests, BeautifulSoup, Scrapy。爬虫涉及HTTP交互、HTML解析及法律道德问题。以下是爬取豆瓣电影Top250的步骤:确定目标,分析网站,安装必要库(requests, BeautifulSoup),编写代码抓取电影名称、评分和简介,处理异常并优化,如设置请求间隔、使用代理IP和遵循Robots协议。
|
12天前
|
数据采集 JSON API
自动化Reddit图片收集:Python爬虫技巧
自动化Reddit图片收集:Python爬虫技巧
|
18天前
|
数据采集 存储 C++
单线程 vs 多进程:Python网络爬虫效率对比
本文探讨了Python网络爬虫中的单线程与多进程应用。单线程爬虫实现简单,但处理速度慢,无法充分利用多核CPU。而多进程爬虫通过并行处理提高效率,更适合现代多核架构。代码示例展示了如何使用代理IP实现单线程和多进程爬虫,显示了多进程在效率上的优势。实际使用时还需考虑代理稳定性和反爬策略。
单线程 vs 多进程:Python网络爬虫效率对比
|
19天前
|
数据采集 存储 中间件
Python高效爬虫——scrapy介绍与使用
Scrapy是一个快速且高效的网页抓取框架,用于抓取网站并从中提取结构化数据。它可用于多种用途,从数据挖掘到监控和自动化测试。 相比于自己通过requests等模块开发爬虫,scrapy能极大的提高开发效率,包括且不限于以下原因: 1. 它是一个异步框架,并且能通过配置调节并发量,还可以针对域名或ip进行精准控制 2. 内置了xpath等提取器,方便提取结构化数据 3. 有爬虫中间件和下载中间件,可以轻松地添加、修改或删除请求和响应的处理逻辑,从而增强了框架的可扩展性 4. 通过管道方式存储数据,更加方便快捷的开发各种数据储存方式
|
20天前
|
数据采集 XML 前端开发
Python爬虫:BeautifulSoup
这篇内容介绍了Python中BeautifulSoup库的安装和使用。首先,通过在命令行输入`pip install bs4`进行安装,或使用清华源加速。接着讲解BeautifulSoup的基本概念,它是一个用于数据解析的工具,便于处理HTML和XML文档。与正则表达式不同,BeautifulSoup提供更方便的方式来查找和操作标签及其属性。 文章详细阐述了BeautifulSoup的两个主要方法:`find`和`find_all`。`find`方法用于查找单个指定标签,可结合属性字典进行精确选择;`find_all`则返回所有匹配标签的列表。通过这些方法,可以方便地遍历和提取网页元素。
25 0
|
20天前
|
数据采集 前端开发 JavaScript
Python爬虫入门
网络爬虫是自动抓取网页数据的程序,通过URL获取网页源代码并用正则表达式提取所需信息。反爬机制是网站为防止爬取数据设置的障碍,而反反爬是对这些机制的对策。`robots.txt`文件规定了网站可爬取的数据。基础爬虫示例使用Python的`urllib.request`模块。HTTP协议涉及请求和响应,包括状态码、头部和主体。`Requests`模块是Python中常用的HTTP库,能方便地进行GET和POST请求。POST请求常用于隐式提交表单数据,适用于需要发送复杂数据的情况。
21 1
|
1月前
|
数据采集 Web App开发 数据处理
Lua vs. Python:哪个更适合构建稳定可靠的长期运行爬虫?
Lua vs. Python:哪个更适合构建稳定可靠的长期运行爬虫?

热门文章

最新文章