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()
flask搭建的server

协程

   特殊的函数:表示一组指定的操作
    - 如果一个函数的定义被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() 

 

posted @ 2020-07-06 15:29  电竞杰森斯坦森  阅读(367)  评论(0编辑  收藏  举报