单线程+多任务异步协程:
意义:提升爬取数据的效率,我们也可以使用线程池,
异步爬虫方式:
- 多线程/多进程(电脑吃不消,没办法无节制开启)不建议
- 池:池中的线程或进程也是无法任意开启.
- 单线程+多任务异步协程(推荐)(500个协程,最优)
概念:
协程:本质就是一个对象,协程对象,怎么去获取?可以使用asynic
该关键字去修饰一个函数定义,此时的函数就叫它特殊函数,当该特殊函数被调用之后,就可以返回一个协程对象,特殊之处不仅仅是返回一个协程对象,当函数内部实现的语句不会被立即执行(时间循环开启后执行),导入模块import asycio
import asyncio
#特殊函数:
async def test(num):
print(num)
c = test(10)
print(c)
#内部函数语句没有被执行,返回的是一个协程对象,一个地址,仅仅一个协程对象没有意义
任务对象:
- 本质上就是对协程对象的进一步封装,封装之后也是一个协程对象,任务对象变相等于一个特殊函数.
- 给任务对象绑定一个回调:add_done_callback(callback)时间循环执行完成后再执行回调函数.
import asyncio
#特殊函数:
async def test(num):
print(num)
c = test(10)
#内部函数语句没有被执行,返回的是一个协程对象,一个地址,仅仅一个协程对象没有意义
#进行封装,根据一个协程对象封装一个任务对象
task = asyncio.ensure_future(c)
绑定回调:
import asyncio
import time
async def request(url):
print('正在请求:',url)
time.sleep(2)
print('请求完成':url)
return url
#定义一个任务对象的回调函数:
#task参数就是该函数被绑定的任务对象,必须要有.
def task_callback(task):
print('a callback')
print(task)
print(task.result())
#task.result()返回的是特殊函数内部的返回值.
c1 = request('www.qq.com')#协程对象
c2 = request('www.qq.com')
c3 = request('www.qq.com')
task_A = asyncio.ensure_future(c1)
task_B = asyncio.ensure_future(c2)
task_C = asyncio.ensure_future(c3)#任务对象
#绑定回调:函数请求完毕后调用
#只传回调函数的名字,没有传递参数,task就是参数,
task.add_done_callback(task_callback)
#创建时间循环对象
loop = asyncio.get_event_loop()
#将任务对象注册到事件循环对象中并且开启事件循环
loop.run_until_complete(task_A)#放协程对象会是可以的,肯定不会把协程对象直接注册到事件循环
事件循环:(eventloop)
我们必须将任务对象注册到事件循环对象中,开启事件循环对象.无限的循环对象(无法确定执行次数是多少次,遇到多少次阻塞).
事件循环开启之后,a,b,c
都开始循环执行,都会有阻塞,如果单线程的话,会耗时,事件循环是异步核心,事件循环对象执行任务对象是基于异步的,执行a对象的发现阻塞时,挂起阻塞对象,开始同时执行b,b遇到阻塞时也会执行c,但是此时当a阻塞完毕时,他会直接去执行a,等a处理完成后再去执行c.(a被执行了两次,所以跟任务对象个数无关)
import asyncio
import time
async def request(url):
print('正在请求:',url)
time.sleep(2)
print('请求完成':url)
c1 = request('www.qq.com')#协程对象
c2 = request('www.qq.com')
c3 = request('www.qq.com')
task_A = asyncio.ensure_future(c1)
task_B = asyncio.ensure_future(c2)
task_C = asyncio.ensure_future(c3)#任务对象
#创建事件循环对象
loop = asyncio.get_event_loop()
#将任务对象注册到时间循环对象中并且开启事件循环
loop.run_until_complete(task_A)#放协程对象会是可以的,肯定不会把协程对象直接注册到事件循环
关键字:
await
:
当前阻塞时,交出cpu
的控制权,就挂起.
注意事项:
- 特殊函数内部不可以出现不支持异步的模块代码
- 特殊函数内部遇到阻塞操作时,必须使用await对其进行手动挂起.
- 如果想要将多个任务注册到事件循环中,必须将多个任务对象装进一个列表中,必须使用wait方法将列表中的任务进行挂起.
多任务异步协程对象:
import asyncio
import time
#在特殊函数内部不可以出现不支持异步模块相关的代码.time模块不支持异步,
async def request(url):
print('正在请求:',url)
#time.sleep(2)
#阻塞之后挂起,手动的设置
await asyncio.sleep(2)
print('请求完成':url)
return url
urls = [
'www.goo.com',
'www.sss.com',
'www.sss.com',
'www.sss.com',
]
def task_callback(task):
print(task.result())
tasks = []
for url in urls:
c = request(url)
task = asyncio.ensure_future(cc)
task.add_done_callback(task_callback)
task.append(task)#装进一个列表内
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))#将任务列表中的任务对象进行挂起操作
requests
模块不支持异步,从而我们引入支持异步的模块aiohttp
aiohttp
模块:支持异步网络请求模块,pip install aiohttp
import asyncio
import time
import aiohttp
start = time.time()
#在特殊函数内部不可以出现不支持异步模块相关的代码
#简单的框架:在此基础上补充细节
async def request(url):
with aiohttp.ClientSession() as s:
#s.get/post和requests中的get/post用法几乎一样:url,headers,data/params
#在s.get中如果使用代理操作:proxy = 'http://ip:port'
with s.get(url) as response:
#获取字符串类型的响应数据:response.text()
#获取byte类型:response.read()
page_text = response.text()
return page_text
#细节1:在每一个with前加上async关键字
#细节2:在get方法前和response.text()前加上await关键字进行手动挂起操作
#真实代码:
async def request(url):
async with aiohttp.ClientSession() as s:
async with await s.get(url) as response:
page_text = await response.text()
return page_text
urls = []
for i in range(500):
urls.append('https://127.0.0.1:5000/bobo')
def parse(task):
page_text = task.result()
print(page_text+',请求数据')
tasks =[]
for url in urls:
c = 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))
print(time.time()-start)
aiohttp
案例:
import aiohttp
import asyncio
from lxml import etree
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
}
async def request(url):
async with aiohttp.ClientSession() as s:
async with await s.get(url,headers=headers) as response:
page_text = await response.text()
return page_text
urls = []
all_titles = []
url = 'http://wz.sun0769.com/index.php/question/reply?page=%d'
for page in range(10):
u_page = page*30
new_url = format(url%u_page)
urls.append(new_url)
def parse(task):
page_text = task.result()#page_text
tree = etree.HTML(page_text)
tr_list = tree.xpath('/html/body/div[4]/table[2]//tr')
for tr in tr_list:
title = tr.xpath('./td[2]/a[1]/text()')[0]
print(title)
all_titles.append(title)
tasks = []
for url in urls:
c = 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))
async
:
selenium
基本使用:
概念:基于浏览器自动化的模块.
selenium和爬虫之间的关联:很便捷的捕获动态加载的数据,实现模拟登陆,登陆成功之后可以将页面的爬取.
使用环境:
pip install selenium
下载一个浏览器的驱动程序
创建一个浏览器对象
from selenium import webdriver
bro = webdriver.Chrome(executable_path = 'chromedriver.exe')#驱动程序路径
#发起指定url请求
bro.get('http:www.baidu.com')
#再搜索框中搜索
#可以使用find系列方法标签定位
bro.find_element_by_xpath('//*[@id="key"]')
#向搜索框中写入商品名称
search_input.send_keys('iphone')
sleep(2)
btn = bro.find_element_by_xpath('//*[@id="search"]/div/div[2]/button')
btn.click()
sleep(2)
bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
sleep(2)
bro.execute_script('window.scrollTo(0,-document.body.scrollHeight)')
page_text = bro.page_source
with open('./jd.html','w',encoding='utf-8')as p:
p.write(page_text)
#关闭浏览器
bro.quit()
动作链:
如果想要触发一系列的连续的行为动作.actionchains
from selenium import webdriver
from selenium.webdriver import ActionChains #动作连
from time import sleep
bro = webdriver.Chrome(executable_path='chromedriver.exe')
bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
#定位要拖动的标签
#定位的标签是存在于iframe的子页面中,如果直接使用find做定位,是定位不到的
# target_ele = bro.find_element_by_id('draggable')
#像定位iframe中子页面中的标签必须进行如下操作
bro.switch_to.frame('iframeResult')
target_ele = bro.find_element_by_id('draggable')
#基于动作连实现滑动操作
action = ActionChains(bro)
#点击且长按
action.click_and_hold(target_ele)
for i in range(5):
#perform()表示立即执行动作连指定好的动作
action.move_by_offset(17,0).perform()
sleep(0.5)
action.release()
sleep(4)
bro.quit()