S++

千线一眼

导航

python爬虫-异步爬虫

前言

异步爬虫的目的:提高数据爬取的性能和效率

异步爬虫的方式

  • 多线程/多进程
    为相关阻塞的操作单独开启线程或进程,使得阻塞操作可以异步执行。但是由于资源有限,我们并不能无限制的开启多线程或多进程。
  • 线程池/进程池
    可以降低系统为了创建和销毁线程或进程而产生的开销。但是池中的线程或进程仍然是有上限的。
样式
"""
使用进程池模拟异步爬虫
"""
import time
from multiprocessing.dummy import Pool


def get_page(url):
    print('读取:', url)
    time.sleep(3)
    print('完成:', url)


if __name__ == '__main__':
    urls = ['url1', 'url2', 'url3', 'url4']
    # 实例化进程池
    pool = Pool(2)
    start_time = time.time()
    # 使用进程池
    pool.map(get_page, urls)
    end_time = time.time()
    print('second: %d' % (end_time - start_time))

读取: url1
读取: url2
完成: url1
读取:完成: url2
读取:  url3url4

完成:完成: url3 url4

second: 6
  • 单线程+异步协程
    这里涉及比较复杂我们在另外一篇里展开
    python版本要求:3.6
    event_loop:事件循环。相当于一个无限循环,我们可以把一些函数注册到这个事件循环上。当满足某些条件时,函数会循环执行。
    coroutine:协程对象。我们可以将协程对象注册到事件循环中,它会被事件循环调用。我么可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象。
    task:任务。他是协程对象的进一步封装,包含了任务的各个状态。
    future:表示将来执行或还未执行的任务,和 task没本质区别。
    async:定义一个协程。
    await:用来挂起阻塞方法的执行。

异步爬虫案例

进程池爬取梨视频音乐板块热门
"""
使用进程池爬取梨视频音乐栏目视频数据
注意:线程池处理的主要是阻塞且耗时的操作
"""
from multiprocessing.dummy import Pool  # 导出进程池模块对应的类
import requests
from lxml import etree
import random
import os


def get_video_url(url):
    # 在Network->XHR->Headers找到头部信息
    headers = {
        'Host': 'www.pearvideo.com',
        'Referer': url,
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/86.0.4240.198 Safari/537.36',
        'X-Requested-With': 'XMLHttpRequest'
    }

    ajax_url = "https://www.pearvideo.com/videoStatus.jsp?"  # Headers里面的Request URL,后面跟的是可变参数

    contId = url.split('/')[-1].split('video_')[1]  # contId就是视频url编号
    # print(contId)
    mrd = random.random()  # mrd就是个随机小数

    params = {
        'contId': contId,
        'mrd': str(mrd)
    }

    try:
        response = requests.get(url=ajax_url, headers=headers, params=params)
        # print(response.headers['content-type'])
        # print(response.encoding)
        # print(response.text)
        if response.status_code == 200:
            json = response.json()  # 因为MP4的视频是动态加载出来的,所以通过ajax请求获取视频的真实网址
            if json:
                fake_url = json['videoInfo']['videos']['srcUrl']  # 通过官网界面提取的url,并不是真正的url
                # print("fake_url:", fake_url)
                fake_url_list = fake_url.split('/')
                # print("fake_url_list:", fake_url_list)
                end = fake_url_list.pop()  # 删除不必要的字符串
                # print("end:", end)
                end_list = end.split("-")
                # print("end_list:", end_list)
                end_url = ""  # end_url是一个结尾字符串
                for i in range(len(end_list) - 1):
                    end_url = end_url + "-" + end_list[i + 1]
                # print("end_url:", end_url)

                """
                通过分析发现,ajax请求获取的网址是一个伪网址,和真实网址有区别
                伪地址:https://video.pearvideo.com/mp4/third/20210606/1623161815187-11724129-212335-ld.mp4
                真地址:https://video.pearvideo.com/mp4/third/20210606/cont-1731450-11724129-212335-ld.mp4
                """

                # 真实的地址,先用假地址,然后组合contId
                real_url = ""
                for element in fake_url_list:
                    real_url = real_url + element + "/"
                real_url = real_url + "cont-" + str(contId) + end_url
                print("real_url:", real_url)
                return real_url

    except requests.ConnectionError as e:
        print("错误:", e.args)


def download_video(url_dict):
    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                             'Chrome/86.0.4240.198 Safari/537.36'}
    video_data = requests.get(url=url_dict['url'], headers=headers).content
    print(url_dict['name'], "正在下载……")

    filename = url_dict['name']
    with open(filename, 'wb') as f:
        f.write(video_data)

    print(url_dict['name'], "下载成功!")


def main():
    url = 'https://www.pearvideo.com/category_59'  # 爬取梨视频“音乐版块”
    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                             'Chrome/86.0.4240.198 Safari/537.36'}
    response = requests.get(url=url, headers=headers)
    page_text = response.text
    tree = etree.HTML(page_text)
    li_list = tree.xpath('//ul[@id="listvideoListUl"]/li')
    video_urls = list()  # 用于存储多个url的列表

    for li in li_list:
        detail_url = 'https://www.pearvideo.com/' + li.xpath('./div/a/@href')[0]

        name = li.xpath('./div/a/div[@class="vervideo-title"]/text()')[0] + '.mp4'
        # print(detail_url, name)
        real_url = get_video_url(detail_url)
        url_dict = {'name': name, 'url': real_url}
        video_urls.append(url_dict)

    try:
        os.mkdir('LiShiPin')  # 创建“LiShiPin”文件夹
    except FileExistsError:
        pass
    os.chdir('LiShiPin')

    pool = Pool(4)
    pool.map(download_video, video_urls)
    pool.close()
    pool.join()


if __name__ == '__main__':
    main()

posted on 2022-03-17 18:08  S++  阅读(234)  评论(0)    收藏  举报