多任务爬虫
2020-07-07 12:24
246 查看
- 基于线程的生产者与消费者模式爬虫案例
1.1 进程与线程
进程:进程是资源(cpu,内存)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。
线程:线程是一条执行路径,是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。
一个正在运行的应用(如迅雷)就是一个进程,一个进程可以同时运行多个任务( 迅雷软件可以同时下载多个文件,每个下载任务就是一个线程), 可以简单的认为进程是线程的集合。
协程:是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
1.2 多线程加对列在生产者与消费者模式下爬虫的应用案例(王者荣耀壁纸爬取)
from urllib import parse from urllib import request import requests import os import threading import queue import time class Producer(threading.Thread): """生产者""" headers = { 'User-Agent': '', 'referer': 'https://pvp.qq.com/web201605/wallpaper.shtml' } def __init__(self,page_q,img_q,*args,**kwargs): super(Producer,self).__init__(*args,**kwargs) # 实例化两个对列 self.page_q = page_q self.img_q = img_q @staticmethod def get_url(data): """获取图片url""" urls=[] for x in range(1,9): # 获取每种图片的标号 img_no=f'sProdImgNo_{x}' # 从data中获取对应标号的加密url后再进行解码,然后将200替换成0 # "sProdImgNo_1":"http%3A%2F%2Fshp%2Eqpic%2Ecn%2 # Fishow%2F2735032619%2F1585220459%5F84828260%5F9035%5FsProdImgNo%5F1%2Ejpg%2F200" url=parse.unquote(data.get(img_no)).replace('200','0') urls.append(url) return urls def run(self): """拿到图片列表页url对列中的url后遍历获取每页的图片地址""" # 判断对列是否为空 while not self.page_q.empty(): # 取出每页的url url=self.page_q.get() # 提取字典格式的数据 text = requests.get(url, headers=self.headers).json() # 取出key为List的所有数据 datas = text["List"] for data in datas: # 提取图片地址 urls =Producer.get_url(data) # 提取名称 "sProdName":"%E9%98%BF%E8%BD%B2%2D%E8%BF%B7%E8%B8%AA%E4%B8%BD%E5%BD%B1%20" prod_name = parse.unquote(data['sProdName']).strip() # 创建存储路径 img_path = os.path.join('images', prod_name) if not os.path.exists(img_path): os.mkdir(img_path) # 遍历出所有url并将对应索引及名称创建成字典形式添加到对列 for index, img_url in enumerate(urls): self.img_q.put({'img_url':img_url,'prod_name':prod_name,'index':index}) # 打印当前执行的线程 print('%s线程执行完毕' %threading.current_thread().name) class Consumer(threading.Thread): def __init__(self,page_q,img_q,lock,*args,**kwargs): super(Consumer,self).__init__(*args,**kwargs) self.page_q=page_q self.img_q=img_q self.lock=threading.Lock() def run(self) -> None: """循环获取图片队列中字典中的数据""" while 1: try: # 从队中取出字典 img_obj=self.img_q.get(timeout=60) img_url=img_obj.get('img_url') name=img_obj.get('prod_name') index=img_obj.get('index') # 创建存储路径 img_path = os.path.join('images',name) try: request.urlretrieve(img_url, os.path.join(img_path, f"{index+1}.jpg")) print((img_url,os.path.join(img_path,f"{index+1}.jpg"))+'下载完成') except Exception as e: print(e) # 对列为空时返回报错信息 except queue.Empty as e: print(e) time.sleep(0.1) continue def main(): # 存放每页url的对列 page_q=queue.Queue(20) # 存放图片的对列 img_q=queue.Queue(1000) # 实例化一个线程锁 lock=threading.Lock() for x in range(18): # 获取图片的接口 url='https://apps.game.qq.com/cgi-bin/ams/module/ishow/V1.0/query/workList_inc.cgi?activityId=2735&sVerifyCode=ABCD' \ '&sDataType=JSON&iListNum=4&totalpage=0&page={page}&iOrder=0&iSortNumClose=1&iAMSActivityId=51991&_everyRead=true' \ '&iTypeId=1&iFlowId=267733&iActId=2735&iModuleId=2735&_=1587033640385'.format(page=x) page_q.put(url) for x in range(10): # 生产者线程,解析数据 t1=Producer(page_q,img_q,name=f"生产者线程{x}") t1.start() for x in range(5): # 消费者线程,保存数据 t2=Consumer(page_q,img_q,lock,name=f"消费者线程{x}") t2.start() if __name__ == '__main__': main()
- 基于异步协程的多任务爬虫案例
2.1 asyncio模块
特殊函数:函数被asyncio模块中async关键字修饰后内部操作不会立即执行,调用后返回协程对象,特殊函数内部不能存在不支持异步的模块的代码;
# 特殊函数 async def test(): print("Hello World")
任务对象:对协程对象进一步封装,可以绑定回调函数,在任务结束后执行,回调函数携带参数为其调用者(任务对象),任务对象调用result()返回特殊函数的return结果;
# 协程对象 c=test() # 任务对象 task=asyncio.ensure_future(c) # 绑定回调函数 task.add_done_callback(fun_callback)
循环事件对象:将多个任务对象注册到事件对象后启动事件可异步执行多个任务;
# 创建循环事件对象 loop=asyncio.get_event_loop() # 注册并启动事件 loop.run_until_complete(asyncio.wait(tasks))
wait方法:赋予将任务对象挂起的权限并释放cpu的使用
await关键字:在特殊函数中的阻塞操作用此关键字修饰
2.2 aiohttp异步请求模块
import aiohttp async def get_request(url): # 实例化一个请求对象 async with aiohttp.ClientSession() as sess: # 发起请求,返回响应对象 # get/post(url,headers,params/data,proxy="http://ip:port") async with await sess.get(url,headers=headers) as response: # text()获取字符串类型的响应数据 # read()获取byte类型的响应数据 res_text=await response.text() return res_text
异步多任务网易云歌曲下载案例:
import requests from fake_useragent import UserAgent from lxml import etree import asyncio import aiohttp from urllib import request ua=UserAgent().random headers={'user-agent':ua} def get_urls(): url=" https://music.163.com/discover/playlist" text=requests.get(url,headers=headers).text html=etree.HTML(text) url_list=html.xpath("//ul[@id='m-pl-container']/li/div/a/@href") # print(url_list) return url_list # print(url_list) async def get_request(url): # 实例化一个请求对象 async with aiohttp.ClientSession() as sess: # 发起请求,返回响应对象 async with await sess.get(url,headers=headers) as response: # 获取网页响应数据 res_text=await response.text() # print(res_text) return res_text def parse(t): """ 解析数据 :param t: t为parse的调用者(特殊函数) :return: """ # 特殊函数获取的源代码 text=t.result() html=etree.HTML(text) # 歌曲信息需在隐藏标签定位,可在response中分析 names=html.xpath("//ul[@class='f-hide']/li//text()") song_urls=html.xpath("//ul[@class='f-hide']/li/a/@href") # print(names,song_urls) for name,song_url in zip(names,song_urls): # 下载外链 url=f"http://music.163.com/song/media/outer/url?id={song_url.strip('/song?id=')}"+'.mp3' request.urlretrieve(url, f'./musics/{name}.mp3') print(f"{name}下载完成") if __name__=='__main__': tasks=[] for herf in get_urls(): url="https://music.163.com/"+herf # print(url) c=get_request(url) # 创建一个任务对象 task=asyncio.ensure_future(c) # 给任务对象绑定回调函数 task.add_done_callback(parse) tasks.append(task) # 创建一个时间循环对象 loop=asyncio.get_event_loop() # 注册并启动时间循环 loop.run_until_complete(asyncio.wait(tasks))
相关文章推荐
- jfinal 任务调度与爬虫 java
- Python初级爬虫(利用多任务协程爬取虎牙MM图片)
- java sql编辑器 动态报表 数据库备份还原 quartz定时任务调度 自定义表单 java图片爬虫
- 利用WebCollector爬虫内核定制自己的爬虫——任务生成器Generator
- 预习任务:python 网络爬虫
- 爬虫初学03:进程实现多任务
- python爬虫--多任务异步协程, 快点,在快点......
- 基于浏览器内核的被动式爬虫任务下发框架
- pyspider创建淘女郎图片爬虫任务--出师不利
- Python学习 第一天任务 (三:Python不止基础学习 继续安装爬虫环境的配置)
- 爬虫第四次任务,实现腾讯新闻爬取
- day08 数据推送,实时监控,定时任务,反爬虫介绍
- 爬虫任务三
- 【初码干货】记一次分布式B站爬虫任务系统的完整设计和实施
- pyspider创建淘女郎图片爬虫任务-源码解析
- 珠峰培训node 珠峰爬虫| cron 定时任务
- 用crontab做爬虫定时任务
- pyspider创建淘女郎图片爬虫任务-运行流程解析
- 分布式任务队列与任务调度系统Celery进阶——分布式爬虫