我们在写python爬虫的过程中,对于大量数据的抓取总是希望能获得更高的速度和效率,但由于网络请求的延迟、IO的限制,单线程的运行总是不能让人满意。因此有了多线程、异步协程等技术。

下面介绍一下python中的多线程及线程池技术,并通过一个具体的爬虫案例实现具体运用。


多线程

先来分析单线程。写两个测试函数

  1. def func1():
  2. for i in range(500000):
  3. print("func1", i)
  4. def func2():
  5. for i in range(500000):
  6. print("func2", i)

在主函数中调用

  1. if __name__ == "__main__":
  2. func1()
  3. func2()

当程序执行时,按照主程序中的执行顺序,func1全部运行完毕后才会运行func2,这就是单线程的效果。

接下来测试多线程。

先导包

  1. from threading import Thread

改造主函数

  1. thread1 = Thread(target=func1)
  2. thread1.start()
  3. thread2 = Thread(target=func2)
  4. thread2.start()
  5. thread1.join()
  6. thread2.join()

这里的thread.join()是阻塞进程,因为这里主函数中没有

执行效果如下:

可以看到func1func2函数分为两个不同的线程同时工作、互不干扰。


线程池

以此类推,如果同时开着20个这样的线程,是否可以同时执行呢?但手动分配这么多线程显然是不可能的,因此引入线程池这一概念,一次开辟一些进程,我们用户直接给线程池提交任务,线程任务的调度交给线程池来完成。这样一来,就能十分方便的分配线程的任务了。

首先导包

  1. from concurrent.futures import ThreadPoolExecutor

改造一下子函数

  1. def func(url):
  2. for i in range(1000):
  3. print(url)

主函数

  1. if __name__ == "__main__":
  2. # 创建线程池
  3. with ThreadPoolExecutor(50) as t:
  4. for i in range(100):
  5. t.submit(func, url=f"线程{i}")
  6. print("over")

我们建立一个线程池,分配50个线程,提交100个任务,让他们去自由分配。现有的50个线程先去拿到了1-50这些任务,当谁先完成就去拿到51个任务,以此类推。相当于50个工人一起干活,互不干涉,显然效率较单人更高一些。

再来看运行结果


线程锁

了解了线程池的基本概念之后就可以去改造我们的爬虫了。但是在此之前该需要了解一个线程锁的概念。先看下面这个例子

  1. from threading import Thread
  2. num = 0
  3. def add():
  4. global num
  5. for i in range(100000):
  6. num += 1
  7. def minus():
  8. global num
  9. for i in range(100000):
  10. num -= 1
  11. if __name__=="__main__":
  12. thread1 = Thread(target=add)
  13. thread2 = Thread(target=minus)
  14. thread1.start()
  15. thread2.start()
  16. thread1.join()
  17. thread2.join()
  18. print(num)

开辟两个线程,一个做自增一个做自减,他们两个同时运行,按常理num最终的值应为0,但实际运行结果是不稳定的。

由于每个线程运行速度极快,因此在他们的临界点都想对全局变量num操作时会出现竞争状态,有可能出现数值丢失、自增失败的情况,因此需要加入线程锁来控制每次只允许有一个线程对全局变量num进行操作。

  1. import threading
  2. lock = threading.Lock()
  1. lock.acquire()
  2. num += 1
  3. lock.release()

在线程中的关键操作加上线程锁,再跑起来就不会出现竞争状态了。


爬虫实战

要在爬虫中运用到线程池,基本的思路很简单,

1.如何抓取到单个页面的数据

2.上线程池批量抓取

目标:https://www.dydytt.net/html/gndy/dyzz/list_23_1.html

这里仅做线程池的基本实验,具体案例移步这里

先随便写个爬虫拿到第一页的所有电影标题数据

  1. import requests
  2. from lxml import etree
  3. filmNameList = []
  4. def download(url):
  5. global filmNameList
  6. resp = requests.get(url)
  7. resp.encoding="gb2312"
  8. html = etree.HTML(resp.text)
  9. filmName = html.xpath('//table[@class="tbspan"]/tr[2]/td[2]/b/a/text()')
  10. for each in filmName:
  11. filmNameList.append(each)
  12. pass
  13. if __name__=="__main__":
  14. url = "https://www.dydytt.net/html/gndy/dyzz/list_23_1.html"
  15. download(url)
  16. for i in filmNameList:
  17. print(i)

非常轻松的拿到了第一页的数据


接下来上线程池

  1. import requests
  2. import threading
  3. from concurrent.futures import ThreadPoolExecutor
  4. from lxml import etree
  5. filmNameList = []
  6. lock = threading.Lock()
  7. def download(url):
  8. global filmNameList
  9. resp = requests.get(url)
  10. resp.encoding="gb2312"
  11. html = etree.HTML(resp.text)
  12. filmName = html.xpath('//table[@class="tbspan"]/tr[2]/td[2]/b/a/text()')
  13. for each in filmName:
  14. lock.acquire()
  15. filmNameList.append(each)
  16. lock.release()
  17. resp.close()
  18. if __name__=="__main__":
  19. with ThreadPoolExecutor(5) as t:
  20. for i in range(1, 11):
  21. url = f"https://www.dydytt.net/html/gndy/dyzz/list_23_{i}.html"
  22. t.submit(download, url=url)
  23. for i in filmNameList:
  24. print(i)
  25. print(f"total_len is {len(filmNameList)}")

我们给线程池分配了5个线程,抓了前10页共250条数据。

****

Python多线程、线程池及实际运用的更多相关文章

  1. C#多线程--线程池(ThreadPool)

    先引入一下线程池的概念: 百度百科:线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的堆栈大小,以默认的优先级运行, ...

  2. linux C 多线程/线程池编程 同步实例

    在多线程.线程池编程中经常会遇到同步的问题. 1.创建线程 函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, ...

  3. Python的线程池实现

    # -*- coding: utf-8 -*- #Python的线程池实现 import Queue import threading import sys import time import ur ...

  4. 【Python】多线程-线程池使用

    1.学习目标 线程池使用 2.编程思路 2.1 代码原理 线程池是预先创建线程的一种技术.线程池在还没有任务到来之前,创建一定数量的线程,放入空闲队列中.这些线程都是处于睡眠状态,即均为启动,不消耗 ...

  5. Python之路【第八篇】python实现线程池

    线程池概念 什么是线程池?诸如web服务器.数据库服务器.文件服务器和邮件服务器等许多服务器应用都面向处理来自某些远程来源的大量短小的任务.构建服务器应用程序的一个过于简单的模型是:每当一个请求到达就 ...

  6. python自定义线程池

    关于python的多线程,由与GIL的存在被广大群主所诟病,说python的多线程不是真正的多线程.但多线程处理IO密集的任务效率还是可以杠杠的. 我实现的这个线程池其实是根据银角的思路来实现的. 主 ...

  7. [python] ThreadPoolExecutor线程池 python 线程池

    初识 Python中已经有了threading模块,为什么还需要线程池呢,线程池又是什么东西呢?在介绍线程同步的信号量机制的时候,举得例子是爬虫的例子,需要控制同时爬取的线程数,例子中创建了20个线程 ...

  8. 《Python》线程池、携程

    一.线程池(concurrent.futures模块) #1 介绍 concurrent.futures模块提供了高度封装的异步调用接口 ThreadPoolExecutor:线程池,提供异步调用 P ...

  9. [python] ThreadPoolExecutor线程池

    初识 Python中已经有了threading模块,为什么还需要线程池呢,线程池又是什么东西呢?在介绍线程同步的信号量机制的时候,举得例子是爬虫的例子,需要控制同时爬取的线程数,例子中创建了20个线程 ...

  10. java多线程--线程池的使用

    程序启动一个新线程的成本是很高的,因为涉及到要和操作系统进行交互,而使用线程池可以很好的提高性能,尤其是程序中当需要创建大量生存期很短的线程时,应该优先考虑使用线程池. 线程池的每一个线程执行完毕后, ...

随机推荐

  1. [BUUCTF]REVERSE——简单注册器

    简单注册器 附件 步骤: apk文件,直接用apkide打开 去找反编译后的文件,反编译后的语言并没有看大懂,网上百度后找到了一个反编的神器jeb,下载地址 用它反编译后按tab,就能看懂代码了,搜索 ...

  2. 通过idea创建Maven项目整合Spring+spring mvc+mybatis

    创建项目 File→new→project             然后就不断next直到项目面板出来 设置文件夹         注意:这里我个人习惯,在java下还建了ssm文件夹,然后再cont ...

  3. 【RTOS】FreeRTOS中的任务堆栈溢出检测机制

    目录 前言 任务堆栈 堆栈溢出 任务堆栈溢出检测机制 API 两种堆栈溢出检测方式 堆栈溢出钩子函数 内核何时检测任务堆栈溢出 任务堆栈溢出检测存在的局限性 前言 注意:本笔记发布时可能忘记补充查看d ...

  4. Centos7查看防火墙对应的开放端口以及进行端口操作

    1.查看开放端口列表 [root@host bin]# firewall-cmd --list-ports 22/tcp 80/tcp 8888/tcp 39000-40000/tcp 12888/t ...

  5. JAVA整合kaptcha生成验证码 (字母验证码和算术验证码)

    引入maven <!--图片验证码--> <dependency> <groupId>com.github.penggle</groupId> < ...

  6. 自定义C语言CVector

    CVector.h // // cvector.h // GKApp // // Created by 王明辉 on 16/4/15. // Copyright (c) 2016年 GK. All r ...

  7. clang编译代码报错:`_start': (.text+0x24): undefined reference to `main'

    1. 说明 使用clang++10.1编译报错: /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crt1 ...

  8. 【LeetCode】1167. Minimum Cost to Connect Sticks 解题报告 (C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 小根堆 日期 题目地址:https://leetcod ...

  9. 【LeetCode】1047. Remove All Adjacent Duplicates In String 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 栈 日期 题目地址:https://leetcode ...

  10. 【LeetCode】318. Maximum Product of Word Lengths 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 set 位运算 日期 题目地址:https://le ...