python 爬虫 入门 四、线程,历程,协程

[复制链接]
发表于 2026-1-4 17:15:42 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
目次
一、历程
 特性:
 使用:
初始代码
 历程改装代码
二、线程 
特性:
使用: 
三、协程 
后续:五、抓取图片、视频

        线程和历程大部分人估计都知道,但协程就不肯定了。
一、历程

        历程是使用体系分配资源和调理的根本单元,一个步调开始运行时,使用体系会给他分配一块独立的内存空间并分配一个PCB作为唯一标识。初始化内存空间后历程进入停当态,PCB插入停当队列。轮到该历程时,使用体系会给历程分配CPU时间片,让历程进入运行态。时间片用完后,重新返回停当队列,等候下一次分配。如果运行途中,历程遇到了壅闭变乱,就会让出CPU给其他停当历程,本身则进入壅闭队列,待壅闭竣过后,重新返回停当队列。
  
    历程的状态(网图)    特性:



  • 动态性:历程是步调的一次实行过程,有生命期。
  • 并发性:多个历程实体同存于内存中,能并发实行。
  • 独立性:历程是资源分配的根本单元,拥有独立的内存空间和体系资源。
  • 异步性:历程以各自独立、不可预知的速率向前推进。
  • 布局特性:每个历程由步调段、数据段和一个历程控制块(PCB)三部分构成。
 使用:

        历程用的比力少,线程协程用的多,由于历程之间的切换须要的资源太多了,比力慢,而且由于内存独立而不好通讯。
        本日试试这个网站泰坦陨落2steam版新手常见标题办理方法汇总 新手入门指南_逗游网一个游戏攻略,我们要实行获取每一页红框中的内容,统共9页。

        先来看看数据在不在页面源代码中,使用 Ctrl+U进入页面源代码,再Ctrl+F查找笔墨内容。发现页面源代码里有,这表明内容非脚本天生的,少了一大截贫困。

        老样子,通过抓包找到哀求,就知道了url和哀求方法,根据p差别的取值(1~9)即可切换差别的页面。如今我们可以开始写代码了。

初始代码

         先来个无并行的,只纪录哀求部分时间。末了效果存在word文档内里。之后只展示控制台输出,word输出没什么差别。
  1. import time
  2. from io import BytesIO
  3. import requests
  4. from bs4 import BeautifulSoup
  5. from docx import Document
  6. from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
  7. from docx.shared import Inches, RGBColor
  8. def out_word(bs_datas, out_path):  # 将bs_datas内容保存,out_path为保存文件名
  9.     # 创建Word文档
  10.     doc = Document()
  11.     # 遍历HTML内容
  12.     for bs_data in bs_datas:
  13.         if bs_data is None:
  14.             continue
  15.         for section in bs_data.find_all('p'):
  16.             section_all = section.find_all()  # 获取部分中所有元素
  17.             section_all.insert(0, section)  # 列表第一个插入
  18.             section_text = section_all[-1].string  # 最后一个应该是无嵌套的纯文本
  19.             if section_text is None or section_text.strip() == "":  # 空的看看是不是图片
  20.                 if section_all[-1].name == "img":
  21.                     paragraph = doc.add_paragraph()  # 添加一个新的段落
  22.                     run = paragraph.add_run()
  23.                     # 下载图片
  24.                     img_url = section_all[-1]['src']
  25.                     img_response = requests.get(img_url)  # 下载图片
  26.                     img_stream = BytesIO(img_response.content)
  27.                     # 将图片添加到Word文档中
  28.                     run.add_picture(img_stream, Inches(5))  # 调整图片宽度
  29.                 else:
  30.                     continue
  31.             else:  # 有文本,写下来
  32.                 paragraph = doc.add_paragraph()  # 添加一个新的段落
  33.                 run = paragraph.add_run('\t' + section_text.strip())  # 获取标签文本,前面空格
  34.             for part in section_all:  # 遍历每个部分,给段落添加属性
  35.                 if part.name == 'strong':  # 粗体
  36.                     run.bold = True  # 设置为粗体
  37.                 elif 'align' in part.attrs:  # 有对齐方式,这估计只有图片有个居中对齐,不过都写上吧
  38.                     align = part['align'].upper()
  39.                     if align == 'LEFT':
  40.                         paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT
  41.                     elif align == 'RIGHT':
  42.                         paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT
  43.                     elif align == 'CENTER':
  44.                         paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
  45.                     elif align == 'JUSTIFY':
  46.                         paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY
  47.                     else:
  48.                         paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT
  49.                 elif part.name == 'span' and 'style' in part.attrs:  # 有颜色
  50.                     style = part['style']
  51.                     if style.startswith('color:'):
  52.                         color_str = style.split(':')[1]
  53.                         # 将颜色字符串转换为 RGB 分量
  54.                         r, g, b = int(color_str[1:3], 16), int(color_str[3:5], 16), int(color_str[5:7], 16)
  55.                         # 创建一个 RGBColor 对象
  56.                         color = RGBColor(r, g, b)
  57.                         run.font.color.rgb = color  # 上色
  58.     # 保存Word文档
  59.     doc.save(out_path + '.docx')
  60. def get_text(bs_datas, url, i):  # 获取p=i页内容(bs4)存在bs_datas[i-1]
  61.     params = {
  62.         "p": str(i)
  63.     }
  64.     with requests.get(url=url, headers=headers, params=params) as resp:
  65.         resp.encoding = "utf-8"  # 当页面乱码改这里
  66.         bs = BeautifulSoup(resp.text, "html.parser")
  67.         data = bs.find("div", class_="CH396071PsfiiY01QjM3f")
  68.         bs_datas[i - 1] = data
  69.         print(f"页面{i}结束")
  70. url = "https://www.doyo.cn/article/396071"
  71. headers = {
  72.     # 用户代理,某些网站验证用户代理,微微改一下,如果提示要验证码之类的,使用它
  73.     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 "
  74.                   "Safari/537.36 Edg/126.0.0.0",
  75. }
  76. bs_datas = [None] * 9
  77. now_time = time.time()  # 记录访问时间
  78. for i in range(1, 10):
  79.     get_text(bs_datas, url, i)
  80. # get_text(bs_datas, url, 1)
  81. print("用时:", time.time() - now_time)
  82. print(bs_datas)
  83. out_word(bs_datas, 'output')
复制代码

  
    控制台部分输出   
  
    word文档部分效果    历程改装代码

寻常使用时,我们可以通过multiprocessing.Pool创建历程池大概multiprocessing.Process创建历程。先来两段代码展示一下历程池和历程的使用
历程池,历程池像是工具箱,会主动分配和采取历程。
  1. import time
  2. from multiprocessing import Pool
  3. import os
  4. def task(m,n):
  5.     for i in range(m,n):
  6.         print(f"进程: {os.getpid()} 输出:{i}")
  7.         # time.sleep(0)   # 睡一下,让出cpu(为了更好展示并发性)
  8. if __name__ == "__main__":
  9.     ranges = [(10,100)]*10
  10.     with Pool(processes=3) as pool:  # 使用3个进程
  11.         pool.starmap(task, ranges)   # 有10个任务
复制代码
   
      历程完成使命后被分配到新使命         
      三个历程一起完成使命      
历程,留意你启动历程后,只是把它放到了停当队列,具体什么时间运行要看什么时间轮到它。
  1. import time
  2. from multiprocessing import Process
  3. import os
  4. def task(m,n):
  5.     for i in range(m,n):
  6.         print(f"进程: {os.getpid()} 输出:{i}")
  7.         # time.sleep(0)   # 睡一下,让出cpu(为了更好展示并发性)
  8. if __name__ == "__main__":
  9.     processes = []
  10.     for i in range(3):
  11.         p = Process(target=task, args=(100,1000))
  12.         processes.append(p)
  13.     for p in processes: # 启动所有进程
  14.         p.start()
  15.     for p in processes: # join等待进程结束后才会继续运行。
  16.         p.join()
  17.     print("全部进程结束")
复制代码
两种方式都会出现下面这种征象,历程a和历程有几率交织输出,这就是并发的表现 

         下面是用历程改装的代码,(图片哀求591了,大概是刷太多次不让看了?以是加了个200判定,不影响。)切实能快一点。
  1. import time
  2. from io import BytesIO
  3. from multiprocessing import Pool
  4. import requests
  5. from bs4 import BeautifulSoup
  6. from docx import Document
  7. from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
  8. from docx.shared import Inches, RGBColor
  9. def out_word(bs_datas, out_path):  # 将bs_datas内容保存,out_path为保存文件名
  10.     # 创建Word文档
  11.     doc = Document()
  12.     # 遍历HTML内容
  13.     for bs_data in bs_datas:
  14.         if bs_data is None:
  15.             continue
  16.         for section in bs_data.find_all('p'):
  17.             section_all = section.find_all()  # 获取部分中所有元素
  18.             section_all.insert(0, section)  # 列表第一个插入
  19.             section_text = section_all[-1].string  # 最后一个应该是无嵌套的纯文本
  20.             if section_text is None or section_text.strip() == "":  # 空的看看是不是图片
  21.                 if section_all[-1].name == "img":
  22.                     paragraph = doc.add_paragraph()  # 添加一个新的段落
  23.                     run = paragraph.add_run()
  24.                     # 下载图片
  25.                     img_url = section_all[-1]['src']
  26.                     img_response = requests.get(img_url)
  27.                     if not img_response ==200:
  28.                         continue# 下载图片
  29.                     img_stream = BytesIO(img_response.content)
  30.                     # 将图片添加到Word文档中
  31.                     run.add_picture(img_stream, Inches(5))  # 调整图片宽度
  32.                 else:
  33.                     continue
  34.             else:  # 有文本,写下来
  35.                 paragraph = doc.add_paragraph()  # 添加一个新的段落
  36.                 run = paragraph.add_run('\t' + section_text.strip())  # 获取标签文本,前面空格
  37.             for part in section_all:  # 遍历每个部分,给段落添加属性
  38.                 if part.name == 'strong':  # 粗体
  39.                     run.bold = True  # 设置为粗体
  40.                 elif 'align' in part.attrs:  # 有对齐方式,这估计只有图片有个居中对齐,不过都写上吧
  41.                     align = part['align'].upper()
  42.                     if align == 'LEFT':
  43.                         paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT
  44.                     elif align == 'RIGHT':
  45.                         paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT
  46.                     elif align == 'CENTER':
  47.                         paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
  48.                     elif align == 'JUSTIFY':
  49.                         paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY
  50.                     else:
  51.                         paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT
  52.                 elif part.name == 'span' and 'style' in part.attrs:  # 有颜色
  53.                     style = part['style']
  54.                     if style.startswith('color:'):
  55.                         color_str = style.split(':')[1]
  56.                         # 将颜色字符串转换为 RGB 分量
  57.                         r, g, b = int(color_str[1:3], 16), int(color_str[3:5], 16), int(color_str[5:7], 16)
  58.                         # 创建一个 RGBColor 对象
  59.                         color = RGBColor(r, g, b)
  60.                         run.font.color.rgb = color  # 上色
  61.     # 保存Word文档
  62.     doc.save(out_path + '.docx')
  63. def get_text(bs_datas, headers, url, i):  # 获取p=i页内容(bs4)存在bs_datas[i-1]
  64.     params = {
  65.         "p": str(i)
  66.     }
  67.     with requests.get(url=url, headers=headers, params=params) as resp:
  68.         resp.encoding = "utf-8"  # 当页面乱码改这里
  69.         bs = BeautifulSoup(resp.text, "html.parser")
  70.         data = bs.find("div", class_="CH396071PsfiiY01QjM3f")
  71.         print(f"页面{i}结束")
  72.         return str(data),i
  73. if __name__ == "__main__":
  74.     url = "https://www.doyo.cn/article/396071"
  75.     headers = {
  76.         # 用户代理,某些网站验证用户代理,微微改一下,如果提示要验证码之类的,使用它
  77.         "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 "
  78.                       "Safari/537.36 Edg/126.0.0.0",
  79.     }
  80.     bs_datas = [None] * 9
  81.     data_chunks = [(bs_datas,headers, url, i) for i in range(1,10)]
  82.     now_time = time.time()  # 记录访问时间
  83.     with Pool(processes=4) as pool:  # 使用4个进程
  84.         datas = pool.starmap(get_text, data_chunks)
  85.     print("用时:", time.time() - now_time)
  86.     for data in datas:
  87.         bs_datas[data[1]-1]=BeautifulSoup(data[0], "html.parser")
  88.     print(bs_datas)
  89.     out_word(bs_datas, 'output')
复制代码

二、线程 

        线程是cpu调理的根本单元,一个历程能有许多个线程(至少一个),历程是线程的容器。他的状态、特性、使用都和历程相似,偶尔间也成为轻量级历程。
        与历程相比,线程的资源分配、调理、切换的花销都更少。而线程由于没有本身独立的内存空间,在通讯上更机动。
特性:



  • 轻量级:相对于历程而言,线程是轻量级的实行单元,它只拥有一点必不可少的资源,如步调计数器、一组寄存器和栈。
  • 共享资源:线程属于同一历程,它们共享历程的内存空间和资源,这使得线程之间的通讯更加方便。
  • 独立实行流:每个线程都有本身的实行路径,线程在实行过程中独立运行,互不干扰。
  • 上下文切换快:线程间的上下文切换相对较快,由于线程共享了大部分上下文信息。
使用: 

在python中,历程和线程的创建、使用的代码非常相似,这里只展示concurrent库线程池的使用:
  1. import threading
  2. from concurrent.futures import ThreadPoolExecutor
  3. import time
  4. def task(m, n):
  5.     for i in range(m, n):
  6.         print(f"线程: {threading.get_ident()} 输出:{i}")
  7.         # time.sleep(0)   # 睡一下,让出cpu(为了更好展示并发性)
  8. if __name__ == "__main__":
  9.     with ThreadPoolExecutor(3) as t:  # 使用3个线程
  10.         for i in range(10):
  11.             t.submit(task,m=10,n=100)
  12.     print("end")
复制代码
使用线程修改代码: 线程由于切换斲丧少,天然速率能更快一些。
  1. import time
  2. from io import BytesIO
  3. from concurrent.futures import ThreadPoolExecutor
  4. import requests
  5. from bs4 import BeautifulSoup
  6. from docx import Document
  7. from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
  8. from docx.shared import Inches, RGBColor
  9. def out_word(bs_datas, out_path):  # 将bs_datas内容保存,out_path为保存文件名
  10.     # 创建Word文档
  11.     doc = Document()
  12.     # 遍历HTML内容
  13.     for bs_data in bs_datas:
  14.         if bs_data is None:
  15.             continue
  16.         for section in bs_data.find_all('p'):
  17.             section_all = section.find_all()  # 获取部分中所有元素
  18.             section_all.insert(0, section)  # 列表第一个插入
  19.             section_text = section_all[-1].string  # 最后一个应该是无嵌套的纯文本
  20.             if section_text is None or section_text.strip() == "":  # 空的看看是不是图片
  21.                 if section_all[-1].name == "img":
  22.                     paragraph = doc.add_paragraph()  # 添加一个新的段落
  23.                     run = paragraph.add_run()
  24.                     # 下载图片
  25.                     img_url = section_all[-1]['src']
  26.                     img_response = requests.get(img_url)
  27.                     if not img_response ==200:
  28.                         continue# 下载图片
  29.                     img_stream = BytesIO(img_response.content)
  30.                     # 将图片添加到Word文档中
  31.                     run.add_picture(img_stream, Inches(5))  # 调整图片宽度
  32.                 else:
  33.                     continue
  34.             else:  # 有文本,写下来
  35.                 paragraph = doc.add_paragraph()  # 添加一个新的段落
  36.                 run = paragraph.add_run('\t' + section_text.strip())  # 获取标签文本,前面空格
  37.             for part in section_all:  # 遍历每个部分,给段落添加属性
  38.                 if part.name == 'strong':  # 粗体
  39.                     run.bold = True  # 设置为粗体
  40.                 elif 'align' in part.attrs:  # 有对齐方式,这估计只有图片有个居中对齐,不过都写上吧
  41.                     align = part['align'].upper()
  42.                     if align == 'LEFT':
  43.                         paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT
  44.                     elif align == 'RIGHT':
  45.                         paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT
  46.                     elif align == 'CENTER':
  47.                         paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
  48.                     elif align == 'JUSTIFY':
  49.                         paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY
  50.                     else:
  51.                         paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT
  52.                 elif part.name == 'span' and 'style' in part.attrs:  # 有颜色
  53.                     style = part['style']
  54.                     if style.startswith('color:'):
  55.                         color_str = style.split(':')[1]
  56.                         # 将颜色字符串转换为 RGB 分量
  57.                         r, g, b = int(color_str[1:3], 16), int(color_str[3:5], 16), int(color_str[5:7], 16)
  58.                         # 创建一个 RGBColor 对象
  59.                         color = RGBColor(r, g, b)
  60.                         run.font.color.rgb = color  # 上色
  61.     # 保存Word文档
  62.     doc.save(out_path + '.docx')
  63. def get_text(bs_datas, headers, url, i):  # 获取p=i页内容(bs4)存在bs_datas[i-1]
  64.     params = {
  65.         "p": str(i)
  66.     }
  67.     with requests.get(url=url, headers=headers, params=params) as resp:
  68.         resp.encoding = "utf-8"  # 当页面乱码改这里
  69.         bs = BeautifulSoup(resp.text, "html.parser")
  70.         data = bs.find("div", class_="CH396071PsfiiY01QjM3f")
  71.         bs_datas[i - 1] = data
  72.         print(f"页面{i}结束")
  73. if __name__ == "__main__":
  74.     url = "https://www.doyo.cn/article/396071"
  75.     headers = {
  76.         # 用户代理,某些网站验证用户代理,微微改一下,如果提示要验证码之类的,使用它
  77.         "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 "
  78.                       "Safari/537.36 Edg/126.0.0.0",
  79.     }
  80.     bs_datas = [None] * 9
  81.     data_chunks = [(bs_datas,headers, url, i) for i in range(1,10)]
  82.     now_time = time.time()  # 记录访问时间
  83.     with ThreadPoolExecutor(4) as t:  # 使用4个线程
  84.         for i in range(1,10):
  85.             t.submit(get_text,bs_datas=bs_datas,headers=headers,url=url,i=i)
  86.     print("用时:", time.time() - now_time)
  87.     print(bs_datas)
  88.     out_word(bs_datas, 'output')
复制代码
 

三、协程 

        协程比线程更小,它完满是在步调中切换代码的实行,而不须要使用体系的到场,具体来说,它可以在步调壅闭时去实行其他的代码段,而不须要白白让出cpu,不必切换线程也意味着没有切换斲丧。下面来一段步调来表现协程:
  1. import asyncio
  2. import time
  3. def f1s():
  4.     print("f1s-in")
  5.     time.sleep(1)
  6.     print("f1s-out")
  7. def f3s():
  8.     print("f3s-in")
  9.     time.sleep(3)
  10.     print("f3s-out")
  11. def f5s():
  12.     print("f5s-in")
  13.     time.sleep(5)
  14.     print("f5s-out")
  15. async def af1s():
  16.     print("f1s-in")
  17.     # time.sleep(1) # time.sleep是同步操作,会终止异步
  18.     await asyncio.sleep(1)  # 挂起代码,异步操作
  19.     print("f1s-out")
  20. async def af3s():
  21.     print("f3s-in")
  22.     # time.sleep(3)
  23.     await asyncio.sleep(3)
  24.     print("f3s-out")
  25. async def af5s():
  26.     print("f5s-in")
  27.     # time.sleep(5)
  28.     await asyncio.sleep(5)
  29.     print("f5s-out")
  30. async def main():
  31.     now_time = time.time()
  32.     tasks = [asyncio.create_task(af1s()),
  33.              asyncio.create_task(af3s()),
  34.              asyncio.create_task(af5s())]
  35.     await asyncio.wait(tasks)  # 一次启动多个任务
  36.     print("启动协程:", time.time() - now_time)
  37. if __name__ == '__main__':
  38.     now_time = time.time()
  39.     f1s()
  40.     f3s()
  41.     f5s()
  42.     print("正常:", time.time() - now_time)
  43.     asyncio.run(main())
复制代码

        可以看出协程可以大概在壅闭时实行其他函数,节流寿命。在爬虫中,哀求服务器的过程中有大量等候使用,协程能尽大概使用这段等候时间。

        如果要在爬虫中使用协程,我们须要将会产生壅闭的函数换成协程异步函数,比如文件读写用aiofiles库,网络哀求用aiohttp库。
        下面是用协程的代码,只需0.25s更快了。(没加反面生存部分,之前也不绝没盘算生存部分时间。)
  1. import asyncio
  2. import time
  3. import aiohttp
  4. from bs4 import BeautifulSoup
  5. async def get_text(bs_datas, headers, url, i):  # 获取p=i页内容(bs4)存在bs_datas[i-1]
  6.     params = {
  7.         "p": str(i)
  8.     }
  9.     async with aiohttp.ClientSession() as session:  # aiohttp.ClientSession()相当于requests
  10.         async with session.get(url=url, headers=headers, params=params)as resp:
  11.             bs = BeautifulSoup(await resp.text(), "html.parser")  # 图片换.content.red()
  12.             data = bs.find("div", class_="CH396071PsfiiY01QjM3f")
  13.             bs_datas[i - 1] = data
  14.             print(f"页面{i}结束")
  15. async def main(bs_datas, headers, url):
  16.     now_time = time.time()  # 记录访问时间
  17.     tasks = []
  18.     for i in range(1, 10):
  19.         tasks.append(asyncio.create_task(get_text(bs_datas=bs_datas, headers=headers, url=url, i=i)))
  20.     await asyncio.wait(tasks)  # 一次启动多个任务
  21.     print("启动协程:", time.time() - now_time)
  22. if __name__ == "__main__":
  23.     url = "https://www.doyo.cn/article/396071"
  24.     headers = {
  25.         # 用户代理,某些网站验证用户代理,微微改一下,如果提示要验证码之类的,使用它
  26.         "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 "
  27.                       "Safari/537.36 Edg/126.0.0.0",
  28.     }
  29.     bs_datas = [None] * 9
  30.     # asyncio.run(main(bs_datas, headers, url))
  31.     loop = asyncio.get_event_loop()
  32.     loop.run_until_complete(main(bs_datas, headers, url))
  33.     print(bs_datas)
复制代码

 在大量哀求下,协程可以大概节流大量时间,哀求数目越多越显着
后续:五、抓取图片、视频

        会有大量哀求,正是协程展示的好机会
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表