需求
需求分析
部门老大 要求使用爬虫抓取对应网站的公司账号的余额 并且能提供一个API供公司内部其他公示网站调用 原本商议每次请求都用爬虫请求一次,但是最好出于账号安全和系统稳定性考虑决定设置一个定时任务 每两个小时抓取一次 数据存放到缓存当中 但是也需要提供接口
1、登录
第一个难点 需要登录 我首先想到的是逆向,抓包查看逆向登录,开发过程中途也确实是快实现了 但是奈何这个网站的参数太多而且因为整个网站采取的IFRAME标签加载的H5如果要使用requests+逆向个人感觉很麻烦
而且访问IFRAME内部的H5时又会出现登录
这直接给我淦懵逼了 难道我还得又逆向一下这个登录? (我带上原本的请求头无法访问到登录之后的界面,具体原因没有深究)
看到这儿我直接就放弃了逆向 直接使用傻瓜包采用无头浏览器的方式获取页面
为什么选择pyppeteer
至于为什么使用pyppeteer而不是selenium 原因是因为爬虫项目需要放到centos上面的啊 selenium 需要按照浏览器 并且下载对应版本的webdriver就这些工作量我直接选择放弃 转头用pyppeteer (当然也有另一个原因 我不确定服务器上能否安装chrome)
安装pyppeteer
建议直接pip安装
pip install pyppeteer
当然不出意料的下载速度很慢 建议换源
数据抓取
爬虫
登录
思路:
1、抓包查看请求接口
2、使用pyppeteer请求该接口
3、等待页面渲染完毕
4、定位账号密码的输入框 并且模拟输入账号密码
5、定位登录按钮 模拟点击登录
await page.type(
'body > uni-app > uni-layout > uni-content > uni-main > uni-page > uni-page-wrapper > uni-page-body > uni-view > uni-view.login-box-tab > uni-view.uni-container > uni-view.uni-forms.uni-forms--top > uni-form > span > uni-view:nth-child(1) > uni-view > uni-view > uni-view.uni-forms-item__content > uni-input > div > input',
'你的账号', {'delay': 10}) # 模拟输入,输入时间:10 ms
# 输入密码
await page.type(
'body > uni-app > uni-layout > uni-content > uni-main > uni-page > uni-page-wrapper > uni-page-body > uni-view > uni-view.login-box-tab > uni-view.uni-container > uni-view.uni-forms.uni-forms--top > uni-form > span > uni-view.uni-forms-item.icon-container > uni-view > uni-view > uni-view.uni-forms-item__content > uni-input > div > input',
'你的密码', {'delay': 10})
# 模拟点击
await page.click(
'body > uni-app > uni-layout > uni-content > uni-main > uni-page > uni-page-wrapper > uni-page-body > uni-view > uni-view.login-box-tab > uni-view.uni-container > uni-view.uni-forms.uni-forms--top > uni-form > span > uni-view.uni-button-group > uni-button') # 模拟点击,也可以先定位元素,然后await element.click()
await asyncio.sleep(4)
print('登录成功!')
# 登录成功之后需要跳转到基础配置界面
await page.goto('https://dev.dcloud.net.cn/pages/uniLogin/index')
await asyncio.sleep(2)
print('正在跳转界面')
获取对应页面
思路:
1、抓包查看请求接口
2、使用pyppeteer请求该接口
3、等待页面渲染完毕
4、获取到iframe加载的请求接口
5、请求iframe的请求路径 获取到H5
6、数据获取
html_iframe_str = await page.content()
html_iframe = etree.HTML(html_iframe_str)
iframe_url_list = html_iframe.xpath('//iframe/@src')
if iframe_url_list:
await page.goto(iframe_url_list[0])
html_str = await page.content()
html = etree.HTML(html_str)
balances = html.xpath('//span[@class="text-pre"]/text()')
if balances:
return balances[0]
else:
return None
else:
print(html_iframe)
return None
其他组件
判断登录是否失效
await page.goto('https://dev.dcloud.net.cn/pages/uniLogin/index')
await asyncio.sleep(3)
title = await page.title()
if title == '登录':
return 0, page
elif title == '基础配置':
return 1, page
else:
return 2, page
初始化
browser = await launch(
{'headless': True, 'args': ['--disable-infobars', '--no-sandbox'], 'userDataDir': r'.\test',
'ignoreDefaultArgs': ['--enable-automation']})
page = await browser.newPage()
await page.evaluateOnNewDocument('() =>{ Object.defineProperties(navigator,'
'{ webdriver:{ get: () => false } }) }')
await page.evaluate('''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }''')
await page.setUserAgent(
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5")
await page.setViewport(viewport={'width': 1536, 'height': 768})
return page, browser
析构函数
await browser.close()
print('成功退出!')
启动函数
loop = asyncio.get_event_loop()
task1 = loop.create_task(run())
task1.add_done_callback(partial(callback))
loop.run_until_complete(task1)
result = task1.result()
data = {
"data": {
"account_id": "{}".format(result.get('balance', None))
}
}
print(data)
return data
完整代码
import asyncio
from pyppeteer import launch
from lxml import etree
from functools import partial
import redis, json, datetime
# 初始化
async def init_func():
browser = await launch(
{'headless': False, 'args': ['--disable-infobars', '--no-sandbox'], 'userDataDir': r'.\test',
'ignoreDefaultArgs': ['--enable-automation']})
page = await browser.newPage()
await page.evaluateOnNewDocument('() =>{ Object.defineProperties(navigator,'
'{ webdriver:{ get: () => false } }) }')
await page.evaluate('''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }''')
await page.setUserAgent(
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5")
await page.setViewport(viewport={'width': 1536, 'height': 768})
return page, browser
# 析构
async def del_func(browser):
await browser.close()
print('成功退出!')
# 登录
async def login(page):
# 输入账号
await page.type(
'body > uni-app > uni-layout > uni-content > uni-main > uni-page > uni-page-wrapper > uni-page-body > uni-view > uni-view.login-box-tab > uni-view.uni-container > uni-view.uni-forms.uni-forms--top > uni-form > span > uni-view:nth-child(1) > uni-view > uni-view > uni-view.uni-forms-item__content > uni-input > div > input',
'账号', {'delay': 10}) # 模拟输入,输入时间:10 ms
# 输入密码
await page.type(
'body > uni-app > uni-layout > uni-content > uni-main > uni-page > uni-page-wrapper > uni-page-body > uni-view > uni-view.login-box-tab > uni-view.uni-container > uni-view.uni-forms.uni-forms--top > uni-form > span > uni-view.uni-forms-item.icon-container > uni-view > uni-view > uni-view.uni-forms-item__content > uni-input > div > input',
'密码', {'delay': 10})
# 模拟点击
await page.click(
'body > uni-app > uni-layout > uni-content > uni-main > uni-page > uni-page-wrapper > uni-page-body > uni-view > uni-view.login-box-tab > uni-view.uni-container > uni-view.uni-forms.uni-forms--top > uni-form > span > uni-view.uni-button-group > uni-button') # 模拟点击,也可以先定位元素,然后await element.click()
await asyncio.sleep(8)
print('登录成功!')
# 登录成功之后需要跳转到基础配置界面
await page.goto('https://dev.dcloud.net.cn/pages/uniLogin/index')
await asyncio.sleep(8)
print('正在跳转界面')
return page
# 查看余额
async def get_balance(page):
html_iframe_str = await page.content()
html_iframe = etree.HTML(html_iframe_str)
iframe_url_list = html_iframe.xpath('//iframe/@src')
if iframe_url_list:
await page.goto(iframe_url_list[0])
html_str = await page.content()
html = etree.HTML(html_str)
balances = html.xpath('//span[@class="text-pre"]/text()')
if balances:
return balances[0]
else:
return None
else:
print(html_iframe)
return None
# 判断登录是否失效
async def judge_cookie(page):
await page.goto('https://dev.dcloud.net.cn/pages/uniLogin/index')
await asyncio.sleep(8)
title = await page.title()
if title == '登录':
return 0, page
elif title == '基础配置':
return 1, page
else:
return 2, page
# 回调
def callback(data: dict):
return data
# 启动
async def run():
page, browser = await init_func()
statu_code, page = await judge_cookie(page)
if statu_code == 0:
# 重新登录
page = await login(page)
balance = await get_balance(page)
elif statu_code == 1:
# 直接获取文本
balance = await get_balance(page)
else:
# 页面更改 需要改代码
balance = '页面更改 需要更改对应脚本'
await del_func(browser)
data = {
'code': statu_code,
'balance': balance,
}
print(data)
return data
# 外部调用
def run_s():
loop = asyncio.get_event_loop()
task1 = loop.create_task(run())
task1.add_done_callback(partial(callback))
loop.run_until_complete(task1)
result = task1.result()
data = {
"data": {
"account_id": "{}".format(result.get('balance', None)),
'date': str(datetime.datetime.now())
}
}
print(data)
pool = redis.ConnectionPool(host='redis主机IP', port=端口, password='密码', db=库)
server = redis.Redis(connection_pool=pool)
server.delete('balance') # 删除以前的队列
server.lpush('balance', json.dumps(data)) # 创建本地队列
return data
if __name__ == '__main__':
run_s()
API
@app.route('/dcloud1/')
def dcloud_get_balance():
redis_pool = REDISPOOL(queue_name='balance')
result = redis_pool.get_test(index=1)
print(result)
if result:
return result
else:
return 'redis为空'
直接在redis中取数据
定时任务
直接使用crontab发布定时任务 完成更新
crontab-e
小结
1、最好还是麻烦一点用逆向至少稳定 性能也应该比较好
2、现在这一版比较low 因为要得比较急 没有花时间去仔细弄
3、pyppeteer可以直接在centos中使用
4、pyppeteer支持异步 在高并发上应该比selenium更加的优秀