一.单线程+异步协程
1.基本概念:
event_loop:事件循环,相当于一个无限循环,我们可以把一些特殊函数注册(放置)到这个事件循环上,
当满足某些条件的时候,函数就会被循环执行。程序是按照设定的顺序从头执行到尾,运行的次数也是完全按照设定。
当在编写异步程序时,必然其中有部分程序的运行耗时是比较久的,需要先让出当前程序的控制权,让其在背后运行,
让另一部分的程序先运行起来。当背后运行的程序完成后,也需要及时通知主程序已经完成任务可以进行下一步操作,
但这个过程所需的时间是不确定的,需要主程序不断的监听状态,一旦收到了任务完成的消息,就开始进行下一步。
loop就是这个持续不断的监视器。 coroutine:中文翻译叫协程,在 Python 中常指代为协程对象类型,我们可以将协程对象注册到事件循环中, 它会被事件循环调用。我们可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即被执行, 而是返回一个协程对象。 task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。 future:代表将来执行或还没有执行的任务,实际上和 task 没有本质区别。 另外我们还需要了解 async/await 关键字,它是从 Python 3.6 才出现的,专门用于定义协程。4
其中,async 定义一个协程,await 用来挂起阻塞方法的执行。
#代码简单解释
import asyncio # 特殊函数 async def request(url): print('正在请求:',url) print('下载成功:',url) # 返回一个特殊函数的协程对象 c = request('www.baidu.com') #实例化一个事件循环对象 loop = asyncio.get_event_loop() #创建一个任务对象,将协程对象封装到了该对象中 # task = loop.create_task(c) #另一种形式实例化任务对象的方法 task = asyncio.ensure_future(c) print(task) #将协程对象注册到事件循环对象中,并且我们需要启动事件循环对象 loop.run_until_complete(task) print(task)
#给任务对象绑定回调函数
import asyncio async def request(url): print('正在请求:',url) print('下载成功:',url) return url #回调函数必须有一个参数:task #task.result():任务对象中封装的协程对象对应的特殊函数内部的返回值 def callbak(task): print('this is callback!') print(task.result()) c = request('www.baidu.com') #给任务对象绑定一个回调函数 task = asyncio.ensure_future(c) task.add_done_callback(callbak) loop = asyncio.get_event_loop() loop.run_until_complete(task)
2.多任务异步协程
#基本框架
from time import sleep import asyncio import time urls = ['www.baidu.com','www.sogou.com','www.goubanjia.com'] start = time.time() async def request(url): print('正在请求:',url) #在多任务异步协程实现中,不可以出现不支持异步的相关代码。 # sleep(2)
#碰到阻塞就挂起 await asyncio.sleep(2) print('下载成功:',url) loop = asyncio.get_event_loop() #任务列表:放置多个任务对象 tasks = [] for url in urls: c = request(url) task = asyncio.ensure_future(c) tasks.append(task)
#碰到阻塞就挂起 loop.run_until_complete(asyncio.wait(tasks)) print(time.time()-start)
3.在爬虫中的应用
安装模块:
#aiohttp:支持异步的一个基于网络请求的模块
# pip install aiohttp
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 requests import asyncio import time #单线程+多任务异步协程 urls = [ 'http://127.0.0.1:5000/jay', 'http://127.0.0.1:5000/bobo', 'http://127.0.0.1:5000/tom' ] async def get_pageText(url): print('正在下载:',url) #requests模块是不支持异步操作的。 page_text = requests.get(url).text print('下载完毕:',url) return page_text start = time.time() tasks = [] for url in urls: c = get_pageText(url) task = asyncio.ensure_future(c) tasks.append(task) loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks)) print(time.time()-start)
#协程在爬虫中的运用最终版
import requests import asyncio import time import aiohttp #单线程+多任务异步协程 urls = [ 'http://127.0.0.1:5000/jay', 'http://127.0.0.1:5000/bobo', 'http://127.0.0.1:5000/tom' ] #运用代理ip时的 请求操作: #async with await s.get(url,proxy="http://ip:port") as response: async def get_pageText(url): async with aiohttp.ClientSession() as s: async with await s.get(url) as response: page_text = await response.text() # 借助于回调函数进行响应数据的解析操作 return page_text #封装回调函数用于数据解析 def parse(task): #1.获取响应数据 page_text = task.result() print(page_text+',即将进行数据解析!!!') #解析操作写在该位置 start = time.time() tasks = [] for url in urls: c = get_pageText(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)
二.selenium(浏览器自动化模块)
1.基本概念
- 概念:是一个基于浏览器自动化的模块。 - 和爬虫之间的关联? - 帮我我们便捷的爬取到页面中动态加载出来的数据 - 实现模拟登陆
- 列举常见的selenium模块的方法及其作用 - get(url) - find系列的函数进行标签定位 - send_keys(‘key’) - click() - excute_script(‘jsCode’) - page_source - switch_to.frame('iframeID') - quite() - save_screenshot() - a = ActionChains(bro) - a.click_and_hold('tag') - tag.move_by_offset(x,y).perform()
2.安装使用
- pip install selenium - 下载对应的驱动程序:http://chromedriver.storage.googleapis.com/index.html
- 查看驱动和浏览器版本的映射关系:
http://blog.csdn.net/huilan_same/article/details/51896672
- 实例化一个浏览器对象(将浏览器的驱动程序加载到该对象中)
#对百度搜索的简单使用:
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()
#抓取药监总局公司信息:
from selenium import webdriver from lxml import etree import time bro = webdriver.Chrome(executable_path='./chromedriver.exe') #让浏览器对指定url发起访问 bro.get('http://125.35.6.84:81/xk/') #获取浏览器当前打开页面的页面源码数据(可见即可得) page_text = bro.page_source time.sleep(2) tree = etree.HTML(page_text) name = tree.xpath('//*[@id="gzlist"]/li[1]/dl/a/text()')[0] print(name) time.sleep(3) bro.quit()
3.动作制定
①基本操作
from selenium import webdriver import time bro = webdriver.Chrome(executable_path='./chromedriver.exe') bro.get('https://www.taobao.com') #节点定位 find系列的方法(多种方法可以使用) input_text = bro.find_element_by_id('q') #节点交互 input_text.send_keys('苹果') time.sleep(2) #执行js程序(js注入) #向下拖动滚轮一屏的高度 bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') #找到并点击搜索按钮 btn = bro.find_element_by_css_selector('.btn-search').click() time.sleep(3) bro.quit()
②动作链(按住拖动)
from selenium import webdriver #导入动作链对应的模块 from selenium.webdriver import ActionChains import time bro = webdriver.Chrome(executable_path='./chromedriver.exe') bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
#如果定位的节点是被包含在iframes节点之中的,则必须使用switch_to进行frame的切换 bro.switch_to.frame('iframeResult') div_tag = bro.find_element_by_id('draggable') #实例化一个动作链对象(需要将浏览器对象作为参数传递给该对象的构造方法) action = ActionChains(bro) #单击且长按 action.click_and_hold(div_tag) for i in range(5): #让div向右移动 action.move_by_offset(17,0).perform() #perform()立即执行动作链 time.sleep(0.5) time.sleep(2) bro.quit()
4.无头浏览器(无可视化)
- 无可视化浏览器
常用的无头浏览器:
- phantomJs
- 谷歌无头(推荐)
#抓取药监总局公司信息:
from selenium import webdriver from lxml import etree import time from selenium.webdriver.chrome.options import Options
#实例化 chrome_options = Options() chrome_options.add_argument('--headless') chrome_options.add_argument('--disable-gpu') bro = webdriver.Chrome(executable_path='./chromedriver.exe',chrome_options=chrome_options) #让浏览器对指定url发起访问 bro.get('http://125.35.6.84:81/xk/') #获取浏览器当前打开页面的页面源码数据(可见即可得) page_text = bro.page_source time.sleep(2) tree = etree.HTML(page_text) name = tree.xpath('//*[@id="gzlist"]/li[1]/dl/a/text()')[0] print(name) time.sleep(3) bro.quit()
5.规避被检测
from selenium import webdriver from lxml import etree import time from selenium.webdriver import ChromeOptions
option = ChromeOptions() option.add_experimental_option('excludeSwitches', ['enable-automation']) bro = webdriver.Chrome(executable_path='./chromedriver.exe',options=option) #让浏览器对指定url发起访问 bro.get('http://125.35.6.84:81/xk/') #获取浏览器当前打开页面的页面源码数据(可见即可得) page_text = bro.page_source time.sleep(2) tree = etree.HTML(page_text) name = tree.xpath('//*[@id="gzlist"]/li[1]/dl/a/text()')[0] print(name) time.sleep(5) bro.quit()
6.超级鹰+截图+浏览器自动化+动作链
- 超级鹰
http://www.chaojiying.com/ - 注册:普通用户/开发者(都可以) - 登陆: - 创建一个软件(id) - 下载示例代码
#用超级鹰解析验证码
import requests
from hashlib import md5
class Chaojiying_Client(object): def __init__(self, username, password, soft_id): self.username = username password = password.encode('utf8') self.password = md5(password).hexdigest() self.soft_id = soft_id # 898175 self.base_params = { 'user': self.username, 'pass2': self.password, 'softid': self.soft_id, # 898175 } self.headers = { 'Connection': 'Keep-Alive', 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)', } def PostPic(self, im, codetype): """ im: 图片字节 codetype: 题目类型 参考 http://www.chaojiying.com/price.html """ params = { 'codetype': codetype, } params.update(self.base_params) files = {'userfile': ('ccc.jpg', im)} r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers) return r.json() def ReportError(self, im_id): """ im_id:报错题目的图片ID """ params = { 'id': im_id, } params.update(self.base_params) r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers) return r.json()
# 12306自动登录
from selenium import webdriver import time from selenium.webdriver import ActionChains from PIL import Image bro = webdriver.Chrome(executable_path=r'./chromedriver.exe') bro.get('https://kyfw.12306.cn/otn/login/init') time.sleep(2) code_img_ele = bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img') time.sleep(2) #验证码图片的左上角的坐标 location = code_img_ele.location # x,y print('验证码图片的左上角的坐标locaotion:', location) size = code_img_ele.size # 验证码图片的长和宽 print('验证码图片的长和宽size:', size) #验证码左上角和右下角这两个点的坐标 rangle = ( int(location['x']), int(location['y']), int(location['x'] + size['width']), int(location['y'] + size['height']) ) #截取当前浏览器打开的这张页面对应的图像 bro.save_screenshot('aa.png') i = Image.open('./aa.png') #即将被截取下来验证码图片的名称 code_img_name = './code.png' #crop就可以根据左上角和右下角的坐标进行指定区域的截取 frame = i.crop(rangle) frame.save(code_img_name) #调用超级鹰 chaojiying = Chaojiying_Client('xuebaohua', '123456xbh', '898175') # 用户中心>>软件ID 生成一个替换 96001 im = open('./code.png', 'rb').read() result = chaojiying.PostPic(im, 9004) # 9004验证码类型 print("***************", result) result = result['pic_str'] all_list = [] #[[x1,y1],[x2,y2],[x3,y3]] if '|' in result: # 117,75|188,146|117,75 list_1 = result.split('|') count_1 = len(list_1) for i in range(count_1): xy_list = [] x = int(list_1[i].split(',')[0]) #[[x,y],[]] y = int(list_1[i].split(',')[1]) xy_list.append(x) xy_list.append(y) all_list.append(xy_list) else: x = int(result.split(',')[0]) #[[x,y]] y = int(result.split(',')[1]) xy_list = [] xy_list.append(x) xy_list.append(y) all_list.append(xy_list) print(all_list) code_img = bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img') action = ActionChains(bro) for l in all_list: x = l[0] y = l[1] ActionChains(bro).move_to_element_with_offset(code_img, x, y).click().perform() bro.find_element_by_id('username').send_keys('1234') time.sleep(2) bro.find_element_by_id('password').send_keys('123456') time.sleep(2) bro.find_element_by_id('loginSub').click() time.sleep(4) bro.find_element_by_xpath('//*[@id="J-index"]/a').click() time.sleep(4) # bro.find_element_by_id('fromStationText').send_keys('北京') # 出发地 输入拼音大小写或汉字 # time.sleep(0.2) # bro.find_element_by_id('toStationText').send_keys('天津') # 到达地 输入拼音大小写或汉字 # time.sleep(0.2) # bro.find_element_by_id('train_date').send_keys('2019-5-6') # 出发日期 # time.sleep(1) # bro.find_element_by_id('search_one').click() # time.sleep(3) bro.quit()
三. pyppeteer 模块(浏览器自动化)
在pyppeteer中,实际有 chromium浏览器进行页面渲染
chromium是 Chrome(谷歌)的测试版本
第一次安装较慢,要下载
支持异步
#示例:
from pyppeteer import launch import asyncio from lxml import etree async def main(): #实例化一个浏览器对象(谷歌浏览器测试版本) bro = await launch(headless=False) #新建一个空白页 page = await bro.newPage() await page.goto('http://quotes.toscrape.com/js/') #获取page当前显示页面的源码数据 page_text = await page.content() return page_text def parse(task): page_text = task.result() tree = etree.HTML(page_text) div_list = tree.xpath('//div[@class="quote"]') for div in div_list: content = div.xpath('./span[1]/text()')[0] print(content) c = main() task = asyncio.ensure_future(c) task.add_done_callback(parse) loop = asyncio.get_event_loop() loop.run_until_complete(task)