一 线程池实现异步爬虫

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)

 

posted on 2019-08-21 15:17  cs_1993  阅读(269)  评论(0编辑  收藏  举报