爬虫
1 模拟登录
1.1 模拟登录古诗文网
古诗文网登陆页面:https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx
点击登录按钮的请求参数
Request URL: https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx
Request Method: POST
Form Data
__VIEWSTATE: hjj3lgSg...
__VIEWSTATEGENERATOR: C93...
from: http://so.gushiwen.org/user/collect.aspx
email: test@test.com
pwd: test
code: test
denglu: 登录
1.2 识别验证码
超级鹰验证码识别:http://www.chaojiying.com/
超级鹰验证码识别使用流程:
- 注册
自己使用选择用户中心身份进行注册。 - 登陆后充值题分
- 用户中心-软件ID选项卡中生成一个软件ID
- 下载示例代码
在开发文档中选择Python语言,进入Python语言Demo下载页面进行下载。
关键代码
chaojiying = Chaojiying_Client('超级鹰用户名', '超级鹰用户密码', '软件ID')
im = open('code.jpg', 'rb').read() # 本地验证码图片路径
print(chaojiying.PostPic(im, 1902))
参数1902表示验证码由4~6位英文数字组成,参考价格体系页面。
def get_verification_code(img_path, code_type):
chaojiying = Chaojiying_Client('超级鹰用户名', '超级鹰用户密码', '软件ID')
im = open(img_path, 'rb').read() # 本地验证码图片路径
return chaojiying.PostPic(im, code_type)['pic_str']
1.3 识别登录页面中的验证码
import requests
from lxml import etree
headers = {
'User-Agent': 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
}
login_url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx'
login_text = requests.get(url=login_url, headers=headers).text
# 获取验证码图片
tree_obj = etree.HTML(login_text)
img_src = tree_obj.xpath('//*[@id="imgCode"]/@src')[0]
img_url = 'https://so.gushiwen.org/{src}'.format(src=img_src)
img_data = requests.get(url=img_url, headers=headers).content
img_path = './verification_code.jpg'
with open(img_path, 'wb') as fp:
fp.write(img_data)
# 识别验证码图片
verification_code = get_verification_code(img_path, 1004) # 1004: 1~4位英文数字
1.4 模拟登录
动态变化的请求参数
- 一般隐藏在页面中,例如type=hidden的input标签(本案例);
- 也可以由相关的js函数动态生成。
session_obj = requests.Session()
# 登陆页面
main_url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx'
login_text = session_obj.get(url=main_url, headers=headers).text
# 获取验证码
tree_obj = etree.HTML(page_text)
img_url = 'https://so.gushiwen.org' + tree_obj.xpath('//*[@id="imgCode"]/@src')[0]
img_data = session_obj.get(url=img_url, headers=headers).content
with open('./code.jpg', 'wb') as fp:
fp.write(img_data)
code_text = transdform_code_img('./code.jpg', 1004)
print(code_text) # 验证码
# 获取动态变化的请求参数
__VIEWSTATE = tree_obj.xpath('//*[@id="__VIEWSTATE"]/@value')[0]
__VIEWSTATEGENERATOR = tree_obj.xpath('//*[@id="__VIEWSTATEGENERATOR"]')[0]
# 模拟登录
login_url = 'https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx'
data = {
'__VIEWSTATE': __VIEWSTATE,
'__VIEWSTATEGENERATOR': __VIEWSTATEGENERATOR,
'from': 'http://so.gushiwen.org/user/collect.aspx',
'email': 'test@test.com',
'pwd': 'test',
'code': code_text,
'denglu': '登录',
}
login_page_text = session_obj.post(url=login_url, headers=headers, data=data).text
with open('./login.html', 'w', encoding='utf-8') as fp:
fp.write(login_page_text)
2 异步爬虫
2.1 线程池
2.1.1 同步
import time
def get_request(url):
print('正在请求url:' + url)
time.sleep(2)
print('请求结束' + url)
url_list = ['www.test1.com', 'www.test2.com', 'www.test3.com']
start_time = time.time()
for each_url in url_list:
get_request(each_url)
stop_time = time.time()
print('耗时: ', stop_time - start_time) # ~6s
2.1.2 使用线程池进行异步处理
import time
from multiprocessing.dummy import Pool # 线程池
def get_request(url):
print('正在请求url:' + url)
time.sleep(2)
print('请求结束' + url)
url_list = ['www.test1.com', 'www.test2.com', 'www.test3.com']
pool_obj = Pool(3) # 在线程池中开启3个线程
start_time = time.time()
# map方法将列表url_list中的每一个元素依次传给函数get_request进行异步处理。
pool_obj.map(get_request, url_list)
stop_time = time.time()
print('耗时: ', stop_time - start_time) # ~2s
2.2 异步爬虫
2.2.1 使用Flask搭建简易服务器
from flask import Flask, render_template
from time import sleep
# 实例化app对象
app = Flask(__name__)
@app.route('/main')
def main():
return 'I am main.'
@app.route('/test1')
def index1():
sleep(2)
return render_template('test.html')
@app.route('/test2')
def index2():
sleep(2)
return render_template('test.html')
@app.route('/test3')
def index3():
sleep(2)
return render_template('test.html')
if __name__ == "__main__":
app.run()
2.2.2 实现异步爬虫
def get_request(url):
page_text = requests.get(url).text
return page_text
def parse(page_text):
tree_obj = etree.HTML(page_text)
a_href = tree_obj.xpath('//a[@id="feng"]/@href')[0]
print(a_href)
url_list = ['http://127.0.0.1:5000/test1', 'http://127.0.0.1:5000/test2', 'http://127.0.0.1:5000/test3']
pool = Pool(3)
# 异步数据请求
page_text_list = pool.map(get_request, url_list)
# 异步数据解析
pool.map(parse, page_text_list)
2.3 协程
2.3.1 特殊的函数
如果某个函数的定义被async修饰,则该函数变成了特殊的函数。
import asyncio
import time
# 特殊的函数
async def get_request(url):
print('正在请求:', url)
time.sleep(2)
print('请求结束:', url)
return url
被async修饰的特殊函数:
- 调用时函数不会立即执行;
- 调用后返回一个协程对象。
2.3.2 协程对象与任务对象
协程对象,由被async修饰的特殊函数调用后产生。
任务对象, 可以理解为高级的协程对象,是对协程对象进行的进一步封装。
任务对象可以绑定回调函数,而协程对象不能。
创建任务对象
import asyncio
import time
# 特殊的函数
async def get_request(url):
print('正在请求:', url)
time.sleep(2)
print('请求结束:', url)
return '特殊函数get_request的返回值'
# 为任务对象绑定的回调函数
def parse(task): # 必须要有一个参数,指向回调函数的调用者,即任务对象。
print('参数task: ', task)
print(task.result()) # 特殊函数get_request的返回值
# 协程对象
coroutine_obj = get_request("www.test1.com")
# 任务对象
task_obj = asyncio.ensure_future(coroutine_obj)
# 为任务对象绑定回调函数
task_obj.add_done_callback(parse)
2.3.3 事件循环
事件循环可以理解为一个容器,用于装载任务对象。
可以将一个/多个任务对象注册/装载到事件循环对象中,当事件循环对象启动后,可以异步地执行内部装载的任务对象。
import asyncio
import time
async def get_request(url):
print('正在请求:', url)
time.sleep(2)
print('请求结束:', url)
return '特殊函数get_request的返回值'
def parse(task):
print('参数task: ', task)
print(task.result()) # 特殊函数的返回值
coroutine_obj = get_request("www.test1.com")
task_obj = asyncio.ensure_future(coroutine_obj)
task_obj.add_done_callback(parse) # 回调函数
# 创建事件循环对象
loop_obj = asyncio.get_event_loop()
# 将任务对象注册到事件循环对象中,并启动事件循环对象
loop_obj.run_until_complete(task_obj)
2.3.4 多任务
import asyncio
import time
# 特殊的函数
# 注意:在特殊函数的内部不可以出现不支持异步模块的代码,否则会使整个异步执行失效。
async def get_request(url):
print('正在请求:', url)
await asyncio.sleep(2)
# 此处不可以使用time.sleep(),因为不支持异步模块。
print('请求结束:', url)
return url
start_time = time.time()
url_list = ['www.test1.com', 'www.test2.com', 'www.test3.com']
task_list = [] # 任务列表
for each_url in url_list:
# 返回三个协程对象
coroutine_obj = get_request(each_url)
# 返回三个任务对象
task_obj = asyncio.ensure_future(coroutine_obj)
task_list.append(task_obj)
loop_obj = asyncio.get_event_loop()
loop_obj.run_until_complete(asyncio.wait(task_list))
print('总耗时: ', time.time() - start_time) # ~2s
- wait方法
接受的参数是任务列表,可以对任务列表中的每一个任务对象进行可挂起操作。 - await关键字
在异步中确保执行阻塞操作。 - 在特殊函数的内部不可以出现不支持异步模块的代码,否则会使整个异步执行失效。
2.3.5 多任务异步爬虫
requests模块不支持异步,可以使用aiohttp模块。
aiohttp模块是支持异步的网络请求模块。
import asyncio
import aiohttp
import time
from lxml import etree
def parse(task):
page_text = task.result()
tree = etree.HTML(page_text)
print(tree.xpath('//img/@src'))
# 细节
# 1. with前添加async
# 2. 阻塞操作前添加await
async def get_request(url):
# 创建请求对象
async with aiohttp.ClientSession() as request_obj:
# 发起请求,返回响应对象
# 与requests的区别: proxies(接受字典参数),proxy(接受字符串参数)
# requests.get(url, headers, params, proxies={'https': 'ip:port'})
# request_obj.get(url, headers, params, proxy='https://ip:port')
async with await request_obj.get(url) as response_obj:
# 获取响应数据
# text(): 返回字符串形式响应数据
# read(): 返回bytes类型的响应数据
page_text = await response_obj.text()
return page_text
start_time = time.time()
url_list = ['http://127.0.0.1:5000/test1', 'http://127.0.0.1:5000/test2', 'http://127.0.0.1:5000/test3']
task_list = []
for each_url in url_list:
coroutine_obj = get_request(each_url)
task_obj = asyncio.ensure_future(coroutine_obj)
task_obj.add_done_callback(parse) # 使用回调函数处理数据解析
task_list.append(task_obj)
loop_obj = asyncio.get_event_loop()
loop_obj.run_until_complete(asyncio.wait(task_list))
print('总耗时:', time.time() - start_time)