数据爬取
协程
- 特殊的函数:
- 如果一个函数的定义被async修饰后,则该函数就是一个特殊的函数。
- 协程:
- 对象。特殊函数被调用后,函数内部的实现语句不会被立即执行,然后该函数
调用会返回一个协程对象。
- 结论:协程对象==特殊的函数调用
- 任务对象
- 起始就是对协程对象的进一步封装。
- 结论:任务对象==高级的协程对象==特殊的函数调用
- 绑定回调:
- 回调函数什么时候被执行?
- 任务对象执行结束后执行回调函数
- task.add_done_callback(func)
- func必须要有一个参数,该参数表示的是该回调函数对应的任务对象
- 回调函数的参数.result():任务对象对应特殊函数内部的返回值
- 事件循环对象
- 作用:将其内部注册的任务对象进行异步执行。
- 编码流程:
- 定义特殊函数
- 创建协程对象
- 封装任务对象
- 创建事件循环对象
- 将任务对象注册到事件循环中且开启事件循环对象
- 注意:在特殊函数内部的实现语句中不可以出现不支持异步的模块对应的代码,否则
就是终止多任务异步协程的异步效果
单协程
import asyncio from time import sleep #函数的定义 async def get_request(url): print('正在请求:',url) sleep(1) print('请求结束:',url) #函数调用:返回的就是一个协程对象 c = get_request('www.1.com') #创建一个任务对象:基于协程对象创建 task = asyncio.ensure_future(c) #创建一个事件循环对象 loop = asyncio.get_event_loop() #将任务对象注册到事件循环对象中并且开启事件循环 loop.run_until_complete(task)
多任务异步协程
import asyncio from time import sleep import time #函数的定义 async def get_request(url): print('正在请求:',url) await asyncio.sleep(1) print('请求结束:',url) #创建3个协程对象 urls = [ '1.com','2.com','3.com' ] start = time.time() #任务列表:存储的是多个任务对象 tasks = [] for url in urls: c = get_request(url) # 创建一个任务对象:基于协程对象创建 task = asyncio.ensure_future(c) tasks.append(task) #创建一个事件循环对象 loop = asyncio.get_event_loop() #将任务对象注册到事件循环对象中并且开启事件循环 loop.run_until_complete(asyncio.wait(tasks)) # 放入的如果是列表要加asyncio.wait() print('总耗时:',time.time()-start)
给任务对象绑定回调
func必须要有一个参数,该参数表示的是该回调函数对应的任务对象
回调函数的参数.result():任务对象对应特殊函数内部的返回值
task.add_done_callback(parse)
import asyncio from time import sleep import time #函数的定义 async def get_request(url): print('正在请求:',url) await asyncio.sleep(3) print('请求结束:',url) return 'bobo' def parse(task): print('i am task callback()!!!=----',task.result()) #创建3个协程对象 urls = [ '1.com','2.com','3.com' ] start = time.time() #任务列表:存储的是多个任务对象 tasks = [] for url in urls: c = get_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)
多任务的异步爬虫(重点)
- 注意重点:requests模块不支持异步,在多任务的异步协程中不可以使用requests
- aiohttp
- 概念:支持异步的网络请求模块
- 编码流程:
- 写基本架构:
with aiohttp.ClientSession() as s:
with s.get(url) as response:
page_text = response.text()
return page_text
- 补充细节:
- 添加async关键字
- 每一个with前加上async
- 添加await关键字
- 加载每一步的阻塞操作前加上await
- 请求
- 获取响应数据
import asyncio import requests import time import aiohttp from lxml import etree #特殊函数:发起请求获取页面源码数据 # async def get_request(url): # #requests是一个不支持异步的模块 # page_text = requests.get(url).text # return page_text async def get_request(url): async with aiohttp.ClientSession() as s: #get/post:proxy = 'http://ip:port' #url,headers,data/prames跟requests一直 async with await s.get(url) as response: page_text = await response.text()#text()字符串形式的响应数据。read()二进制的响应数据 return page_text def parse(task): page_text = task.result() tree = etree.HTML(page_text) print(tree.xpath('/html/body/div[2]/div[3]/div[3]/h3/a/@href')) urls = [ 'https://www.huya.com/?ptag=gouzai&rso=huya_h5_395', ] start = time.time() tasks = [] #任务列表 for url in urls: c = get_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)
selenium
- 概念:基于浏览器自动化的一个模块。 - Appium是基于手机的自动化的模块。 - selenium和爬虫之间的关联 - 便捷的爬取到动态加载的数据 - 可见即可得 - 便捷的实 现模拟登陆 - 基本使用: - 环境安装 - pip install selenium - 下载浏览器的驱动程序 - http://chromedriver.storage.googleapis.com/index.html - 浏览器版本和驱动程序的映射关系:https://blog.csdn.net/huilan_same/article/details/51896672
selenium的基本使用
from selenium import webdriver # webdriver外部浏览器的驱动 import time #实例化某一款浏览器对象 bro = webdriver.Chrome(executable_path='chromedriver.exe') #基于浏览器发起请求 bro.get('https://www.jd.com/') #商品搜索 #标签定位 search_input = bro.find_element_by_id('key') #往定位到的标签中录入数据 search_input.send_keys('袜子') #点击搜索按钮 btn = bro.find_element_by_xpath('//*[@id="search"]/div/div[2]/button') time.sleep(2) btn.click() #点击 time.sleep(2) #滚轮滑动(js注入) bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') time.sleep(6) # bro.quit() # 关闭浏览器
捕获动态家加载的数据
from selenium import webdriver import time from lxml import etree #实例化某一款浏览器对象 bro = webdriver.Chrome(executable_path='chromedriver.exe') bro.get('https://www.fjggfw.gov.cn/Website/JYXXNew.aspx') time.sleep(1) #page_source:当前页面所有的页面源码数据 page_text = bro.page_source #存储前3页对应的页面源码数据 all_page_text = [page_text] for i in range(3): next_page_btn = bro.find_element_by_xpath('//*[@id="kkpager"]/div[1]/span[1]/a[7]') next_page_btn.click() time.sleep(1) all_page_text.append(bro.page_source) for page_text in all_page_text: tree = etree.HTML(page_text) title = tree.xpath('//*[@id="list"]/div[1]/div/h4/a/text()')[0] print(title)
动作连
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') sleep(1) bro.switch_to.frame('iframeResult') #frame的参数为iframe标签的id属性值 div_tag = bro.find_element_by_id('draggable') #基于动作连实现滑动操作 action = ActionChains(bro) #点击且长按 action.click_and_hold(div_tag) for i in range(5): #perform()表示让动作连立即执行 action.move_by_offset(20,20).perform() sleep(0.5) sleep(3) bro.quit()
谷歌无头浏览器
bro.save_screenshot('./123.jpg') # 截取的屏幕截图会保存在当前的目录下
- 无头浏览器
- phantomjs
- 谷歌无头浏览器(推荐)
from selenium import webdriver from time import sleep from selenium.webdriver.chrome.options import Options # 创建一个参数对象,用来控制chrome以无界面模式打开 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) bro.get('https://www.taobao.com/') bro.save_screenshot('./123.jpg') # 截取的屏幕截图会保存在当前的目录下 print(bro.page_source)
规避检测
- 如何规避selenium被监测到的风险
- 网站可以根据:window.navigator.webdriver的返回值鉴定是否使用了selenium
- undefind:正常
- true:selenium
from selenium import webdriver from time import sleep # 规避检测 from selenium.webdriver import ChromeOptions option = ChromeOptions() option.add_experimental_option('excludeSwitches', ['enable-automation']) # 后面是你的浏览器驱动位置,记得前面加r'','r'是防止字符转义的 bro = webdriver.Chrome(r'chromedriver.exe',options=option) bro.get('https://www.taobao.com/')
12306模拟登陆
rom ChaoJiYing import Chaojiying_Client from selenium import webdriver from selenium.webdriver import ActionChains from time import sleep #下载pil或者是Pillow 图像处理 from PIL import Image def transformCode(imgPath,imgType): chaojiying = Chaojiying_Client('bobo328410948', 'bobo328410948', '899370') im = open(imgPath, 'rb').read() return chaojiying.PostPic(im,imgType)['pic_str'] bro = webdriver.Chrome(executable_path='chromedriver.exe') bro.get('https://kyfw.12306.cn/otn/login/init') sleep(2) bro.save_screenshot('main.png') #在main.jpg中截取下验证码图片 img_tag = bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img') location = img_tag.location #左下角坐标 size = img_tag.size #img标签对应图片的长宽(尺寸) #裁剪范围 rangle = (int(location['x']),int(location['y']),int(location['x']+size['width']),int(location['y']+size['height'])) i = Image.open('./main.png') frame = i.crop(rangle) frame.save('code.png') result = transformCode('./code.png',9004) #260,140|260,139 ==> [[260,140],[260,139]] all_list = []#[[260,140],[260,139]] if '|' in result: list_1 = result.split('|') count_1 = len(list_1) for i in range(count_1): xy_list = [] x = int(list_1[i].split(',')[0]) 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]) y = int(result.split(',')[1]) xy_list = [] xy_list.append(x) xy_list.append(y) all_list.append(xy_list) for xy in all_list: x = xy[0] y = xy[1] ActionChains(bro).move_to_element_with_offset(img_tag,x,y).click().perform() sleep(1)
小结:
- 多任务的异步协程 - 特殊的函数 - 函数调用的时候函数内部的程序语句不会被立即执行 - 函数调用后会返回一个协程对象 - 协程对象:等同于特殊的函数 - 任务对象:高级的协程对象==协程==特殊的函数 - 绑定回调:task.add_done_callback(parse) - 回调函数一定是在任务对象执行结束后被执行 - 主要是用来实现数据解析的 - 事件循环对象: - 需要将多个任务注册/放置在该对象中 - 启动事件循环,该对象就可以异步的去执行其内部注册的所有的任务对象 - 挂起:将任务列表注册到事件循环中的时候需要使用asyncio.wait(tasks) - 注意事项:在特殊函数内部的程序语句中不可以出现不支持异步的模块代码 - requests就不可以在该模式下被使用 - aiohttp:支持异步的网络请求模块 - 基本架构:with实现 - 补充细节
import aiohttp
import asyncio
async def get_request(url):
async with aiohttp.ClientSession() as s:
async with await s.get(url) as response:
page_text = await response.text()#read()
return page_text
- selenium
- 概念
- 和爬虫之间的关联是什么?
- 便捷的捕获动态加载的数据(可见即可得)
- 实现模拟登陆
- 基本使用:
- 实例化一个浏览器对象(驱动程序)
- get()
- find系列的函数:用作于标签定位
- send_keys():进行向标签中录入数据
- click
- excute_script(js):js注入
- page_source:返回的是页面源码数据
- switch_to.frame()
- save_scrennshot()
- 动作连:ActionChains
- 无头浏览器:
- phantomjs
- 谷歌无头
- 规避监测
空气质量爬取
- 分析 - 1.在页面中更换查找条件可以让抓包工具捕获到我们想要的数据包 - 2.apistudyapi.php该数据包就是我们最终定位到的爬取数据对应的数据包 - 该数据包中可以提取到url和请求参数(可能是一组密文,然后该是动态变化) - 响应数据的是经过加密的密文 - 3.当修改了查询条件后且点击了查询按钮后发起了一个ajax请求,该请求就可以请求到apistudyapi.php数据包 - 想要捕获的数据是可以通过点击搜索按钮生成的 - 4.通过火狐浏览器的开发者工具可以找到搜索按钮绑定的点击事件对应的事件函数(getData()) - 5.分析getData():在该函数实现内部没有找到ajax请求对应的操作 - type这个变量可以为HOUR - getAQIData();getWeatherData(); - 6.分析:getAQIData();getWeatherData(); - 发现这两个函数的实现除了method变量的赋值不同剩下的都一致 - method = (GETDETAIL 或者 GETCITYWEATHER) - 在这两个函数的实现中也没有发现ajax请求对应的代码,但是发现了一个叫做getServerData的函数调用,则分析ajax请求对应的代码肯定是存在于getServerData这个函数的实现中 - getServerData(method, param,匿名函数,0.5) - method = (GETDETAIL 或者 GETCITYWEATHER) - param是一个字典,内部有四组(city,type,startTime,endTime)键值对 - 7.分析getServerData函数的实现: - 最终通过抓包工具的全局搜索定位到了该函数的实现,但是实现的js代码被加密了,该种形式的加密被称为js混淆。 - 如何破解js混淆? - http://www.bm8.com.cn/jsConfusion/进行js反混淆 - 在该函数的实现中终于找到了ajax请求对应的代码: - ajax请求的url - ajax请求方式 - 请求参数的来源:getParam(method, param) - 对加密的响应数据解密:decodeData(密文) - 8.基于python模拟执行js代码 - PyExecJS模块可以让python模拟执行js代码 - 环境安装: - pip install PyExecJS - 在本机安装nodejs的开发环境
获取ajax请求的动态变化且加密的请求参数
#获取ajax请求的动态变化且加密的请求参数(d:xxx) import execjs node = execjs.get() # Params method = 'GETCITYWEATHER' city = '北京' type = 'HOUR' start_time = '2018-01-25 00:00:00' end_time = '2018-01-25 23:00:00' # Compile javascript file = 'test.js' ctx = node.compile(open(file,encoding='utf-8').read()) # Get params js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time) params = ctx.eval(js) print(params)
携带捕获到请求参数进行请求
#携带捕获到请求参数进行请求 import execjs import requests node = execjs.get() # Params method = 'GETCITYWEATHER' city = '北京' type = 'HOUR' start_time = '2018-01-25 00:00:00' end_time = '2018-01-25 23:00:00' # Compile javascript file = 'test.js' ctx = node.compile(open(file,encoding='utf-8').read()) # Get params js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time) params = ctx.eval(js) #发起post请求 url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php' response_text = requests.post(url, data={'d': params}).text print(response_text)
对捕获到的加密的响应数据进行解密
#对捕获到的加密的响应数据进行解密 import execjs import requests node = execjs.get() # Params method = 'GETDETAIL' city = '北京' type = 'HOUR' start_time = '2018-01-25 00:00:00' end_time = '2018-01-25 23:00:00' # Compile javascript file = 'test.js' ctx = node.compile(open(file,encoding='utf-8').read()) # Get params js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time) params = ctx.eval(js) #发起post请求 url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php' response_text = requests.post(url, data={'d': params}).text #对加密的响应数据进行解密 js = 'decodeData("{0}")'.format(response_text) decrypted_data = ctx.eval(js) print(decrypted_data)