爬虫之 单线程+多任务异步协程
单线程+多任务异步协程的概念
- 实现的意义:
- 提取爬取数据的效率
- 实现异步爬虫的方式
- 多线程/多进程(不建议)
- 池(适当)
- 单线程+多任务异步协程(推荐)
- 概念
- 协程:协程对象。可以使用async关键字修饰一个函数的定义(特殊的函数),当该特殊的函数被调用后,就可以返回一个协程对象。当函数调用后,函数内部的实现语句不会被立即执行
- 协程 == 特殊函数
- 任务对象
- 本质上就是对协程对象的进一步封装
- 任务对象 == 特殊函数
- 给任务对象绑定一个回调
- add_done_callback(callback)
- 本质上就是对协程对象的进一步封装
- 事件循环(EventLoop):无限的循环对象
- 我们必须将任务对象注册到事件循环对象中,然后开启事件循环对象
- 事件循环对象在执行任务对象的时候是基于异步
- await async
- 注意事项
- 保证特殊函数内部不可以出现不支持异步模块对应的代码
- 在特殊函数内部遇到阻塞操作必须使用await关键字对其进行手动挂起
- 如果想要将多个任务对象注册到事件循环中,必须将多个任务对象封装到一个列表中,然后将列表注册(必须使用wait方法将列表中的任务对象进行挂起)到事件循环中
- aiohttp模块
- 是一个支持异步的网络请求模块
- pip install aiohttp
- selenium模块的使用
- 概念:就是一个基于浏览器自动化的模块
- elenium和爬虫之间的关联
- 很便捷的捕获动态加载的数据 - 可见即可得
- 实现模拟登陆
- 使用
- 环境的安装:
- pip install selenium
- 下载一个浏览器的驱动程序
- 谷歌驱动下载:http://chromedriver.storage.googleapis.com/index.html
- 驱动程序和浏览器版本的映射关系:http://blog.csdn.net/huilan_same/article/details/51896672
- 环境的安装:
- 创建某一款一个浏览器对象
- 动作链 如果想要触发一系列连续的行为动作
- 协程:协程对象。可以使用async关键字修饰一个函数的定义(特殊的函数),当该特殊的函数被调用后,就可以返回一个协程对象。当函数调用后,函数内部的实现语句不会被立即执行
代码逻辑:
定义一个特殊的函数,协程对象
import asyncio import time # 定义了一个特殊的函数 # 特殊:调用后会返回一个协程对象,且函数内部的实现语句不会被立即执行 # 创建一个协程对象 async def test(num): print(num) c = test(10) print(c)
#<coroutine object test at 0x0000025058808A40>
封装一个任务对象
#封装一个任务对象 async def test(num): print(num) c = test(10) # #根据协程对象封装了一个任务对象 task = asyncio.ensure_future(c) print(task)
#<Task pending coro=<test() running at H:/autoclient/test/test.py:6>>
创建事件并执行
#事件循环对象 async def request(url): print('正在请求:',url) time.sleep(2) print('请求完毕!',url) c1 = request('www.1.com') task_A = asyncio.ensure_future(c1) #创建一个事件循环对象 loop = asyncio.get_event_loop() #将任务对象注册到该对象中并且启动事件循环 loop.run_until_complete(task_A)
任务对象的绑定回调
import asyncio import time async def request(url): print('正在请求:',url) time.sleep(2) print('请求完毕!',url) return url #定义一个任务对象的回调函数 #task参数表示的就是该函数被绑定的那个任务对象 def task_callback(task): print('i am task_callback()') print(task.result()) #task.result()返回的就是任务对象对应的特殊函数内部的返回值 c = request('www.xxx.com') task = asyncio.ensure_future(c) task.add_done_callback(task_callback) loop = asyncio.get_event_loop() loop.run_until_complete(task)
多任务异步协程:
import asyncio import time start = time.time() #在特殊函数内部不可以出现不支持异步模块相关的代码 async def request(url): print('正在请求:',url) # time.sleep(2)#time模块是不支持异步 await asyncio.sleep(2) #阻塞操作必须使用await关键字进行挂起 print('请求完毕!',url) return url urls = [ 'www.1.com', 'www.2.com', 'www.3.com' ] def task_callback(task): print(task.result()) tasks = [] #多任务列表:存放多个任务对象 for url in urls: c = request(url) task = asyncio.ensure_future(c) task.add_done_callback(task_callback) tasks.append(task) #将多个任务对象装在到一个任务列表中 loop = asyncio.get_event_loop() #多任务注册 #wait就是将任务列表中的任务对象进行挂起 loop.run_until_complete(asyncio.wait(tasks)) print(time.time()-start)
多任务异步爬虫测试:一般开500个协程
flask代码:
from flask import Flask import time app = Flask(__name__) @app.route('/bobo') def index_bobo(): time.sleep(2) return 'Hello bobo' @app.route('/jay') def index_jay(): time.sleep(2) return 'Hello jay' @app.route('/tom') def index_tom(): time.sleep(2) return 'Hello tom' if __name__ == '__main__': app.run(threaded=True)
爬虫测试代码:
import asyncio import time import requests start = time.time() #在特殊函数内部不可以出现不支持异步模块相关的代码 async def request(url): print('正在请求:',url) response = requests.get(url) return response.text urls = [ 'http://127.0.0.1:5000/bobo', 'http://127.0.0.1:5000/tom', 'http://127.0.0.1:5000/jay' ] 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的使用:
mport 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/prames # #在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: #s.get/post和requests中的get/post用法几乎一样:url,headers,data/prames #在s.get中如果使用代理操作:proxy="http://ip:port" async with await s.get(url) as response: #获取字符串形式的响应数据:response.text() #获取byte类型的:response.read() page_text = await response.text() return page_text # urls = [ # 'http://127.0.0.1:5000/bobo', # 'http://127.0.0.1:5000/tom', # 'http://127.0.0.1:5000/jay', # 'http://127.0.0.1:5000/bobo', # 'http://127.0.0.1:5000/tom', # 'http://127.0.0.1:5000/jay', # 'http://127.0.0.1:5000/bobo', # 'http://127.0.0.1:5000/tom', # 'http://127.0.0.1:5000/jay', # ] urls = [] for i in range(500): urls.append('http://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)
案例:
import aiohttp import asyncio from lxml import etree all_titles = [] headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 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 = [] url = 'http://wz.sun0769.com/index.php/question/questionType?type=4&page=%d' for page in range(100): u_page = page * 30 new_url = format(url%u_page) urls.append(new_url) tasks = [] def parse(task): page_text = task.result() page_text = page_text.encode('gb2312').decode('gbk') tree = etree.HTML(page_text) tr_list = tree.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr') for tr in tr_list: title = tr.xpath('./td[2]/a[2]/text()')[0] print(title) all_titles.append(title) 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))
selenim演示:
from selenium import webdriver from time import sleep # 后面是你的浏览器驱动位置,记得前面加r'','r'是防止字符转义的 driver = webdriver.Chrome(r'chromedriver.exe') # 用get打开百度页面 driver.get("http://www.baidu.com") # 查找页面的“设置”选项,并进行点击 driver.find_elements_by_link_text('设置')[0].click() sleep(2) # # 打开设置后找到“搜索设置”选项,设置为每页显示50条 driver.find_elements_by_link_text('搜索设置')[0].click() sleep(2) # 选中每页显示50条 m = driver.find_element_by_id('nr') sleep(2) m.find_element_by_xpath('//*[@id="nr"]/option[3]').click() m.find_element_by_xpath('.//option[3]').click() sleep(2) # 点击保存设置 driver.find_elements_by_class_name("prefpanelgo")[0].click() sleep(2) # 处理弹出的警告页面 确定accept() 和 取消dismiss() driver.switch_to_alert().accept() sleep(2) # 找到百度的输入框,并输入 美女 driver.find_element_by_id('kw').send_keys('美女') sleep(2) # 点击搜索按钮 driver.find_element_by_id('su').click() sleep(2) # 在打开的页面中找到“Selenium - 开源中国社区”,并打开这个页面 driver.find_elements_by_link_text('美女_百度图片')[0].click() sleep(3) # 关闭浏览器 driver.quit()
selenium的基本使用:
from selenium import webdriver from time import sleep
//chromedriver.exe 浏览器的驱动文件 bro = webdriver.Chrome(executable_path='chromedriver.exe') #发起指定url的请求 bro.get('https://www.jd.com/') #在搜索框中搜索商品 #可以使用find系列的方法进行标签定位 search_input = bro.find_element_by_xpath('//*[@id="key"]') #想搜索框中写入商品名称 search_input.send_keys('iphonex') sleep(2) btn = bro.find_element_by_xpath('//*[@id="search"]/div/div[2]/button') btn.click() sleep(2) #执行js让滚轮向下滑动,滑动一屏 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('./jingdong.html','w',encoding='utf-8') as fp: fp.write(page_text) print(page_text) sleep(4) #关闭浏览器 bro.quit()
selenium的动作链
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()