一 线程池实现异步爬虫
import time import requests from multiprocessing.dummy import Pool start_time = time.time() def get_page(url): print("正在下载:", url) response = requests.get(url) time.sleep(3) print("下载完成", url) return {'url': url, 'content': response.text} urls = [ 'http://www.jd.com', 'https://www.baidu.com', 'https://www.python.org' ] if __name__ == '__main__': # 实例化一个线程对象 pool = Pool(4) # 将列表中每一个列表元素传给get_page进行处理 pool.map(get_page, urls)
使用线程池爬取梨视频数据
import time, re import requests from multiprocessing.dummy import Pool from lxml import etree # 线程池处理的是阻塞较为耗时的操作 start_time = time.time() headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36' } # 对下述url发起请求解析出视频详情页的url和视屏名称 url = "https://www.pearvideo.com/category_5" page_text = requests.get(url=url, headers=headers).text tree = etree.HTML(page_text) li_list = tree.xpath('//ul[@id="listvideoListUl"]/li') urls = [] # 存储所有视频的连接 for li in li_list: detail_url = "https://www.pearvideo.com/" + li.xpath("./div/a/@href")[0] name = li.xpath('./div/a/div[2]/text()')[0] + ".mp4" print(detail_url, name) # 对详情页的url发起请求 detail_page_text = requests.get(url=detail_url, headers=headers).text # 从详情页中解析出视频的地址 该视频地址在js中 ex = 'srcUrl="(.*?)",vdoUrl' video_url = re.findall(ex, detail_page_text)[0] urls.append({'name': name, "url": video_url}) def get_video_data(data): url = data['url'] video_name = data['name'] print(video_name, "正在下载") video_data = requests.get(url=url, headers=headers).content # 持久化存储操作 with open(video_name, 'wb') as fp: fp.write(video_data) print(video_name, "下载成功!") # 使用线程池对视频数据进行请求(较为耗时的阻塞操作) pool = Pool(4) pool.map(get_video_data, urls) pool.close() pool.join()
二 单线程+异步协程(推荐):
event_loop: 事件循环, 相当于一个无限循环, 我们可以把一些函数注册到这个事件循环上,当满足某些条件的时候,函数就会被循环执行
coroutine:协程对象, 我们可以将协程对象注册到事件循环中, 它会被事件循环调用。我们可以使用async关键字来定义一个方法, 这个方法在调用时不会立即被执行,而是返回一个协程对象
task: 任务, 它是对协程对象的进一步封装, 包含了任务的各个状态
async: 定义一个协程
await: 用来挂起阻塞方法的执行
协程使用示例
import asyncio async def request(url): print("正在请求的url是", url) print("请求成功", url) # async修饰函数, 调用之后返回一个协程对象 c = request("www.baidu.com") # 创建一个事件循环对象 loop = asyncio.get_event_loop() # 将协程对象注册到loop, 然后启动loop loop.run_until_complete(c)
task的使用
task是对 coroutine 对象的进一步封装,它里面相比 coroutine 对象多了运行状态,比如 running、finished 等,我们可以用这些状态来获取协程对象的执行情况。
import asyncio async def request(url): print("正在请求的url是", url) print("请求成功", url) # async修饰函数, 调用之后返回一个协程对象 c = request("www.baidu.com") loop = asyncio.get_event_loop() # 基于loop创建一个task对象 task = loop.create_task(c) print(task) loop.run_until_complete(task)
furture的使用
另外定义 task 对象还有一种方式,就是直接通过 asyncio 的 ensure_future() 方法,返回结果也是 task 对象,这样的话我们就可以不借助于 loop 来定义,即使我们还没有声明 loop 也可以提前定义好 task 对象,写法如下:
import asyncio async def request(url): print("正在请求的url是", url) print("请求成功", url) # async修饰函数, 调用之后返回一个协程对象 c = request("www.baidu.com") loop = asyncio.get_event_loop() task = asyncio.ensure_future(c) print(task) loop.run_until_complete(task) print(task)
绑定回调函数
import asyncio async def request(url): print("正在请求的url是", url) print("请求成功", url) # async修饰函数, 调用之后返回一个协程对象 c = request("www.baidu.com") loop = asyncio.get_event_loop() task = asyncio.ensure_future(c) # 回调函数 def callback_full(task): print(task.result()) # 将回调函数绑定到任务对象中 task.add_done_callback(callback_full) loop.run_until_complete(task)
多任务协程
如果我们想执行多次请求应该怎么办呢?我们可以定义一个 task 列表,然后使用 asyncio 的 wait() 方法即可执行。
import asyncio import time start = time.time() async def request(url): print("正在下载", url) # 在异步协程如果出现了同步模块相关的代码, 那么久无法实现异步 # time.sleep(2) # 基于异步模块, 当asyncio中遇到阻塞操作必须进行手动挂起 await asyncio.sleep(2) print("下载完毕", url) return url urls = [ "www.baidu.com", "www.sougou.com", "www.goubanjia.com" ] # 任务列表:存放多个任务对象 stasks = [] for url in urls: c = request(url) task = asyncio.ensure_future(c) stasks.append(task) loop = asyncio.get_event_loop() # 需要将任务列表封装到wait中 loop.run_until_complete(asyncio.wait(stasks)) print(time.time() - start)
多任务异步爬虫
import asyncio import time import requests start = time.time() urls = [ 'http://www.jd.com', 'https://www.baidu.com', 'https://www.python.org' ] async def get_page(url): print("正在下载", url) # request.get是基于同步, 必须使用基于异步的网络请求模块进行指定url的请求发送 # aiohttp: 基于异步网络请求模块 response = requests.get(url=url) print("下载完毕", response.text) tasks = [] for url in urls: c = get_page(url) task = asyncio.ensure_future(c) tasks.append(task) loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks)) end = time.time() print(end - start)
aiohttp+多任务异步协程
#环境安装:pip install aiohttp # 使用该模块中的ClientSession import asyncio import time import requests import aiohttp start = time.time() urls = [ 'http://www.jd.com', 'https://www.baidu.com', 'https://www.python.org' ] async def get_page(url): print("正在下载", url) # aiohttp: 基于异步网络请求模块 async with aiohttp.ClientSession() as session: # get():发起get请求 # post(): 发起post请求 # get和post可以添加headers(UA伪装)参数, params/data(携带参数), proxy="http://ip:port"(代理IP) async with await session.get(url) as response: # text()方法返回字符串形式的响应数据 # read()返回的二进制形式的响应数据 # json()返回josn对象 # 注意:在获取相应数据之前一定要使用await进行手动挂起 page_text = await response.text() print("下载完毕", page_text) tasks = [] for url in urls: c = get_page(url) task = asyncio.ensure_future(c) tasks.append(task) loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks)) end = time.time() print(end - start)