爬取流程
其实在研一的时候就已经做过一次,但是吧,技术不熟练好多东西都可以优化一下,由于今年一节课也没有,实习也找不到,算了优化一下代码顺便听课去。
第一步:输入学号密码登入网站进入教务处管理系统分析界面并导包
分析界面
由于我们只需要一次性获取这些东西,cookie之类的东西我们即使更新就好,不去获取了,用现成的就好。
简单分析,我们需要红框里面的东西去推出蓝框里面的东西,然后再由这四个框去获得列表,那么我们接下来肯定是去获取红框里面的东西去得到篮框里面的东西,蓝框里面的东西就是专业代码了。看请求也是这样。
导包
所需要的包如下:
import pandas as pd
from tqdm import tqdm
from lxml import etree
import aiohttp
import asyncio
import nest_asyncio
import json
import numpy as np
nest_asyncio.apply()
第二步:获取培养层次,年级,院(系)/部 并获取专业代码
打开开发者模式直接查看源代码,内容直接摆在这里了,直接清洗就完事。
代码如下,过于简单。
def get_key_val(content):
html = etree.HTML(content)
level_1_val = html.xpath('//option/@value')
level_1_key = html.xpath('//option/text()')
level_1_val = [val for val in level_1_val if val!='']
return pd.DataFrame([level_1_key, level_1_val], index=['key', 'val']).T
s_1 = '''<select id="sel_pycc" name="sel_pycc" style="width:80px" οnchange="var vpn_return;eval(vpn_rewrite_js((function () { doChangeSelzy(); }).toString().slice(14, -2), 2));return vpn_return;">undefined<option value="1">本科</option><option value="2" selected="selected">硕士</option><option value="3">博士</option><option value="4">夜大</option><option value="5">第二学士学位</option><option value="6">预科</option><option value="9">其它</option></select>'''
s_2 = '''<select id="sel_nj" name="sel_nj" style="width:80px;" οnchange="var vpn_return;eval(vpn_rewrite_js((function () { doChangeSelzy(); }).toString().slice(14, -2), 2));return vpn_return;"><option value=""></option>undefined<option value="2020">2020</option><option value="2021">2021</option><option value="2022" selected="selected">2022</option><option value="2023">2023</option></select>'''
s_3 = '''<select id="sel_yxb" name="sel_yxb" style="width:180px;" οnchange="var vpn_return;eval(vpn_rewrite_js((function () { doChangeSelzy(); }).toString().slice(14, -2), 2));return vpn_return;"><option value=""></option>undefined<option value="BG">北京师范大学昌平校区</option><option value="9I">北京师范大学珠海校区</option><option value="CO">本科旁听生</option><option value="38">本科生工作处(武装部)</option><option value="BF">党委学生工作部</option><option value="AP">地理科学学部</option><option value="08">法学院</option><option value="99">辅修(辅修学士学位)</option><option value="00">公共资源服务中心</option><option value="50">国际交流与合作处(港澳台事务办公室)</option><option value="60">国际中文教育学院</option><option value="C9">国家安全与应急管理学院</option><option value="9K">国民核算研究院</option><option value="9R">瀚德学院</option><option value="89">核科学与技术学院</option><option value="CI">弘文书院</option><option value="09">化学学院</option><option value="20">环境学院</option><option value="C2">会同书院</option><option value="43">继续教育与教师培训学院</option><option value="26">减灾与应急管理研究院</option><option value="B9">教务部(研究生院)</option><option value="BB">教务部(研院)培养办公室</option><option value="61">教务处</option><option value="9W">教育部高校辅导员培训和研修基地(大学生发展研究中心)</option><option value="88">教育学部</option><option value="02">经济与工商管理学院</option><option value="82">经济与资源管理研究院</option><option value="C1">乐育书院</option><option value="03">历史学院</option><option value="9J">励耘学院</option><option value="23">马克思主义学院</option><option value="83">脑与认知科学研究院</option><option value="92">全球变化与地球系统科学研究院</option><option value="BQ">人工智能学院</option><option value="9N">人文宗教高等研究院</option><option value="71">社会发展与公共政策学院</option><option value="AH">社会学院</option><option value="10">生命科学学院</option><option value="11">数学科学学院</option><option value="84">水科学研究院</option><option value="12">体育与运动学院</option><option value="13">天文系</option><option value="AF" selected="selected">统计学院</option><option value="27">图书馆</option><option value="04">外国语言文学学院</option><option value="CC">湾区国际商学院</option><option value="BP">未来教育学院</option><option value="C8">未来设计学院</option><option value="9O">文化创新与传播研究院</option><option value="C4">文理学院</option><option value="07">文学院</option><option value="14">物理学系</option><option value="9Q">系统科学学院</option><option value="57">校团委</option><option value="AQ">心理学部</option><option value="15">心理学院</option><option value="AG">新闻传播学院</option><option value="AC">新兴市场研究院</option><option value="16">信息科学与技术学院</option><option value="85">刑事法律科学研究院</option><option value="31">学生就业与创业指导中心</option><option value="28">学生心理咨询与服务中心</option><option value="24">研究生院</option><option value="AB">研究生院珠海分院</option><option value="BM">“一带一路”学院</option><option value="05">艺术与传媒学院</option><option value="06">哲学学院</option><option value="9P">政府管理学院</option><option value="C3">知行书院</option><option value="9Y">中国基础教育质量监测协同创新中心</option><option value="BS">中国教育与社会发展研究院</option><option value="AE">中国易学文化研究院</option><option value="CR">中华文化研究院/京师书院</option></select>'''
df_1 = get_key_val(s_1)
df_2 = get_key_val(s_2)
df_3 = get_key_val(s_3)
df = df_1.assign(temp=1).merge(df_2.assign(temp=1), how='outer', on='temp').drop('temp', axis=1)
df = df.assign(temp=1).merge(df_3.assign(temp=1), how='outer', on='temp').drop('temp', axis=1)
df.columns = ['key_1', 'val_1', 'key_2', 'val_2', 'key_3', 'val_3']
得到结果如下:
接下来获取蓝框部分代码也就是 key_4 和 val_4 , 直接解析请求发送就好了,如图所示,三个val一带进去直接出结果,继续弄个笛卡儿积吧。
大概有2072个请求,一个一个请求这破教务处服务器慢得要死,弄个异步;代码如下:
lst = list(df.to_dict('index').values())
lst[:1]
class GetData:
def __init__(self, lst_dic, cookies, headers):
self.que = asyncio.Queue()
[self.que.put_nowait(dic) for dic in lst_dic]
self.cookies = cookies
self.headers = headers
self.result = {}
self.eventloop()
async def scrape_url(self, session, dic):
url = 'https://onevpn.bnu.edu.cn/http/77726476706e69737468656265737421eaee478b69326645300d8db9d6562d/frame/droplist/getDropLists.action?vpn-12-o1-zyfw.bnu.edu.cn'
data = {
'comboBoxName': 'MsGrade_PYCC_Specialty',
'paramValue': f"nj={dic['val_2']}&pycc={dic['val_1']}&dwh={dic['val_3']}",
'isYXB': '0',
'isCDDW': '0',
'isPYCC': '0',
'isPYCCNJ': '0',
'zyPyccFiled': '',
'isKBLB': '0',
'isXQ': '0',
'isBJ': '0',
'isSXJD': '0',
'isDJKSLB': '0',
}
async with session.post(url=url, data=data) as response:
if response.status == 200:
content = await response.text()
self.result[dic['index']] = content
async def main(self):
pbar = tqdm(total=self.que.qsize())
while True:
if self.que.empty():
print('任务完成!')
break
else:
dic = await self.que.get()
async with aiohttp.ClientSession(headers=self.headers, cookies=self.cookies) as session:
await self.scrape_url(session, dic)
pbar.update(1)
def eventloop(self):
loop = asyncio.get_event_loop()
loop.run_until_complete(self.main())
cookies = {
'wengine_vpn_ticketonevpn_bnu_edu_cn': 'asdasda8ef47665ab54',
'show_vpn': '0',
'heartbeat': '1',
'show_faq': '0',
'refresh': '1',
}
headers = {
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'https://onevpn.bnu.edu.cn',
'Referer': 'https://onevpn.bnu.edu.cn/http/77726476706e69737468656265737421eaee478b69326645300d8db9d6562d/student/wsxk.kcbcx10319.html?menucode=JW130417',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69',
'sec-ch-ua': '"Chromium";v="116", "Not)A;Brand";v="24", "Microsoft Edge";v="116"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}
data = GetData(lst, cookies=cookies, headers=headers)
df_4 = pd.DataFrame([[key, val] for key, val in data.result.items()], columns=['index', 'content'])
df_4['content'] = df_4['content'].map(lambda x: json.loads(x) if x!='[]' else np.nan)
df_4 = df_4.explode('content')
df = df.merge(df_4, on='index').set_index('index')
df = df.dropna()
def apply_row(row):
row['key_4'] = row['content']['name']
row['val_4'] = row['content']['code']
del row['content']
return row
df = df.apply(apply_row, axis=1)
df.to_excel('专业列表.xlsx', index=False)
得到结果:
一共4102个专业,接下来获取课表。
第三步:通过四个参数值获取课表
测试请求,获取所需要的参数,参数如下:
参数分析:
- xh, xn, xq, nj, pycc, dwh都是需要的,一些为空值的parameters都是不需要的
- self_pycc, self_nj, self_yxb, self_zydm 对应上面的val_1, val_2, val_3, val_4
- xnxq 表示学年和学期 中间以,分割 0表示第一学期,1表示第二学期,今年是2023年第一学期
总结,我们只需要修改self_pycc, self_nj, self_yxb, self_zydm, xnxq就好了。
先测试请求,获取单个页面成功,清洗得到如下结果:
下一步获取所有的课表,还是使用的异步方法,但是服务器可能限制了,还是需要5,6个小时才能搞完,可能使用多线程多进程要好一些,代码如下:
异步—耗时大概八小时
df = df.reset_index(drop=True).reset_index()
lst = list(df.to_dict('index').values())
lst[:1]
class GetCurriculum:
def __init__(self, lst_dic, cookies, headers, params):
self.que = asyncio.Queue()
[self.que.put_nowait(dic) for dic in lst_dic]
self.cookies = cookies
self.headers = headers
self.params = params
self.result = {}
self.eventloop()
async def scrape_url(self, session, dic):
xn = 2023
xq = 0
pycc = dic['val_1']
nj = dic['val_2']
yxb = dic['val_3']
zydm = dic['val_4']
url = 'https://onevpn.bnu.edu.cn/http/77726476706e69737468656265737421eaee478b69326645300d8db9d6562d/taglib/DataTable.jsp'
data = f'initQry=0&xktype=2&xh=202261291404&xn=2023&xq=0&nj=2022&pycc=2&dwh=AF&zydm=AF025200221000&kclb1=&kclb2=&isbyk=&items=&xnxq={xn}%2C{xq}&btnFilter=%C0%E0%B1%F0%B9%FD%C2%CB&btnSubmit=%CC%E1%BD%BB&sel_pycc={pycc}&sel_nj={nj}&sel_yxb={yxb}&sel_zydm={zydm}&kkdw_range=self&sel_cddwdm=&menucode_current=JW130417'
async with session.post(url=url, params=self.params, data=data) as response:
if response.status == 200:
content = await response.text()
data = pd.read_html(content)[0]
self.result[dic['index']] = data
async def main(self):
pbar = tqdm(total=self.que.qsize())
while True:
if self.que.empty():
print('任务完成!')
break
else:
dic = await self.que.get()
async with aiohttp.ClientSession(headers=self.headers, cookies=self.cookies) as session:
await self.scrape_url(session, dic)
pbar.update(1)
def eventloop(self):
loop = asyncio.get_event_loop()
loop.run_until_complete(self.main())
cookies = {
'wengine_vpn_ticketonevpn_bnu_edu_cn': 'asdasasd0a4a8ef47665ab54',
'show_vpn': '0',
'heartbeat': '1',
'show_faq': '0',
'refresh': '1',
}
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Cache-Control': 'max-age=0',
'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'https://onevpn.bnu.edu.cn',
'Referer': 'https://onevpn.bnu.edu.cn/http/77726476706e69737468656265737421eaee478b69326645300d8db9d6562d/student/wsxk.kcbcx10319.html?menucode=JW130417',
'Sec-Fetch-Dest': 'iframe',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-User': '?1',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69',
'sec-ch-ua': '"Chromium";v="116", "Not)A;Brand";v="24", "Microsoft Edge";v="116"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}
params = {
'tableId': '5327042',
}
data = GetCurriculum(lst, cookies=cookies, headers=headers, params=params)
for key, val in data.result.items():
val['index'] = key
df_5 = pd.concat(data.result.values())
df = df.merge(df_5, on='index', how='outer')
df.to_excel('完整数据集.xlsx', index=False)
运行结果如下,太慢了:
多进程—耗时大概两小时
import pandas as pd
from tqdm import tqdm
import requests
from multiprocessing import Pool
from concurrent.futures import ProcessPoolExecutor
def get_curriculum(dic):
print(dic['index'], '正在执行!')
cookies = {
'wengine_vpn_ticketonevpn_bnu_edu_cn': 'asdasdasd0a4a8ef47665ab54',
'show_vpn': '0',
'heartbeat': '1',
'show_faq': '0',
'refresh': '1',
}
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Cache-Control': 'max-age=0',
'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'https://onevpn.bnu.edu.cn',
'Referer': 'https://onevpn.bnu.edu.cn/http/77726476706e69737468656265737421eaee478b69326645300d8db9d6562d/student/wsxk.kcbcx10319.html?menucode=JW130417',
'Sec-Fetch-Dest': 'iframe',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-User': '?1',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69',
'sec-ch-ua': '"Chromium";v="116", "Not)A;Brand";v="24", "Microsoft Edge";v="116"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}
params = {
'tableId': '5327042',
}
xn = 2023
xq = 0
pycc = dic['val_1']
nj = dic['val_2']
yxb = dic['val_3']
zydm = dic['val_4']
url = 'https://onevpn.bnu.edu.cn/http/77726476706e69737468656265737421eaee478b69326645300d8db9d6562d/taglib/DataTable.jsp'
data = f'initQry=0&xktype=2&xh=202261291404&xn=2023&xq=0&nj=2022&pycc=2&dwh=AF&zydm=AF025200221000&kclb1=&kclb2=&isbyk=&items=&xnxq={xn}%2C{xq}&btnFilter=%C0%E0%B1%F0%B9%FD%C2%CB&btnSubmit=%CC%E1%BD%BB&sel_pycc={pycc}&sel_nj={nj}&sel_yxb={yxb}&sel_zydm={zydm}&kkdw_range=self&sel_cddwdm=&menucode_current=JW130417'
response = requests.post(
url=url,
params=params,
cookies=cookies,
headers=headers,
data=data,
)
if response.status_code == 200:
df = pd.read_html(response.text)[0]
df['index'] = dic['index']
df.to_csv(f'./data/{int(dic["index"])}.csv')
print(dic['index'], '执行完毕!')
else:
print(dic['index'], '失败!')
if __name__ == '__main__':
df = pd.read_excel('专业列表.xlsx').reset_index()
df_list = []
P = ProcessPoolExecutor(30)
for i in range(df.shape[0]):
dic = df.iloc[i]
print("main执行完毕")
得到结果快得一点,大概要2小时。
总结
这套模板我也试过其他大学,小改一下应该是可以通用的,编码花费1个多小时,奶奶滴!听课去咯