python爬虫之多线程、多进程爬虫

2023-05-16

一、原因

多线程对爬虫的效率提高是非凡的,当我们使用python的多线程有几点是需要我们知道的:

1.Python的多线程并不如java的多线程,其差异在于当python解释器开始执行任务时,受制于GIL(全局解释所),Python 的线程被限制到同一时刻只允许一个程执行这样一个执行模型。
2.Python 的线程更适用于处理 I/O 和其他需要并发行的阻塞操作(比如等待 I/O、等待从数据库获取数据等等),而不是需要多处理器行的计算密集型任务。幸运的是,爬虫大部分时间在网络交互上,所以可以使用多线程来编写爬虫。
3.这点其实和多线程关系不大,scrapy的并发并不是采用多线程来实现,它是一个twisted应用,通过异步非阻塞来达到并发,这个后面我会写文章来讲解。
4.Python中当你想要提高执行效率,大部分开发者是通过编写多进程来提高运行效率,使用multiprocessing进行并行编程,当然,你可以编写多进程爬虫来爬取信息,缺点是每个进程都会有自己的内存,数据多的话,内存会吃不消。
5.使用线程有什么缺点呢,缺点就是你在编写多线程代码时候,要注意死锁的问题、阻塞的问题、以及需要注意多线程之间通信的问题(避免多个线程执行同一个任务)。

二、了解threading

我们通过 threading 模块来编写多线程代码,或者你可以使用 from concurrent.futures import ThreadPoolExecutor (线程池)也可以达到同样的目的,线程池的知识我会在后续讲。

我们先看怎么编写线程代码,以及它如何使用,简单的示例:

import time
from threading import Thread

def countdown(n):
	while n > 0:
		print('T-minus', n)
		n -= 1
		time.sleep(5)
	
t = Thread(target=countdown, args=(10,))
t.start()

countdown是一个计数的方法,正常执行它,我们一般使用countdown(10),就可以达到执行的目的,当你通过线程去调用它时,首先你需要从threading模块中引入Thread,然后,t = Thread(target=countdown, args=(10,)),当你创建好一个线程对象后,该对象并不会立即执行,除非你调用它的 start方法(当你调用 start() 方法时,它会调用你传递进来的函数,并把你传递进来的数传递给该函数),这就是一个简单的线程执行的例子。

你可以查询一个线程对象的状态,看它是否还执行:

if t.is_alive():
	print('Still running')
else:
	print('Completed, Go out !')

Python 解释器直到所有线程都终止前仍保持运行。对于需要长时间运行的线程或
者需要一直运行的后台任务,你应当考虑使用后台线程。

t = Thread(target=countdown, args=(10,), daemon=True)
t.start()

如果你需要终止线程,那么这个线程必须通过编程在某个特定点轮询来退
出。你可以像下边这样把线程放入一个类中:

class CountDownTask:
    def __init__(self):
        self._running = True

    def terminate(self):
        self._running = False

    def run(self, n):
        while self._running and n > 0:
            print('T-minus', n)
            n -= 1
            time.sleep(5)

if __name__ == '__main__':
    c = CountDownTask()
    t = Thread(target=c.run, args=(10,))
    t.start()
    c.terminate()
    t.join()

三、多线程初体验

上面的代码都是单线程,下面我们来看看多线程,并使用它来编写多线程爬虫,不过,在真正编写多线程爬虫之前,我们还要为编写多线程做准备,如何保持各线程之间的通信,在这里,我们使用队列Queue作为多线程之间通信的桥梁。

首先,创建一个被多个线程共享的 Queue 对象,这些线程通过使用 put() 和 get() 操来向队列中添加或者删除元素。

from queue import Queue
from threading import Thread

def producer(out_q):
    while True:
        out_q.put(1)


def consumer(in_q):
    while True:
        data = in_q.get()
        
if __name__ == '__main__':
    q = Queue()
    t1 = Thread(target=consumer, args=(q, ))
    t2 = Thread(target=producer, args=(q, ))
    t1.start()
    t2.start()

上面的produecer(生产者)和consumer(消费者),是两个不同的线程,它们共用一个队列:q,当生产者生产了数据后,消费者会拿到,然后消费它,所以不用担心,产生其他相同的数据。值得注意的是:尽管列是最常见的线程间通信机制,但是仍然可以自己通过创建自己的数据结构并添加需的锁和同步机制来实现线程间通信。

下面我们来编写一个简单的多线程爬虫,方法写的比较臃肿,正常情况下不应该这么写,作为简易的例子我就这么写了:

import re
import time
import requests
import threading
from lxml import etree
from bs4 import BeautifulSoup
from queue import Queue
from threading import Thread


def run(in_q, out_q):
    headers = {
        'Accept': '',
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
        'Connection': 'keep-alive',
        'Cookie': '',
        'DNT': '1',
        'Host': 'www.g.com',
        'Referer': '',
        'Upgrade-Insecure-Requests': '1',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
                      '(KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'
    }
    while in_q.empty() is not True:
        data = requests.get(url=in_q.get(), headers=headers)
        r = data.content
        content = str(r, encoding='utf-8', errors='ignore')
        soup = BeautifulSoup(content, 'html5lib')
        fixed_html = soup.prettify()
        html = etree.HTML(fixed_html)
        nums = html.xpath('//div[@class="col-md-1"]//text()')
        for num in nums:
            num = re.findall('[0-9]', ''.join(num))
            real_num = int(''.join(num))
            out_q.put(str(threading.current_thread().getName()) + '-' + str(real_num))
        in_q.task_done()


if __name__ == '__main__':
    start = time.time()
    queue = Queue()
    result_queue = Queue()
    for i in range(1, 1001):
        queue.put('http://www.g.com?page='+str(i))
    print('queue 开始大小 %d' % queue.qsize())

    for index in range(10):
        thread = Thread(target=run, args=(queue, result_queue, ))
        thread.daemon = True  # 随主线程退出而退出
        thread.start()

    queue.join()  # 队列消费完 线程结束
    end = time.time()
    print('总耗时:%s' % (end - start))
    print('queue 结束大小 %d' % queue.qsize())
    print('result_queue 结束大小 %d' % result_queue.qsize())

在这里插入图片描述
首先构造一个任务队列,一个保存结果的队列。

在这里插入图片描述
构造一个1000页的任务队列。
在这里插入图片描述
使用十个线程来执行run方法消化任务队列,run方法有两个参数,一个任务队列,一个保存结果的队列。
在这里插入图片描述
headers是爬虫的请求头。
在这里插入图片描述
in_q.empty(),是对列的一个方法,它是检测队列是否为空,是一个布尔值,url = in_q.get(),这个操作是拿出队列的一个值出来,然后,把它从队列里删掉。
在这里插入图片描述
out_q.put 队列的添加操作,相当于list的append操作。in_q.task_done(),通知对列已经完成任务。
以上,我们就完成了一个多线程爬虫。
一个线程时所消耗的时间:
在这里插入图片描述
多线程所消耗的时间:
在这里插入图片描述
可以看到提升效果还是很多的,人生苦短,有些事情能快则快,爬虫亦然。

4.生产者消费者爬虫

下面实现一个简易的生产者消费者爬虫:

# !usr/bin/env python 3.6
# -*- coding: utf-8 -*-
# Author: fcj
# Time: 2019-05-09
# Description: python 多线程-普通多线程-生产者消费者模型

import re
import time
import requests
import threading
from lxml import etree
from bs4 import BeautifulSoup
from queue import Queue
from threading import Thread


def producer(in_q):  # 生产者
    ready_list = []
    while in_q.full() is False:
        for i in range(1, 1001):
            url = 'http://www.g.com/?page='+str(i)
            if url not in ready_list:
                ready_list.append(url)
                in_q.put(url)
            else:
                continue


def consumer(in_q, out_q):  # 消费者
    headers = {
        'Accept': ‘',
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
        'Connection': 'keep-alive',
        'Cookie': ',
        'DNT': '1',
        'Host': 'www..com',
        'Referer': 'http://www.g.com',
        'Upgrade-Insecure-Requests': '1',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
                      '(KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'
    }
    while True:
        data = requests.get(url=in_q.get(), headers=headers)
        r = data.content
        content = str(r, encoding='utf-8', errors='ignore')
        soup = BeautifulSoup(content, 'html5lib')
        fixed_html = soup.prettify()
        html = etree.HTML(fixed_html)
        nums = html.xpath('//div[@class="col-md-1"]//text()')
        for num in nums:
            num = re.findall('[0-9]', ''.join(num))
            real_num = int(''.join(num))
            out_q.put(str(threading.current_thread().getName()) + '-' + str(real_num))
        in_q.task_done()  # 通知生产者,队列已消化完


if __name__ == '__main__':
    start = time.time()
    queue = Queue(maxsize=10)  # 设置队列最大空间为10
    result_queue = Queue()
    print('queue 开始大小 %d' % queue.qsize())

    producer_thread = Thread(target=producer, args=(queue,))
    producer_thread.daemon = True
    producer_thread.start()

    for index in range(10):
        consumer_thread = Thread(target=consumer, args=(queue, result_queue, ))
        consumer_thread.daemon = True
        consumer_thread.start()

    queue.join()
    end = time.time()
    print('总耗时:%s' % (end - start))
    print('queue 结束大小 %d' % queue.qsize())
    print('result_queue 结束大小 %d' % result_queue.qsize())


一个线程执行生产,生产者一次生产十个url,十个线程执行消费,代码很简单,读了上面的代码,这部分代码不难理解,我就不解释了。

5.多进程爬虫

python中多进程与多线程有太多相似的地方,例如它们方法大体相同,甚至可以说完全相同,为什么使用多进程呢?当你想提高cpu密集型任务的效率时,你便可以使用多进程来改善这种情况,这里,我使用的是进程池来编写多进程爬虫,如果你不想使用进程池的话,编写多进程任务与多线程类似,只不过,你需要从from multiprocessing import Process方法,使用手法与Thread方法类似,所以我在这里就不多谈了,关于多进程写法的一些东西,我已经写在代码的注释里,需要了解请查看。

# !usr/bin/env python 3.6
# -*- coding: utf-8 -*-
# Author: fcj
# Time: 2019-05-10
# Description: python 多线程-进程池

import re
import time
import requests
from lxml import etree
from bs4 import BeautifulSoup
import multiprocessing


def run(in_q, out_q):
    headers = {
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8'
                  ',application/signed-exchange;v=b3',
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
        'Connection': 'keep-alive',
        'Cookie': ‘',
        'DNT': '1',
        'Host': 'www.g.com',
        'Referer': 'http://www.g.com',
        'Upgrade-Insecure-Requests': '1',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
                      '(KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'
    }
    while in_q.empty() is not True:
        data = requests.get(url=in_q.get(), headers=headers)
        r = data.content
        content = str(r, encoding='utf-8', errors='ignore')
        soup = BeautifulSoup(content, 'html5lib')
        fixed_html = soup.prettify()
        html = etree.HTML(fixed_html)
        nums = html.xpath('//div[@class="col-md-1"]//text()')
        for num in nums:
            num = re.findall('[0-9]', ''.join(num))
            real_num = int(''.join(num))
            out_q.put(str(real_num))
        in_q.task_done()
        return out_q


if __name__ == '__main__':
    start = time.time()
    queue = multiprocessing.Manager().Queue()
    result_queue = multiprocessing.Manager().Queue()

    for i in range(1, 1001):
        queue.put('http://www.g.com2?page='+str(i))
    print('queue 开始大小 %d' % queue.qsize())

    pool = multiprocessing.Pool(10)  # 异步进程池(非阻塞)
    for index in range(1000):
        '''
        For循环中执行步骤:
        (1)循环遍历,将1000个子进程添加到进程池(相对父进程会阻塞)
        (2)每次执行10个子进程,等一个子进程执行完后,立马启动新的子进程。(相对父进程不阻塞)
         apply_async为异步进程池写法。异步指的是启动子进程的过程,与父进程本身的执行(爬虫操作)是异步的,
         而For循环中往进程池添加子进程的过程,与父进程本身的执行却是同步的。
        '''
        pool.apply_async(run, args=(queue, result_queue,))   # 维持执行的进程总数为10,当一个进程执行完后启动一个新进程.
    pool.close()
    pool.join()
    
    queue.join()  # 队列消费完 线程结束
    end = time.time()

    print('总耗时:%s' % (end - start))
    print('queue 结束大小 %d' % queue.qsize())
    print('result_queue 结束大小 %d' % result_queue.qsize())

多进程运行结果:
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

python爬虫之多线程、多进程爬虫 的相关文章

  • RabbitMQ 发展史与安装

    RabbitMQ 天降奇兵 解决的实例1 xff1a 统计用户的行为 xff0c 利用消息队列解耦模块和认证服务器 xff0c 认证模块被设计为 xff0c 在每一次请求页面的时候 xff0c 发送一条认证请求消息到rabbitmq xff
  • 小白学java------做一个歌手比赛系统(二)

    完整代码加实验报告都在 https download csdn net download qq 39980334 11232331 我已经设置成0积分下载了 xff0c 有需要的自行下载 xff0c 如果页面打不开可能还在审核中 xff08
  • 网络爬虫入门

    1 网络爬虫 网络爬虫 xff08 Web crawler xff09 xff0c 是一种按照一定的规则 xff0c 自动地抓取万维网信息的程序或者脚本 1 1 爬虫入门程序 1 1 1 环境准备 JDK1 8IntelliJ IDEAID
  • Java API入门篇

    文章目录 1 API1 1 API概述1 2 如何使用API帮助文档 2 String类2 1 String类概述2 2 String类的特点2 3 String类的构造方法2 4 创建字符串对象两种方式的区别2 5 字符串的比较2 5 1
  • Java异常篇

    文章目录 1 异常2 JVM默认处理异常的方式3 try catch方式处理异常4 Throwable成员方法5 编译时异常和运行时异常的区别6 throws方式处理异常7 throws和throw的区别8 自定义异常 1 异常 异常的概述
  • Java集合篇

    文章目录 1 Collection集合1 1 集合体系结构1 2 Collection集合概述和基本使用1 3 Collection集合的常用方法1 4 Collection集合的遍历1 5 集合使用步骤图解1 6 集合的案例 Collec
  • macOS下安装JDK11和配置环境变量

    1 下载 官网下载地址 华为云下载JDK11地址 tar包或者dmg xff0c 二者区别在于 xff1a tar你自己解压 xff0c 放在你想要的地方 xff08 配置JAVA HOME的时候是你自己选的位置 xff01 xff09 d
  • Java文件操作、IO流

    文章目录 1 File类1 1 File类概述和构造方法1 2 File类创建功能1 3 File类判断和获取功能1 4 File类删除功能 2 递归2 1 递归2 2 递归求阶乘2 3 递归遍历目录 3 IO流3 1 IO流概述和分类3
  • Java多线程

    文章目录 1 实现多线程1 1 进程和线程1 2 实现多线程方式一 xff1a 继承Thread类1 3 设置和获取线程名称1 4 线程优先级1 5 线程控制1 6 线程的生命周期1 7 实现多线程方式二 xff1a 实现Runnable接
  • Java编程思想(第4版)习题答案

    https span class token operator span span class token operator span span class token operator span greggordon span class
  • Bootstrap给表格设置宽度不起作用

    更改名称为table的class 将table layout属性设置为fixed span class token selector table span span class token punctuation span span cla
  • C++作用域运算符“::“的作用

    1 当存在具有相同名称的局部变量时 xff0c 要访问全局变量 include lt iostream gt using namespace std int x Global x int main int x 61 10 Local x c
  • HTML实现鼠标拖动元素排序

    1 简介 拖放 xff08 Drag和 drop xff09 是 HTML5 标准的组成部分 xff0c 为了使元素可拖动 xff0c 必须把 draggable 属性设置为 true xff0c 整个拖拽事件触发的顺序如下 xff1a d
  • MacOS安装svn客户端

    一 安装Homebrew环境 打开终端 输入以下命令 bin zsh c span class token string 34 span class token variable span class token variable span
  • MySql数据库root账户无法远程登陆解决办法

    最近换了新的腾讯云服务器后 xff0c 通过宝塔面板安装了mysql 数据库 xff0c 之后使用root用户通过navicat远程连接登录不了 解决办法如下 两行代码ok MySQL5 7和MySql8 开启root 用户远程访问 mys
  • MacOS修改Hosts文件

    1 苹果Mac电脑上 xff0c hosts文件的路径为 etc hosts xff0c 打开VI编辑 span class token function sudo span span class token function vim sp
  • ERD Online 4.0.0 免费私有部署方案

    文章目录 1 快速安装2 前置条件 3 一键安装 4 兼容性列表4 1 云主机兼容性列表4 2 虚拟机兼容性列表 1 快速安装 ERD Online的服务理念 xff1a 能喂到嘴里 xff0c 绝不递到手里 2 前置条件 一台至少配置为
  • mysql: error while loading shared libraries: libtinfo.so.5 解决办法

    Centos8中安装mysql8 xff0c 服务启动后 xff0c 连接服务时报错为以下错误信息 mysql error span class token keyword while span loading shared librari
  • trilead-ssh2连接不上centos服务器Caused by: java.io.IOException: Cannot negotiate, proposals do not match.

    导致此问题的原因是ssh升级后 xff0c 为了安全 xff0c 默认不在采用原来一些加密算法 xff0c 我们手工添加进去即可 1 步骤一 修改ssh的配置文件 etc ssh sshd config 搜索KexAlgorithms xf
  • macos安装nvm

    文章目录 1 概述2 安装注意事项3 安装nvm 1 概述 nvm 即 node version manager xff0c 好处是方便切换 node js 版本 2 安装注意事项 卸载从node官网下载pkg安装的nodesudo rm

随机推荐

  • Linux下生产者消费者问题的C语言实现

    注 xff1a 查看全文请关注作者 xff0c 或点击前往 xff1a 生产者 消费者问题的C语言实现 实验六 生产者 消费者问题实现 实验题目 要求 在Linux操作系统下用C实现经典同步问题 xff1a 生产者 消费者 xff0c 具体
  • ftpClient文件上传成功但总是返回false

    ftpClient storeFile newFileName is xff1b 文件上传成功但总是返回false flag span class token operator 61 span ftpClient span class to
  • “佛祖保佑“代码注释

    注释如下 oo0oo o8888888o 88 34 34 88 0 61 0 96 39 39 39
  • android带视频和图片的轮播(banner)解决方案

    方案只包含一个视频和多张图片 xff0c 如果又多个视频的 xff0c 可以修改适配器中的的播放器为一个list xff0c 并且在滑动中做相应的释放操作 一 xff1a 实现一个视频和多张图片的轮播banner 使用到第三方框架有 1 轮
  • Android灯光系统(硬件访问服务框架)

    Android灯光系统 硬件访问服务框架 Java类 xff1a LightsService java LightsService java通过调用 xff0c LightsService JNI来实现com android server包
  • Ubuntu16.04简易安装pycharm社区版

    1 首先到官网下载linux版本的pycharm xff0c 为了使用方便 xff0c 直接安装社区版 https www jetbrains com pycharm download section 61 linux xff08 因为虚拟
  • vue 严格格式问题

    用vue cli脚手架搭建开发环境 xff0c 会自动安装eslint严格格式 xff0c 如果代码格式不按照严格模式写 xff0c 会经常报警告 xff08 如缩进4个空格会警告 xff09 xff0c 如下图是一些警告类型 xff1a
  • vue watch深度监控一个对象下新增属性不生效问题

    先简单还原下项目中遇到的问题 xff1a adc为一个空对象 xff0c watch深度监听abc下的pageNum属性 xff08 此时还没有 xff09 data return abc watch 39 abc 39 deep true
  • python 归并排序

    归并排序 xff08 Merge Sort xff09 是一种典型的递归法排序 它把复杂的 排序过程分解成一个简单的合并子序列的过程 至于怎么得到这个子 序列 xff0c 就得自己调用自己了 归并排序首先要做的就是将数列分成左右两部分 xf
  • 面试之Spring的启动原理

    引入 为什么突然说一下Spring启动原理呢 xff0c 因为之前面试的时候 xff0c 回答的那可谓是坑坑洼洼 xff0c 前前后后 xff0c 补补贴贴 总而言之就是不行 xff0c 再次看一下源码发掘一下 在Spring Boot还没
  • Zabbix5.0钉钉告警实战

    1 服务器环境准备 xff08 Centos7 6 xff09 1 服务器1 xff1a zabbix服务端 服务器2 xff1a 客户端 2 客户端服务器上安装agent xff0c 并且安装httpd服务 xff0c 在zabbix网页
  • c++项目实战---->QT文件日志输出

    QT说明文档 xff08 输出详细日志 xff09 span class token macro property span class token directive keyword include span span class tok
  • linux下将Qt程序打包成deb文件,发布、安装及使用,ubuntu和银河麒麟下可用

    本次测试为在ubuntu打包程序为deb文件包 xff0c 然后再Ubuntu和银河麒麟下安装测试 xff0c 均可使用 1 Qt下Release模式编译工程文件 xff0c 文件名为wapp 2 创建如下基本目录结构 本目录结构在 目录下
  • Python制作简单的加法计分器

    运行结果如下图 代码如下 oo0oo o8888888o 88 34 34 88 0 61 0 96 39 39 39
  • Spring的学习之路(二)的Spring详细配置bean的基础配置

    1 xml的提示配置 1 1 Schema的配置 lt xml version 61 34 1 0 34 encoding 61 34 UTF 8 34 gt lt beans xmlns 61 34 http www springfram
  • Jetpack之ViewBinding

    ViewBinding 的作用 xff1a 代替findViewById xff0c 还可以保证空安全和类型安全 基本原理 会为每一个布局xml文件生成一个视图绑定类 xff0c 类名称是布局文件名转化为UpperCamelCase 43
  • angr原理与实践(二)—— 各类图的生成(CFG CG ACFG DDG等)

    本文系原创 xff0c 转载请说明出处 Please Subscribe Wechat Official Account xff1a 信安科研人 xff0c 获取更多的原创安全资讯 上一篇文章介绍了angr的原理 xff0c 自此篇文章开始
  • spring项目开发流程简述

    运用技术 xff1a spring spring MVC xff0c spring boot xff0c mybatis xff0c vue 一 设计数据库 根据需求和原型图 xff0c 画ER关系图 xff1b 然后创建数据表 xff0c
  • Cloudflare反爬

    Cloudflare反爬 Cloudflare反爬实例背景解决途径 盗文章死妈 建议看下文scrapy兼容cloudscraper因使用cloudescraper需升级openssl版本教程 Cloudflare反爬实例 很久没有写博文了
  • python爬虫之多线程、多进程爬虫

    一 原因 多线程对爬虫的效率提高是非凡的 xff0c 当我们使用python的多线程有几点是需要我们知道的 xff1a 1 Python的多线程并不如java的多线程 xff0c 其差异在于当python解释器开始执行任务时 xff0c 受