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爬虫之多线程、多进程爬虫 的相关文章

  • 【日记 2021-05-14】树莓派获取环境亮度(光照度)

    什么是亮度 xff1f 光照度指被照射物体单位面积上所接受可见光的光通量 xff0c 单位是勒克斯 Lux 或流明每平方米 lm m2 xff0c 是用来描述光的亮度的 我们平时最关心的我家灯到底够不够亮 xff0c 具体数值可以用照度计测
  • 【Java】语法糖

    什么是语法糖 xff1f 语法糖就是指 java 编译器把 java 源码编译为 class 字节码的过程中 xff0c 自动生成和转换的一些代码 有哪些语法糖 xff1f 默认构造函数 xff1a 当一个类没有显式的提供构造函数时 xff
  • 【Java多线程】FutureTask的使用示例

    炎热的夏天 xff0c 一位少侠到客栈吃杯茶 xff0c 客栈准备茶水有如下两种方式 xff1a 单线程 先擦桌子 xff0c 花费3秒 xff1b 再准备茶水 xff0c 花费3秒 xff1b 共花费6秒 xff01 多线程 擦桌子和准备
  • 旷视研究院博士图鉴|Be that challenger

    图为旷视研究院日常工作一景 旷视研究院有这样一批挑战者 他们是 PhD xff0c 更是 Researcher 他们 深入探索科技发展的前沿阵地 笃信自己的科研价值 渴望见证一行行代码的快速落地 期待与同样优秀的人碰撞思维 做自己所爱的事
  • 【Java多线程】CompletableFuture的使用示例

    刘备 关羽和张飞三兄弟在家吧喝酒 xff0c 突然发现忘带钱了 xff0c 于是差下人回去取钱 为了不影响三兄弟喝酒的气氛 xff0c 刘备吩咐下人钱取来后交给旁边侍候的赵云即可 span class token keyword publi
  • 【Java基础】Arrays.sort()使用示例

    狗有名字 体重和年龄3个属性 xff1a span class token keyword public span span class token keyword class span span class token class nam
  • 【Spring】aop的使用示例

    场景 去饭店吃饭的时候 xff0c 在进入饭店时门卫会为你开门 xff0c 并问候说 欢迎光临 xff0c 当你吃完离开时 xff0c 门卫会说 请慢走 xff0c 欢迎下次光临 此场景下涉及如下两个角色 xff1a 顾客 xff08 cu
  • 关于从Github上下载历史版本

    第一步 打开一个仓库 xff0c 可以看到此时在主分支下 xff0c 点击1位置查看历史版本 第二步 现在可以查看到所有的版本 xff08 提交 xff09 信息 xff0c 单击2位置进入该版本 第三步 单击3位置浏览并打开该版本 第四步
  • 数据结构——结构体

    结构体是一种复合数据类型 xff0c 定义了一组变量列表 xff0c 这些变量将放在一个内存块中的名称下 它允许通过使用指向结构的一个指针来访问不同的变量 struct structure name data type member1 da
  • python 归并排序

    归并排序 xff08 Merge Sort xff09 是一种典型的递归法排序 它把复杂的 排序过程分解成一个简单的合并子序列的过程 至于怎么得到这个子 序列 xff0c 就得自己调用自己了 归并排序首先要做的就是将数列分成左右两部分 xf
  • ROS学习笔记—— rospy

    所有资料均来自于 https www icourse163 org learn ISCAS 1002580008 learn announce 和 https github com DroidAITech ROS Academy for B
  • XCOM(串口监视器,无单片机)+ESP8266显示心知天气天气信息

    XCOM xff08 串口监视器 xff0c 无单片机 xff09 43 ESP8266显示心知天气天气信息 ESP8266 AT指令显示 这是第一次写博客 xff0c 写的内容尽量通俗易懂贴近生活 PS 写的不好务必不要打我 ESP826
  • Linux编程——交叉编译器基本指令介绍

    Linux编程 交叉编译器基本指令介绍 arm span class token operator span linux span class token operator span gnueabihf span class token o
  • 马尔可夫链蒙特卡洛采样(MCMC)

    首先我们要明确的是马尔可夫链蒙特卡洛采样以下简称MCMC xff0c 它首先是个采样方法 1 采样的目的 采样作为任务 xff0c 用于生成新的样本求和 求积分 比如我们知道样本z的后验分布 我们经常会有一个需求 xff0c 得到目标函数f
  • dlang语法的简单整理

    dlang整理 为什么使用dlang 优点 xff1a 快速 xff0c 开发高效的 xff0c 方便 xff0c 无虚拟机的 xff0c 快速的 xff0c 高性能的 垃圾回收 缺点 xff1a 语法较为复杂 xff0c 支持gc 曾经很
  • docker 搭建基于prometheus的监控体系

    Prometheus是一个时间序列数据库 但是 xff0c 它不仅仅是一个时间序列数据库 它涵盖了可以绑定的整个生态系统工具集及其功能 Prometheus主要用于对基础设施的监控 包括服务器 xff0c 数据库 xff0c VPS xff
  • React回退上个页面及跳转下个页面

    回退上个页面 React 不保存数据 span class token keyword this span span class token punctuation span props span class token punctuati
  • Linux上jar包运行,但是接口测试Connect超时

    工作过程中遇到的 xff0c 这个异常就是连接超时 引起连接超时的问题有很多 xff0c 因为是feign调用超时 xff0c 我第一时间没怀疑是不是我的程序无法访问 xff0c 我一直怀疑是feigin那部分出错了 xff0c 什么跨服务
  • 网络调试助手(pc端)+ESP8266指令

    一 所需软件 链接 xff1a https pan baidu com s 1ycyOSZJOsiIocY3umrG7 g 提取码 xff1a 38f2 链接 xff1a https pan baidu com s 1EUuXUKcvf A
  • AD、PADS、allegro 哪个好用?

    AD PADS allegro 哪个好用 xff1f 用哪个都没问题 xff0c 都能完成任务 xff0c 主要看公司的选择了 AD是元老级的软件了 xff0c 也是PCB设计最先出的软件 xff0c 使用最为广范 在很多操作上都非常的人性

随机推荐

  • 基于python+pyqt5的串口助手

    基于python 43 pyqt5的串口助手 环境 xff1a pycharm python3 8 xff0c pyqt5 xff0c pyserial xff08 需要该节的工程文件 请私信 xff0c 或加VX xff1a Crazzy
  • STM32F4四轴飞行器总结

    xff08 菜鸡一枚 xff0c 记录一些学习的体会 xff0c 并记录了学习时提出的问题 xff0c 便于自己再次查阅 xff0c 若有错误之处 xff0c 希望大佬们指正 xff0c 谢谢 xff09 四旋翼简介 xff1a 嵌入式芯片
  • 详解RTK,RTD,SBAS,WAAS,PPP,PPK,广域差分等技术之间的关系与区别

    RTK与RTD的区别 xff0c 一个是载波相位差分 一个是码差分 xff0c 并且RTK的定位精度要高一些 RTK与PPK的区别 xff0c 一个是实时提供数据信息 xff0c 一个是事后处理 WAAS是SBAS系统一个具体的实例 xff
  • c实现set集合

    集合有点编程语言会带有 xff0c 有的没有 但是我想redis的集合set你一定听说过或者用过 下面咱们用链表来实现set 相信有了前面的基础我们可以很容易的实现set集合 需要引入我的链表的list c和list h 头文件 span
  • 实时单目物体SLAM Real-time Monocular Object SLAM

    2015 摘要 xff1a 我们提出了一个基于对象的实时SLAM系统 xff0c 该系统利用了迄今为止最大的对象数据库 我们的方法包括两个主要部分 xff1a 1 xff09 利用对象刚性约束改进地图并找到其真实比例的单目SLAM算法 xf
  • linux下多线程服务器编程

    前言 xff1a 整个程序的架构就是一开始创建一个线程 xff0c 在这个线程里面做下面几个操作 xff1a 1 调用socket xff0c 创建监听客户端的socket 2 设置socket属性 xff0c 端口可以重用 3 调用bin
  • MavLink简单使用

    参考连接 xff1a https mavlink io zh 整体说明 本文不探究mavlink协议的具体内容 xff0c 只说明如何简单使用 环境 xff1a win10 c c 43 43 MAVLink仓库clone 我们其实clon
  • gazebo支持的载具类型

    类型make指令四旋翼make px4 sitl gazebo具有光流的四旋翼make px4 sitl gazebo iris opt flow3DR Solo xff08 四旋翼 xff09 make px4 sitl gazebo s
  • YoLov3目标检测代码C++版本运行

    论文地址 xff1a YOLO YOLOv2 YOLO9000 YOLOv3 YOLOv4 YOLO系列权重 配置文件下载地址 xff1a https github com AlexeyAB darknet 代码解读 xff1a Deep
  • C++ ::

    表示作用域 xff0c 和所属关系 是运算符中等级最高的 xff0c 它分为三种 1 global scope 全局作用域符 xff09 xff0c 用法 xff08 name 2 class scope 类作用域符 xff09 xff0c
  • 下载Gazebo模型

    下载Gazebo模型 这时运行下面Gazebo命令 xff0c OK xff0c 错误没有了 xff0c 但怎么是一抹黑啊 xff01 roscore amp rosrun gazebo ros gazebo 首次运行Gazebo xff0
  • Linux 项目实战记录

    1 阻塞 非阻塞 同步 异步 网络IO 典型的一次IO的两个阶段是什么 xff1f 数据就绪和数据读写 数据就绪 xff1a 根据IO操作的就绪状态 阻塞 xff1a 1 调用IO方法的线程进入阻塞状态 xff0c xff08 函数进入wa
  • Mission Planner日志分析

    将找到的不同平台的Mission planner日志分析资源汇总起来以便以后查看 xff1a Pixhawk无人机教程 8 1 在Mission Planner中下载与分析日志 APM 自动拍摄后期POS数据 APM和PIX飞控日志分析入门
  • 关于iai_kinect2中标定数据的个人理解

    iai kinect2 kinect2 bridge data 196605135147 iai kinect2 kinect2 bridge data 299150235147
  • mac地址的作用

    最近读一本关于linux编程的书籍 xff0c 看到一部分很迷茫 xff0c 忽然不知道mac地址的作用 xff0c 既然已经有了ip地址了要mac地址何用呢 xff1f MAC地址是数据链路层的地址 xff0c 如果mac地址不可直达 直
  • putty远程连接服务器,显示超时,网络么有问题

    今天用putty连接阿里云服务器时 xff0c 用ip连接一直显示超时 换了端口号也没用 xff0c 最后 xff0c 重启了一下服务就好了 xff0c 记录一下以免以后碰到
  • CUDA11.x VS2017编译报错MSB3721

    问题原因 xff1a 11 XCUDA版本太高 xff0c 用10版本的就行了 博主电脑是联想拯救者3070显卡 xff0c 于是就按照网上的教程下载了适合我NVIDIA版本11 4的CUDA 按照网上配置弄完之后 xff0c VS2017
  • Ubuntu 使用VNCserver远程连接灰屏问题的解决方案

    问题 xff1a Ubuntu需要使用VNCserver服务提供远程桌面 xff0c 使用TightVNC Viewer远程连接主机 xff0c 发现只能出现三个正常显示的桌面 xff0c 继续开更多的桌面 xff0c 就会发现新开的窗口都
  • opencv-python(六):颜色空间及转换

    0 颜色模式 RGB 模式 百万种颜色 CMYK 模式 四种印刷色 索引模式 256 种颜色 xff09 灰度模式 256 级灰度 xff09 位图模式 两种颜色 xff09 0 1 灰度模式 也就是灰度图 黑白照片 xff0c 每个像素只
  • python爬虫之多线程、多进程爬虫

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