爬虫开发05--异步爬虫

目录

 

异步爬虫

1、单线程爬虫

1)模拟网络请求,单线程下载 -- 多任务执行时,耗时多

2、线程池爬虫

1)模拟网络请求,使用线程池方式执行 -- 多任务执行时,耗时少,但受限于线程数量

2)线程池方式下载

3、单线程+异步协程爬虫(推荐)

1)未使用异步协程时,request方法会被立即运行;

2)使用async 关键字定义方法,request方法被调用后,方法体中的内容不会被立即执行;

3)使用异步协程,将协程对象注册到事件循环中,并启动事件循环后,request方法会被执行;

4)将协程对象封装到task任务对象中,将task注册到事件循环中

5)将协程对象封装到future任务对象中,将future任务对象注册到事件循环中

6)绑定回调 -- task.add_done_callback(callback_func)

4、多任务异步协程爬虫(推荐)

1)基于同步的多任务协程  -- 方法中含有同步代码

2)基于异步的多任务协程 -- 方法中的同步代码,改为异步代码

5、多任务异步协程爬虫 -- 基于异步的网络请求aiohttp模块

1)使用flask框架,写的服务端的接口

2)多任务异步协程--基于同步的网络请求的模块 requests.get(url)

3)多任务异步协程--基于异步的网络请求的模块 aiohttp.ClientSession()


异步爬虫

  • 在爬虫中,使用异步实现高性能的数据爬取操作;如requests.get()是一个阻塞的方法;
  • 异步爬虫的方式:
    • 1)多线程,多进程:不推荐使用(无法对每个请求开辟一个进程或线程)
      • 好处:可以为相关阻塞的操作单独开启线程或者进程,阻塞操作就可以异步执行;
      • 弊端:无法无限制的开启多线程或多进程(耗费CPU资源,影响响应效率)
    • 2)线程池,进程池适当使用
      • 好处:可以降低系统对进程或者线程创建和销毁的一个频率,从而很好的降低系统的开销;
      • 弊端:池中线程或进程的数量是有上限;(当阻塞操作大大高于线程池对象的时候,即待请求数远高于线程数量时,不适用;)
      • 原则:不能把所有的阻塞操作,都交给线程池来处理,线程池应该处理的是阻塞且耗时的操作
    • 3)单线程+异步协程:推荐
    • 4)多任务异步协程aiohttp:推荐

 

1、单线程爬虫

1)模拟网络请求,单线程下载 -- 多任务执行时,耗时多

  • 使用单线程下载(下载4个,每个下载需要时间为2秒,总耗时8秒)
# 模拟网络请求下载的操作,每个下载需要时间为2秒;
# 使用单线程串行方式执行
import time

def get_page(str):
    print('正在下载:', str)
    time.sleep(2)
    print('下载成功:', str)

name_list = ['xiaozi', 'aa', 'bb', 'cc']
start_time = time.time()

for i in range(len(name_list)):
    get_page(name_list[i])
end_time = time.time()
print('下载耗时为:%s 秒' % (end_time - start_time))

输出结果:

正在下载: xiaozi
下载成功: xiaozi
正在下载: aa
下载成功: aa
正在下载: bb
下载成功: bb
正在下载: cc
下载成功: cc
下载耗时为:8.002454280853271 秒

 

2、线程池爬虫

1)模拟网络请求,使用线程池方式执行 -- 多任务执行时,耗时少,但受限于线程数量

  • 使用线程池下载(下载4个,每个下载需要时间为2秒,总耗时2秒左右)
  • 导入线程池模块对应的类,应用在阻塞操作中:from multiprocessing.dummy import Pool
  • 实例化一个线程池对象,如:4个线程,pool = Pool(4)
  • 使用线程池处理阻塞的方法,参数:阻塞的方法,可迭代序列,result_list = pool.map(get_page, name_list)  
# 模拟网络请求下载的操作,每个下载需要时间为2秒;
# 使用线程池方式执行
import time
# 导入线程池模块对应的类,应用在阻塞操作中
from multiprocessing.dummy import Pool

def get_page(str):
    print('正在下载:', str)
    time.sleep(2)
    print('下载成功:', str)

name_list = ['xiaozi', 'aa', 'bb', 'cc']
start_time = time.time()

# 实例化一个线程池对象,4个线程
pool = Pool(4)

# 使用线程池处理阻塞的方法,参数:阻塞的方法,可迭代序列
# 将列表中每一个列表元素传递给get_page进行处理
result_list = pool.map(get_page, name_list)  # 阻塞方法get_page()的返回值会赋给result_list
end_time = time.time()
print('下载耗时为:%s 秒' % (end_time - start_time))
print(result_list)  # [None, None, None, None]
print(type(result_list))  # <class 'list'>

输出结果:

正在下载:正在下载:正在下载:  aa正在下载:bb 
cc 

xiaozi
下载成功:下载成功:下载成功: 下载成功:aa   bbxiaozi


cc
下载耗时为:2.042710781097412 秒
[None, None, None, None]
<class 'list'>

 

2)线程池方式下载

  • 线程池使用原则:线程池处理阻塞并且较为耗时的操作,如:耗时操作有视频下载,图片下载,mp3下载;文本解析,可以不使用线程池,耗时较少;
  • 线程开多了,影响下载速度,可能会产生连接超时;
  • 视频的下载地址,是在请求返回的response中,可以使用正则表达式re抽取response中的视频地址
    • src_url = re.findall('srcUrl":"(.*)"}', response.text)[0]
  • 线程池方式下载视频:pool.map(get_content, video_info_list)  # 参数是阻塞的方法,下载视频方法,和传入的视频下载地址,视频名称
# 爬取视频数据
# 线程池原则:线程池处理的是阻塞且耗时的操作

import requests
from fake_useragent import UserAgent
from lxml import etree
import re
import os.path
import os
import time
from multiprocessing.dummy import Pool

ua = UserAgent()
header = {
    "User-Agent": ua.random,
    "Referer": None
}

# 获取视频【世界】板块的新闻详情页url
def get_detail_url(url):
    response = requests.get(url=url, headers=header)
    response.encoding = 'utf-8'

    info_dic_list = []
    parser = etree.HTMLParser(encoding='utf-8')
    tree = etree.HTML(response.text, parser=parser)
    div_list = tree.xpath('//div[@class="vervideo-bd"]')
    for div in div_list:
        a = 'https://www.pearvideo.com/' + div.xpath('./a/@href')[0]
        video_name = div.xpath('.//div[@class="vervideo-title"]/text()')[0]
        info_dic = {
            "detail_url": a,
            "video_name": video_name
        }
        info_dic_list.append(info_dic)
    return info_dic_list

# 拼接新闻视频url
def get_video_url(detail_url):
    # 通过详情页获取contID
    cont_id = detail_url.rsplit(sep='_', maxsplit=1)[1]
    get_url = f'https://www.pearvideo.com/videoStatus.jsp?contId={cont_id}'  # ajax请求获取新闻video的url
    header["Referer"] = f"https://www.pearvideo.com/video_{cont_id}"
    session = requests.session()
    session.headers = header
    response = session.get(get_url)
    # 替换srcUrl中部分数字串为contID
    src_url = re.findall('srcUrl":"(.*)"}', response.text)[0]
    url_list = src_url.rsplit(sep='/', maxsplit=1)
    url_replace_list = url_list[1].split(sep='-', maxsplit=1)
    url_replace_list[0] = "cont-" + cont_id
    url_list[1
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值