3-爬虫-模拟登录、图片验证码处理、多任务异步爬虫(进程池、协程、生产者消费者模式)
今日内容
- 模拟登录
- 异步爬虫
- 线程池
- 单线程+多任务异步协程
- 生产者消费者模式
模拟登录
- 验证码的识别
- 线上的打码平台
- 超级鹰
- url:https://www.chaojiying.com/about.html
- 使用流程:
- 注册:注册一个用户中心的账号
- 登录:用户中心的身份
- 创建一个软件ID: 899370
- 下载示例代码
- 云打码
- 超级鹰
- 线上的打码平台
- 动态变化的请求参数
- 动态变化请求参数的处理
- 一般会隐藏在前台页面中
- 是由相关的js函数动态生成
- 动态变化请求参数的处理
超级鹰的示例代码
#!/usr/bin/env python # coding:utf-8 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 self.base_params = { 'user': self.username, 'pass2': self.password, 'softid': self.soft_id, } 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() if __name__ == '__main__': chaojiying = Chaojiying_Client('超级鹰用户名', '超级鹰用户名的密码', '96001') #用户中心>>软件ID 生成一个替换 96001 im = open('a.jpg', 'rb').read() #本地图片文件路径 来替换 a.jpg 有时WIN系统须要// print chaojiying.PostPic(im, 1902) #1902 验证码类型 官方网站>>价格体系 3.4+版 print 后要加()
识别古诗文网中的验证码图片
import requests from lxml import etree headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36' } def transdform_code_img(img_path,img_type): chaojiying = Chaojiying_Client('超级鹰账号', '密码', '软件ID') #用户中心>>软件ID 生成一个替换 96001 im = open(img_path, 'rb').read() #本地图片文件路径 来替换 a.jpg 有时WIN系统须要// return chaojiying.PostPic(im, img_type)['pic_str'] main_url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx' page_text = requests.get(url=main_url,headers=headers).text #解析验证码图片地址 tree = etree.HTML(page_text) img_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0] img_data = requests.get(url=img_src,headers=headers).content with open('./code.jpg','wb') as fp: fp.write(img_data) transdform_code_img('./code.jpg',1004)
实现模拟登录
sess = requests.Session() main_url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx' page_text = sess.get(url=main_url,headers=headers).text #解析验证码图片地址 tree = etree.HTML(page_text) img_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0] img_data = sess.get(url=img_src,headers=headers).content with open('./code.jpg','wb') as fp: fp.write(img_data) __VIEWSTATE = tree.xpath('//*[@id="__VIEWSTATE"]/@value')[0] __VIEWSTATEGENERATOR = tree.xpath('//*[@id="__VIEWSTATEGENERATOR"]')[0] code_text = transdform_code_img('./code.jpg',1004) print(code_text) 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': '账号', 'pwd': '密码', 'code': code_text, 'denglu': '登录', } login_page_text = sess.post(url=login_url,headers=headers,data=data).text with open('./login.html','w',encoding='utf-8') as fp: fp.write(login_page_text)
异步爬虫
线程池
import requests import time from multiprocessing.dummy import Pool # 线程池 from lxml import etree # 异步爬虫 start = time.time() def get_request(url): page_text = requests.get(url).text return page_text def parse(page_text): # 数据解析 tree = etree.HTML(page_text) a_href = tree.xpath('//a[@id="feng"]/@href')[0] print(a_href) urls = ['http://127.0.0.1:5000/jay', 'http://127.0.0.1:5000/bobo', 'http://127.0.0.1:5000/tom'] pool = Pool(3) # 使用get_request将urls列表中每一个列表元素进行指定操作 page_text_list = pool.map(get_request, urls) # 异步请求,返回结果也是一个列表 pool.map(parse, page_text_list) # 异步解析 print('总耗时:', time.time() - start)
flask搭建的server
#!/usr/bin/env python # -*- coding:utf-8 -*- from flask import Flask, render_template from time import sleep # 安装flask模块 # 1.实例化app对象 app = Flask(__name__) @app.route('/main') def main(): return 'i am main' @app.route('/bobo') def index1(): sleep(2) return render_template('test.html') @app.route('/jay') def index2(): sleep(2) return render_template('test.html') @app.route('/tom') def index3(): sleep(2) return render_template('test.html') if __name__ == "__main__": app.run()
协程
特殊的函数:表示一组指定的操作
- 如果一个函数的定义被async修饰了,则该函数就变成了一个特殊的函数
- 特殊之处:
- 1.刚该特殊函数被调用后,函数内部的语句不会被立即执行
- 2.该函数调用后会返回一个协程对象协程
- 协程对象。可以由特殊函数调用获得。
- 一组指定操作 = 特殊函数 = 协程对象
- 协程对象 = 一组指定操作
任务
- 就是高级的协程对象。任务对象就是对协程进行了进一步封装。
- 任务对象 = 协程 = 特殊函数 = 一组指定操作
- 任务对象 = 一组指定的操作
- 任务对象可以给它绑定一个回调
事件循环
- 可以当做是一个容器对象。该容器是用来装载任务对象。
- 当我们有了一个或多个任务对象,可以将任务对象注册/装载到事件循环对象中
- 当事件循环对象启动后,就可以异步的执行其内部装载的任务对象。
import asyncio from time import sleep import time # 特殊函数,前边加了async就是特殊函数 async def get_request(url): print('正在请求:', url) sleep(2) print('请求结束:', url) return 123 def parse(task): #必须要有一个参数,就是回调函数的调用者(任务对象) print('i am parse,参数task=',task) print(task.result())#特殊函数return的返回值 # 特殊函数执行的到协程对象 c = get_request("www.1.com") # 写成对象进一步封装得到任务对象 task = asyncio.ensure_future(c) # 单独的任务对象也可以执行,但如果任务对象想要异步执行就要把它注册到事件循环对象中执行 #给任务对象绑定回调函数 task.add_done_callback(parse) # 创建事件循环对象 loop = asyncio.get_event_loop() # 将任务对象注册到事件循环对象中,并且启动事件循环对象 loop.run_until_complete(task)
多任务
注意:在特殊函数实现内部不可以出现不支持异步的模块代码,否则会中断整个异步效果
- wait(tasks):
- 该方法的参数一定得是一个任务列表。可以将任务列表中的每一个任务对象进行可挂起操作,这样当任务对象想要被挂起就才可以挂起
- 挂起:挂起任务对象就是说让当前的任务对象交出cpu的使用权
- await关键字
- 在异步中确保阻塞操作被执行
import asyncio from time import sleep import time start = time.time() #特殊的函数 #注意:在特殊函数实现内部不可以出现不支持异步的模块代码,否则会中断整个异步效果 async def get_request(url): print('正在请求:',url) await asyncio.sleep(2) # await等待阻塞操作执行 print('请求结束:',url) return url urls = ['www.1.com','www.2.com','www.3.com'] tasks = [] #任务列表 for url in urls: #返回三个协程对象 c = get_request(url) #返回三个任务对象 task = asyncio.ensure_future(c) tasks.append(task) loop = asyncio.get_event_loop() #wait()? loop.run_until_complete(asyncio.wait(tasks)) print('总耗时:',time.time()-start)
aiohttp:支持异步的网络请求模块
安装aiohttp模块:pip install aiohttp
aiohttp编码流程
# 1.写出一个大致架构 async def get_request(url): with aiohttp.ClientSession() as s: with s.get(url) as response: page_text = response.text() return page_text # 2.补充细节: # 在所有with前加上async关键字 # 在相关阻塞操作前加上await关键字 async def get_request(url): async with aiohttp.ClientSession() as s: async with await s.get(url) as response: # 发起请求返回响应对象 page_text = await response.text() return page_text
多任务异步爬虫
import asyncio from time import sleep import time import requests import aiohttp from lxml import etree start = time.time() # 特殊的函数 # 注意:在特殊函数实现内部不可以出现不支持异步的模块代码,否则会中断整个异步效果 # async def get_request(url): # #reqeusts是不支持异步 # page_text = requests.get(url).text # return page_text def parse(task): # 数据解析 page_text = task.result() tree = etree.HTML(page_text) print(tree.xpath('//img/@src')) async def get_request(url): # 创建了一个请求对象 async with aiohttp.ClientSession() as s: # requests:get(url,params,headers,proxies) # aiohttp.get(url,params,headers,proxy='https://ip:port') async with await s.get(url) as response: # 发起请求返回响应对象 # 获取响应数据 # text()返回字符串形式响应数据 # read()返回byte类型的响应数据 page_text = await response.text() return page_text urls = ['http://127.0.0.1:5000/jay', 'http://127.0.0.1:5000/bobo', 'http://127.0.0.1:5000/tom'] 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)
生产者消费者模式
import threading import requests from lxml import etree import os from urllib import request from queue import Queue class Producer(threading.Thread): headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36", } def __init__(self, page_queue, img_queue, *args, **kwargs): super(Producer, self).__init__(*args, **kwargs) self.page_queue = page_queue self.img_queue = img_queue def run(self): while True: if self.page_queue.empty(): break url = self.page_queue.get() self.parse_page(url) def parse_page(self, url): response = requests.get(url=url,headers=self.headers) text = response.text html = etree.HTML(text) img_list = html.xpath('//div[@class="page-content text-center"]/div/a/img') for img in img_list: img_url = img.xpath('./@data-original')[0] img_name = img.xpath('./@alt')[0]+'.jpg' self.img_queue.put((img_url, img_name)) class Consumer(threading.Thread): def __init__(self, page_queue, img_queue, *args, **kwargs): super(Consumer, self).__init__(*args, **kwargs) self.page_queue = page_queue self.img_queue = img_queue def run(self): while True: if self.page_queue.empty() and self.img_queue.empty(): break img_url, img_name = self.img_queue.get() request.urlretrieve(img_url, "imgs/" + img_name) print(img_name + " 下载完成!") # 定义一个主方法,该方法向处理方法中传值 def main(): page_queue = Queue(50) #存储页码链接 img_queue = Queue(100)#存储解析出来的图片链接 #想要爬取前10也的数据 for x in range(1, 11): url = "https://www.doutula.com/photo/list/?page=%d" % x page_queue.put(url) #将10页的页码链接加入到了page_queue for x in range(3): t = Producer(page_queue, img_queue) t.start() for x in range(3): t = Consumer(page_queue, img_queue) t.start() if __name__ == '__main__': main()