python 批量爬取四级成绩单

使用本文爬取成绩大致有几个步骤:1、提取表格(或其他格式文件——含有姓名,身份证等信息)中的数据,为进行准考证爬取做准备。2、下载准考证文件并提取出准考证和姓名信息。3、根据得到信息进行数据分析和存储。

所有需要的工具库:

import urllib
import requests
import xlrd
import json
import re
import time
import os
import operator

from aip import AipOcr 
from selenium import webdriver 
from PIL import Image

  

  

准考证下载网址:http://cet-bm.neea.edu.cn/Home/QuickPrintTestTicket

使用查分数网址:http://cet.neea.edu.cn/cet/

具体实现:

一、提取姓名身份证信息并获取下载url关键信息

# 创建session访问对象
session = requests.session()
session.headers = {'打开对应网站http://cet-bm.neea.edu.cn/Home/QuickPrintTestTicket,复制cookies,必需!!!'}

# 得到身份证和姓名信息

# 返回一个包含名字和id的列表
def get_name_id():
    xls_data = xlrd.open_workbook(r'你的表格.xls')
    tables = xls_data.sheets()[0]
    for i in range('范围'):
        rows = tables.row_values(i)
        id = rows[7]

        # 对中文进行了url编码处理
        name = urllib.parse.quote(rows[5])
        yield [name, id]

list_info = get_name_id()

# 经过观察发现下载链接中只有一个sid参数有变化,所以我们下面筛选出sid参数并保存


# 需要手动到网站上点一个验证码, 时间大概在一分钟
# 到时后可能会有变动,请根据具体情况修改
yzm = '验证码'

# 存储最终数据
data = []

for i in list_info:

    # 参数provinceCode是省份编码,请根据具体情况及时修改
    # 另参数这里我没有用字典形式传入,所以中文转化成了url形式的
    res = session.post('http://cet-bm.neea.edu.cn/Home/ToQuickPrintTestTicket',
                       data='Name={}&verificationCode={}&provinceCode=34&IDNumber={}&IDTypeCode=1'.format(i[0], yzm, i[1]))

    # 处理数据 以便存储
    txt = res.content.decode('utf8')
    txt = txt.replace('\\', '')
    sid_one = re.findall("SID\":\"(.*?)\",", txt)[0]

    name_ = urllib.parse.unquote(i[0])
    data.append({'name': name_, 'sid': sid_one})

with open("sid.json", 'w') as f:
    f.write(json.dumps(data))

  至此,我们得到了下载地址

二、下载文件并读取

with open('sid.json', 'r') as f:
    sid = json.loads(f.read())

# 下载地址列表
urls = []
for i in sid:
    url = 'http://cet-bm.neea.edu.cn/Home/DownTestTicket?SID={}'.format(i['sid'])
    urls.append([url, i['name']])

for i in urls:
    response = requests.get(i[0])

    # 这里注意保存格式一定要写对!!!
    # 不然是很头疼的一件事
    with open(r'pdf\{}.zip'.format(i[1]), 'wb') as f:
        f.write(response.content)
    print('success')

  这一步我们下载了准考证文件,注意最好创建一个新文件夹保存下载的文件,另外,前一篇写到的批量解压文件又用到了,自己修改一下即可

  下面一段代码我们分析准考证号信息并另存为json文件

# 打开pdf文件并读取,网上很容易找到,这里也只做采用
def open_one_pdf(url):
    # 定义变量接收我们所需的数据
    admission_ticket_number = []
    name = []

    #  文件对象
    pd_file = open(url, 'rb')

    #  pdf文件解析对象
    parser = PDFParser(pd_file)

    # print(parser)
    #  pdf文档对象
    document = PDFDocument()
    parser.set_document(document)
    document.set_parser(parser)

    #  初始化文档密码
    document.initialize()
    if document.is_extractable:
        print(True)
    else:
        raise PDFTextExtractionNotAllowed

    #  存储文档资源
    src = PDFResourceManager()

    #  设备对象
    device = PDFPageAggregator(src, laparams=LAParams())

    #  解释器对象

    inter = PDFPageInterpreter(src, device)

    pages = document.get_pages()

    # 总文本
    str_total = ''

    for page in pages:
        inter.process_page(page)
        layout = device.get_result()
        for x in layout:
            if isinstance(x, LTTextBoxHorizontal):
                str_total += str(x.get_text())


    # 提取所需数据
    admission_ticket_number.append(re.findall('准考证号:(.*?)\n', str_total, re.S)[0])
    name.append(re.findall('姓名:(.*?)\n', str_total, re.S)[0])

    return {'admission_ticket_number': admission_ticket_number, 'name': name}

# 存储所有爬取对象的信息
data_list = []

for file in os.listdir('你的文件目录'):
    if file.endswith('.pdf'):
        # 斜杠别删了,这里没有使用os.path
        data_list.append(open_one_pdf('你的文件目录\' + file))
    else:
        pass

with open('admission_num.json', 'w') as f:
    f.write(json.dumps(data_list))

  到这里,我们得到了准考证信息

三、爬取成绩

# 百度api识别验证码
def deal_yzm():
    """ 你的 APPID AK SK """
    APP_ID = ''
    API_KEY = ''
    SECRET_KEY = ''
    client = AipOcr(APP_ID, API_KEY, SECRET_KEY)

    def get_file_content(filePath):
        with open(filePath, 'rb') as fp:
            return fp.read()

    url = r'yzm.png'
    image = get_file_content(url)

    """ 调用文字识别, 图片参数为本地图片 """
    x = client.basicAccurate(image)
    # 一天500次,一般情况足够使用

    # 设置时间间隔,减少出错率
    time.sleep(1)

    # 筛选 这里的条件是识别出的结果只有一个且长度为4,请自行修改
    if x['words_result_num'] == 1 and len(x['words_result'][0]['words'].replace(' ', '')) == 4:
        return x['words_result'][0]['words'].replace(' ', '')
    else:
        return 'none'


# 保存验证码
# webdriver提取元素的路径可能会有变动,使用前请检查
def save_yzm(img):
    src_url = img.get_attribute('src')
    response = requests.get(src_url)
    with open('yzm.png', 'wb') as f:
        f.write(response.content)

    # 几次试验得到的结果,可能转为灰度更易识别
    I = Image.open('yzm.png')
    L = I.convert('1')
    L.save('yzm.png')


# 保存失败和成功信息的列表
fail = []

while True:

    # 加载准考证和姓名信息
    # 如果有失败记录文件则使用失败记录中的数据
    if os.path.exists('fail.json'):
        with open('fail.json', 'r') as f:
            data = json.loads(f.read())
    else:
        with open('admission_num.json', 'r') as f:
            data = json.loads(f.read())

    # 有分数记录则继续添加
    if os.path.exists('score.json'):
        with open('score.json', 'r') as f:
            score = json.loads(f.read())
    else:
        score = []

    # 遍历列表中的准考证和姓名信息
    for i in data:

        # 创建Chrome对象
        driver = webdriver.Chrome()

        # driver等待时间
        driver.implicitly_wait(3)

        number = i["admission_ticket_number"][0]
        name = i['name'][0]
        driver.get('http://cet.neea.edu.cn/cet/')

        # 获取元素
        btn_id = driver.find_element_by_xpath('//*[@id="zkzh"]')
        btn_name = driver.find_element_by_xpath('//*[@id="name"]')
        btn_yzm = driver.find_element_by_xpath('//*[@id="verify"]')
        btn_submit = driver.find_element_by_xpath('//*[@id="submitButton"]')
        btn_id.send_keys(number)
        btn_name.send_keys(name)
        btn_yzm.click()
        # 点击后验证码出现  

        # 等待加载
        time.sleep(1)
        img = driver.find_element_by_xpath('//*[@id="img_verifys"]')
        # 保存图片 得到验证码
        save_yzm(img)

        # 识别出的字符值
        value = deal_yzm()

        # 处理识别失败的情况,有一类加入了干扰线,不好识别
        # 这里选择刷新重复上传识别
        while value == 'none':
            img_change = driver.find_element_by_xpath('//*[@id="verifysStrDiv"]/a')
            img_change.click()
            time.sleep(1)
            save_yzm(img)
            value = deal_yzm()

        # 发送验证码并点击提交
        btn_yzm.send_keys(value)
        btn_submit.click()

        # 等待
        time.sleep(1)

        # 因为登陆失败会有alert弹窗,driver会有错误提示,会结束程序,所以使用错误处理
        try:
            source = driver.page_source
        except:
            source = ''

        if '找出成绩单中的标志性信息,如学校等' not in source:
            print('验证码获取不正确,请重新执行一次\n 失败id{}已保存至fail.json'.format(i))
            fail.append(i)
            driver.close()
            continue

        # 筛选成绩
        score_one = re.findall('<span id="s">(.*?)</span>', source, re.S)
        print({'name': i['name'][0], 'score': score_one})
        score.append({'name': i['name'][0], 'score': score})

        driver.close()

        
    with open('fail.json', 'w') as f:
        f.write(json.dumps(fail))

    with open('score.json', 'w') as f:
        f.write(json.dumps(score))

    if not fail:
        break

  至此已经做完了数据的爬取,下面进行数据分析:

with open('score.json', 'r')as f:
    data = json.loads(f.read())


sorted_x = sorted(data, key=operator.itemgetter('score'))
flag = 1
for i in sorted_x:
    if i['score'][0] > '425' and flag == 1:
        flag += 1
        print("".center(60, '='))
    print(i['name'] + '\t\t' + i['score'][0])

os.system('pause')

  打印排序后的成绩,在第一个大于425的数后面加一个横线,暂停

 

代码和文章结构可能有些不调理,存在许多不足,使用时请自行修改

 

posted @ 2019-09-01 18:53  lgf133  阅读(691)  评论(0编辑  收藏  举报