交通 | python网络爬虫:“多线程并行 + 多线程异步协程

推文作者:Amiee

编者按:

常规爬虫都是爬完一个网页接着爬下一个网页,不适应数据量大的网页,本文介绍了多线程处理同时爬取多个网页的内容,提升爬虫效率。

1.引言​


一般而言,常规爬虫都是爬完一个网页接着爬下一个网页。如果当爬取的数据量非常庞大时,爬虫程序的时间开销往往很大,这个时候可以通过多线程或者多进程处理即可完成多个网页内容同时爬取的效果,数据获取速度大大提升。​


2.基础知识​


简单来说,CPU是进程的父级单位,一个CPU可以控制多个进程;进程是线程的父级单位,一个进程可以控制多个线程,那么到底什么是进程,什么是线程呢?​


对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程;打开一个QQ就启动一个QQ进程;打开一个Word就启动了一个Word进程,打开两个Word就启动两个Word进程。​


那什么叫作线程呢?在一个进程内部往往不止同时干一件事,比如浏览器,它可以同时浏览网页、听音乐、看视频、下载文件等。在一个进程内部这同时运行的多个“子任务”,便称之称为线程(Thread),线程是程序工作的最小单元。​


此外有个注意点,对于单个CPU而言,某一个时点只能执行一个任务,那么如果这样是怎么在现实中同时执行多个任务(进程)的呢?比如一边用浏览器听歌,一边用QQ和好友聊天是如何实现的呢?​


答案是操作系统会通过调度算法,轮流让各个任务(进程)交替执行。以时间片轮转算法为例:有5个正在运行的程序(即5个进程) : QQ、微信、谷歌浏览器、网易云音乐、腾讯会议,操作系统会让CPU轮流来调度运行这些进程,一个进程每次运行0.1ms,因为CPU执行的速度非常快,这样看起来就像多个进程同时在运行。同理,对于多个线程,例如通过谷歌浏览器(进程)可以同时访问网页(线程1)、听在线音乐(线程2)和下载网络文件(线程3)等操作,也是通过类似的时间片轮转算法使得各个子任务(线程)近似同时执行。​


2.1 Thread()版本案例

from threading import Thread
def func():for i in range(10):print('func', i)
if name == '__main__':t = Thread(target=func) # 创建线程t.start() # 多线程状态,可以开始工作了,具体时间有CPU决定for i in range(10):print('main', i)
执行结果如下:func 0func 1func 2func 3func 4main 0main 1main 2main 3main 4mainfunc 5 5func main 66func 7mainfunc 8func 97main 8main 9

2.2 MyTread() 版本案例​


大佬是这个写法

from threading import Thread​
class MyThread(Thread):​def run(self):​for i in range(10):​print('MyThread', i)​
if name == '__main__':​t = MyThread()​# t.run() # 调用run就是单线程​t.start() # 开启线程​for i in range(10):​print('main', i)​
执行结果:​
MyThread 0​
MyThread 1​
MyThread 2​
MyThread 3​
MyThread 4​
MyThread 5main 0​
main 1​
main 2​
main 3​
main 4​
MyThread ​
main 5​
6​
main MyThread 67​
mainMyThread  78​
mainMyThread  89​
main 9

2.3 带参数的多线程版本

from threading import Thread
def func(name):for i in range(10):print(name, i)
if name == '__main__':t1 = Thread(target=func, args=('子线程1',)) # 创建线程t1.start() # 多线程状态,可以开始工作了,具体时间又CPU决定t2 = Thread(target=func, args=('子线程2',))  # 创建线程t2.start()  # 多线程状态,可以开始工作了,具体时间又CPU决定for i in range(10):print('main', i)

2.4 多进程

一般不建议使用,因为开进程比较费资源

from multiprocessing import Process
def func():for i in range(1000000):print('func', i)
if name == '__main__':p = Process(target=func)p.start() # 开启线程for i in range(100000):print('mainn process', i)

3. 线程池和进程池

线程池:一次性开辟一些线程,我们用户直接给线程池提交任务,线程任务的调度由线程池来完成

from concurrent.futures import ThreadPoolExecutor
def func(name):for i in range(10):print(name, i)
if name == '__main__':# 创建线程池with ThreadPoolExecutor(50) as t:for i in range(100):t.submit(func, name=f'Thread{i}=')# 等待线程池中的人物全部执行完成,才继续执行;也称守护进程
print('执行守护线程')

进程池

from concurrent.futures import ProcessPoolExecutor
def func(name):for i in range(10):print(name, i)
if name == '__main__':# 创建线程池with ProcessPoolExecutor(50) as t:for i in range(100):t.submit(func, name=f'Thread{i}=')# 等待线程池中的人物全部执行完成,才继续执行;也称守护进程print('执行守护进程')

4. 爬虫实战-爬取新发地菜价

单个线程怎么办;上线程池,多个页面同时爬取

import requests
from lxml import etree
import csv
from concurrent.futures import ThreadPoolExecutorf = open('xifadi.csv', mode='w', newline='')
csv_writer = csv.writer(f)
def download_one_page(url):resp = requests.get(url)resp.encoding = 'utf-8'html = etree.HTML(resp.text)table = html.xpath(r'/html/body/div[2]/div[4]/div[1]/table')[0]# trs = table.xpath(r'./tr')[1:] # 跳过表头trs = table.xpath(r'./tr[position()>1]')for tr in trs:td = tr.xpath('./td/text()')# 处理数据中的 \\ 或 /txt = (item.replace('\\','').replace('/','') for item in td)csv_writer.writerow(txt)resp.close()print(url, '提取完毕')if name == '__main__':with ThreadPoolExecutor(50) as t:for i in range(1, 200):t.submit(download_one_page,f'http://www.xinfadi.com.cn/marketanalysis/0/list/{i}.shtml')print('全部下载完毕')

5. python网络爬虫:多线程异步协程与实验案例​


5.1 基础理论​


程序处于阻塞状态的情形包含以下几个方面:​
•input():等待用户输入​
•requests.get():网络请求返回数据之前​
•当程序处理IO操作时,线程都处于阻塞状态​
•time.sleep():处于阻塞状态​

协程的逻辑是当程序遇见IO操作时,可以选择性的切换到其他任务上;协程在微观上任务的切换,切换条件一般就是IO操作;在宏观上,我们看到的是多个任务都是一起执行的;上方的一切都是在在单线程的条件下,充分的利用单线程的资源。​

要点梳理:​
•函数被asyn修饰,函数被调用时,它不会被立即执行;该函数被调用后会返回一个协程对象。​
•创建一个协程对象:构建一个asyn修饰的函数,然后调用该函数返回的就是一个协程对象​
•任务对象是一个高级的协程对象,

import  asyncio​
import time​
async def func1():​print('你好呀11')​
if name == '__main__':​g1 = func1() # 此时的函数是异步协程函数,此时函数执行得到的是一个协程对象​asyncio.run(g1) # 协程城西执行需要asyncio模块的支持

5.2 同步/异步睡眠​

普通的time.sleep()是同步操作,会导致异步操作中断

import  asyncio
import time
async def func1():print('你好呀11')time.sleep(3) # 当程序中除了同步操作时,异步就中端了print('你好呀12')
async def func2():print('你好呀21')time.sleep(2)print('你好呀22')
async def func3():print('你好呀31')time.sleep(4)print('你好呀32')
if name == '__main__':g1 = func1() # 此时的函数是异步协程函数,此时函数执行得到的是一个协程对象g2 = func2()g3 = func3()tasks = [g1, g2, g3]t1 = time.time()asyncio.run(asyncio.wait(tasks)) # 协程城西执行需要asyncio模块的支持t2 = time.time()
print(t2 - t1)你好呀21你好呀22你好呀11你好呀12你好呀31你好呀329.003259658813477

使用异步睡眠函数,遇到睡眠时,挂起;

import  asyncio
import time
async def func1():print('你好呀11')await asyncio.sleep(3) # 异步模块的sleepprint('你好呀12')
async def func2():print('你好呀21'))await asyncio.sleep(4)  # 异步模块的sleepprint('你好呀22')
async def func3():print('你好呀31')await asyncio.sleep(4)  # 异步模块的sleepprint('你好呀32')
if name == '__main__':g1 = func1() # 此时的函数是异步协程函数,此时函数执行得到的是一个协程对象g2 = func2()g3 = func3()tasks = [g1, g2, g3]t1 = time.time()asyncio.run(asyncio.wait(tasks)) # 协程城西执行需要asyncio模块的支持t2 = time.time()
print(t2 - t1)你好呀21你好呀11你好呀31你好呀12你好呀22你好呀324.0028839111328125

整体耗时为最长时间 + 切换时间

5.3 官方推荐多线程异步协程写法

import  asyncio
import time
async def func1():print('你好呀11')# time.sleep(3) # 当程序中除了同步操作时,异步就中端了await asyncio.sleep(3) # 异步模块的sleepprint('你好呀12')
async def func2():print('你好呀21')# time.sleep(2)await asyncio.sleep(4)  # 异步模块的sleepprint('你好呀22')
async def func3():print('你好呀31')# time.sleep(4)await asyncio.sleep(4)  # 异步模块的sleepprint('你好呀32')
async def main():# 写法1:不推荐# f1 = func1()# await f1 # await挂起操作,一般放在协程对象前边# 写法2:推荐,但是在3.8废止,3.11会被移除# tasks = [func1(), func2(), func3()]# await asyncio.wait(tasks)# 写法3:python3.8以后使用tasks = [asyncio.create_task(func1()), asyncio.create_task(func2()),asyncio.create_task(func3())]await asyncio.wait(tasks))if name == '__main__':t1 = time.time()asyncio.run(main())t2 = time.time()
print(t2 - t1)你好呀21你好呀31你好呀11你好呀12你好呀32你好呀224.001523017883301

6. 异步协程爬虫实战

安装包:

pip install aiohttp

pip install aiofiles

基本框架:​
•获取所有的url​
•编写每个url的爬取函数​
•每个url建立一个线程任务,爬取数据

6.1 爬取图片实战代码

import asyncio
import aiohttp
import aiofiles
headers = {'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Mobile Safari/537.36 Edg/91.0.864.41'}
urls = {r'http://kr.shanghai-jiuxin.com/file/mm/20210503/xy2edb1kuds.jpg',r'http://kr.shanghai-jiuxin.com/file/mm/20210503/g4ok0hh2utm.jpg',r'http://kr.shanghai-jiuxin.com/file/mm/20210503/sqla2defug0.jpg',r'http://d.zdqx.com/aaneiyi_20190927/001.jpg'}
async def aio_download(url):async with aiohttp.ClientSession() as session:async with session.get(url, headers=headers) as resp:async with aiofiles.open('img/' + url.split('/')[-1],mode='wb') as f:await f.write(await resp.content.read())await f.close() # 异步代码需要关闭文件,否则会输出0字节的空文件# with open(url.split('/')[-1],mode='wb') as f: # 使用with代码不用关闭文件#      f.write(await resp.content.read()) # 等价于resp.content, resp.json(), resp.text()
async def main():tasks = []for url in urls:tasks.append(asyncio.create_task(aio_download(url)))await asyncio.wait(tasks)if name == '__main__':# asyncio.run(main()) # 可能会报错 Event loop is closed 使用下面的代码可以避免asyncio.get_event_loop().run_until_complete(main())
print('over')

知识点总结:​
•在python 3.8以后,建议使用asyncio.create_task()创建人物​
•aiofiles写文件,需要关闭文件,否则会生成0字节空文件​
•aiohttp中生成图片、视频等文件时,使用resp.content.read(),而requests库时,并不需要read()​
•报错 Event loop is closed时, 将 asyncio.run(main()) 更改为 asyncio.get_event_loop().run_until_complete(main())​
•使用with打开文件时,不用手动关闭

6.2 爬取百度小说

注意:以下代码可能会因为 百度阅读 页面改版而无法使用

主题思想:

  • 分析那些请求需要异步,那些不需要异步;在这个案例中,获取目录只需要请求一次,所以不需要异步

  • 下载每个章节的内容,则需要使用异步操作

import requests
import asyncio
import aiohttp
import json
import aiofiles

https://dushu.baidu.com/pc/detail?gid=4306063500

http://dushu.baidu.com/api/pc/getCatalog?data={"book_id":"4306063500"} 获取章节的名称,cid;只请求1次,不需要异步

http://dushu.baidu.com/api/pc/getChapterContent # 涉及多个任务分发,需要异步请求,拿到所有的文章内容

headers = {'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Mobile Safari/537.36 Edg/91.0.864.41'}
async def aio_download(cid, book_id,title):data = {'book_id': book_id,'cid' : f'{book_id}|{cid}','need_bookinfo': 1}data = json.dump(data)url = f'http://dushu.baidu.com/api/pc/getChapterContent?data={data}'async with aiohttp.ClientSession as session:async with session.get(url) as resp:dic = await resp.json()async with aiofiles.open('img/'+title+'.txt',mode='w') as f:await f.write(dic['data']['novel']['content'])await f.close()
async def getCatalog(url):resp = requests.get(url, headers=headers)dic = resp.json()tasks = []for item in dic['data']['novel']['items']:title = item['title']cid = item['cid']tasks.append(asyncio.create_task(aio_download((cid, book_id,title))))await asyncio.wait(tasks)if name == '__main__':book_id = '4306063500'url = r'http://dushu.baidu.com/api/pc/getCatalog?data={"book_id":"'+book_id+'"}'asyncio.run(getCatalog(url))

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/104024.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录算法训练营第五十二天 | 123.买卖股票的最佳时机III、188.买卖股票的最佳时机IV

123.买卖股票的最佳时机III 视频讲解:动态规划,股票至多买卖两次,怎么求? | LeetCode:123.买卖股票最佳时机III_哔哩哔哩_bilibili 代码随想录 (1)代码 188.买卖股票的最佳时机IV 视频讲解&a…

文字雨特效

效果展示 CSS 知识点 简易实现云朵技巧text-shadow 属性的灵活运用filter 属性实现元素自动变色 实现页面布局 <div class"container"><div class"cloud"><h2>Data Clouds Rain</h2></div> </div>实现云朵 实现云…

从基础到卷积神经网络(第12天)

1. PyTorch 神经网络基础 1.1 模型构造 1. 块和层 首先&#xff0c;回顾一下多层感知机 import torch from torch import nn from torch.nn import functional as Fnet nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))X torch.rand(2, 20) # 生成随机…

<图像处理> Fast角点检测

Fast角点检测 基本原理是使用圆周长为N个像素的圆来判定其圆心像素P是否为角点&#xff0c;如下图所示为圆周长为16个像素的圆&#xff08;半径为3&#xff09;&#xff1b;OpenCV还提供圆周长为12和8个像素的圆来检测角点。 相对中心像素的位置信息 //圆周长为16 static c…

Lumen/Laravel - 事件机制原理与工作流程 - 探究

1.应用场景 主要用于学习与探究Lumen/Laravel的事件机制原理与工作流程。 2.学习/操作 1.文档阅读 chatgpt & 其他资料 2.整理输出 2.1 是什么 TBD 2.2 为什么需要「应用场景」 TBD 2.3 什么时候出现「历史发展」 TBD 2.4 怎么实践 TBD 截图 后续补充 ... 3.问题…

深度学习基础知识 Dataset 与 DataLoade的用法解析

深度学习基础知识 Dataset 与 DataLoade的用法解析 1、Dataset2、DataLoader参数设置&#xff1a;1、pin_memory2、num_workers3、collate_fn分类任务目标检测任务 1、Dataset 代码&#xff1a; import torch from torch.utils import dataclass MyDataset(torch.utils.data.D…

语言模型编码中/英文句子格式详解

文章目录 前言一、Bert的vocab.txt内容查看二、BERT模型转换方法(vocab.txt)三、vocab内容与模型转换对比四、中文编码总结 前言 最近一直在学习多模态大模型相关内容&#xff0c;特别是图像CV与语言LLM模型融合方法&#xff0c;如llama-1.5、blip、meta-transformer、glm等大…

Elasticsearch 和 Arduino:一起变得更好!

作者&#xff1a;Enrico Zimuel 使用 Arduino IoT 设备与 Elasticsearch 和 Elastic Cloud 进行通信的简单方法 在 Elastic&#xff0c;我们不断寻找简化搜索体验的新方法&#xff0c;并开始关注物联网世界。 来自物联网的数据收集可能非常具有挑战性&#xff0c;尤其是当我们…

《Unity Shader入门精要》笔记06

基础纹理 单张纹理纹理的属性Alpha SourceWrap ModeFilter Mode 凹凸映射高度纹理法线纹理实践在切线空间下计算在世界空间下计算 Unity中的法线纹理类型Create from Grayscale 渐变纹理遮罩纹理其他遮罩处理 单张纹理 我们通常会使用一张纹理来代替物体的漫反射颜色 Shader …

K8s Kubernetes Namespave Pod Label Deployment Service 实战

本章节将介绍如何在kubernetes集群中部署一个nginx服务&#xff0c;并且能够对其进行访问。 Namespace Namespace是kubernetes系统中的一种非常重要资源&#xff0c;它的主要作用是用来实现多套环境的资源隔离或者多租户的资源隔离。 默认情况下&#xff0c;kubernetes集群中…

MySQL之双主双从读写分离

一个主机 Master1 用于处理所有写请求&#xff0c;它的从机 Slave1 和另一台主机 Master2 还有它的从 机 Slave2 负责所有读请求。当 Master1 主机宕机后&#xff0c; Master2 主机负责写请求&#xff0c; Master1 、 Master2 互为备机。架构图如下 : 准备 我们…

升级教育技术软件的多合一解决方案

当今时代技术和教育联系越来越紧密&#xff0c;教育机构对强大、安全、灵活的 IT 解决方案的探索至关重要。 全球事件、技术进步以及学生和教职员工不断变化的需求影响着不断变化的教育格局&#xff0c;我们要采取变革性的方法来确保教育的连续性和质量提升。 Splashtop Ente…

力扣刷题 day43:10-13

1.完全平方数 给你一个整数 n &#xff0c;返回 和为 n 的完全平方数的最少数量 。 完全平方数 是一个整数&#xff0c;其值等于另一个整数的平方&#xff1b;换句话说&#xff0c;其值等于一个整数自乘的积。例如&#xff0c;1、4、9 和 16 都是完全平方数&#xff0c;而 3 …

20基于MATLAB的车牌识别算法,在环境较差的情景下,夜间识别度很差的车牌号码可以精确识别出具体结果,程序已调通,可直接替换自己的数据跑。

基于MATLAB的车牌识别算法&#xff0c;在环境较差的情景下&#xff0c;夜间识别度很差的车牌号码可以精确识别出具体结果&#xff0c;程序已调通&#xff0c;可直接替换自己的数据跑。 20matlab车牌识别 (xiaohongshu.com)

【融合ChatGPT等AI模型】Python-GEE遥感云大数据分析、管理与可视化及多领域案例实践应用

目录 第一章 理论基础 第二章 开发环境搭建 第三章 遥感大数据处理基础与ChatGPT等AI模型交互 第四章 典型案例操作实践 第五章 输入输出及数据资产高效管理 第六章 云端数据论文出版级可视化 更多应用 随着航空、航天、近地空间等多个遥感平台的不断发展&#xff0c;近…

如何在 PyTorch 中冻结模型权重以进行迁移学习:分步教程

一、说明 迁移学习是一种机器学习技术&#xff0c;其中预先训练的模型适用于新的但类似的问题。迁移学习的关键步骤之一是能够冻结预训练模型的层&#xff0c;以便在训练期间仅更新网络的某些部分。当您想要保留预训练模型已经学习的特征时&#xff0c;冻结至关重要。在本教程中…

4年软件测试,突破不了20K,太卷了。。。

先说一个插曲&#xff1a;上个月我有同学在深圳被裁员了&#xff0c;和我一样都是软件测试&#xff0c;不过他是平安外包&#xff0c;所以整个组都撤了&#xff0c;他工资和我差不多都是14K。 现在IT互联网已经比较寒冬&#xff0c;特别是软件测试&#xff0c;裁员先裁测试&am…

【Monorepo实战】pnpm+turbo+vitepress构建公共组件库文档系统

Monorepo架构可以把多个独立的系统放到一起联调&#xff0c;本文记录基于pnpm > workspace功能&#xff0c;如何构建将vitepress和组件库进行联调&#xff0c;并且使用turbo进行任务顺序编排。 技术栈清单&#xff1a; pnpm 、vitepress 、turbo 一、需求分析 1、最终目标…

Node.js 新特性 SEA/单文件可执行应用尝鲜

#1 关于 SEA 单文件可执行应用&#xff08;SEA&#xff0c;Singe Executable Applications&#xff09;&#xff0c;是 Node.js 新版本的特性&#xff0c;最初在 v19.7.0、v18.16.0 加入&#xff0c;并在 v20.x 得到扩展。而上个月发布的全家桶 Bun.js&#xff0c;就自带了 SEA…

正点原子嵌入式linux驱动开发——Busybox根文件系统构建

前面已经移植了TF-A、Uboot和Linux kernel&#xff0c;就剩最后一个 rootfs(根文件系统)了&#xff0c;本章就来学习一下根文件系统的组成以及如何构建根文件系统。这是Linux系统移植的最后一步&#xff0c;根文件系统构建好以后就意味着拥有了一个完整的、可以运行的最小系统 …