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的数后面加一个横线,暂停
代码和文章结构可能有些不调理,存在许多不足,使用时请自行修改