3 爬虫 模拟登录 异步爬虫

爬虫

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: [email protected]
pwd: test
code: test
denglu: 登录

1.2 识别验证码

超级鹰验证码识别:http://www.chaojiying.com/

超级鹰验证码识别使用流程:

  1. 注册
    自己使用选择用户中心身份进行注册。
  2. 登陆后充值题分
  3. 用户中心-软件ID选项卡中生成一个软件ID
  4. 下载示例代码
    在开发文档中选择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 模拟登录

动态变化的请求参数

  1. 一般隐藏在页面中,例如type=hidden的input标签(本案例);
  2. 也可以由相关的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': '[email protected]',
    '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修饰的特殊函数:

  1. 调用时函数不会立即执行;
  2. 调用后返回一个协程对象。
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
  1. wait方法
    接受的参数是任务列表,可以对任务列表中的每一个任务对象进行可挂起操作。
  2. await关键字
    在异步中确保执行阻塞操作。
  3. 在特殊函数的内部不可以出现不支持异步模块的代码,否则会使整个异步执行失效。
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)

你可能感兴趣的:(爬虫技术,python,异步爬取)