一个学校朋友让我帮他做一个查询空余教室的小程序,然而又没有现成的数据,只有自己动手找了.
不过我也是第一次写爬虫
我们学校教务网中有一个栏目是关于全校课程的汇总,其中有一个就是按教室进行分类,每一间教室都有一张独立的课程表,其中记录了该教室一个周要上的课和上课周数.
找到了数据,那就开始动手爬吧!
高能! 由于学校的教务系统不知道是哪个年代做的了,所以里面可能会出现各种各样的坑.
首先选定我们的数据库,我习惯于使用mysql数据库,而Python也有很成熟的类库可以使用.
然后设计存储结构,首先我们需要看一下我们可以得到的数据和需要的数据:
从这里可以看到,我们可以从这个表格中获取到教室位置, 校区, 容纳人数, 教室类别这几种数据
从教室的课表中可以得到上课周时间,单双周上课情况,上课人数,上课课程等等. 目前我们需要的数据只有上课周,教室位置,校区,容纳人数,单双周上课情况,教室种类.
现在可以建立数据表了:
{
int id,
//主键
varchar address,
//教室位置
varchar odd,
//单周
varchar even,
//双周
varchar area,
//校区
varchar category,
//种类
varchar population,
//容纳人数
}
我们先在数据库中建立好数据表,具体步骤我就不写了.
现在打开第一张图的网页,查看请求头和源码
呃,好像有点儿长,就这样吧.
开始动手写
import requests as req
from bs4 import BeautifulSoup as bs
import re
import MySQLdb
构建initReq函数:
def initReq(url, header):
response = req.get(url, headers=header)
content = bs(response.content, "html.parser")
return content
首先获取教室表格
header={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299",
"Referer": "http://jiaowu.xxxxxx.edu.cn/web/web/lanmu/kebiao.asp",
"Cookie": "Hm_lvt_20274609f261feba8dcea77ff3f7070c=1523195886; ASPSESSIONIDCCAADCDT=GPKEFCGBHNKNLHIJBDMHBMGD; jcrj%5Fuser=web; jcrj%5Fpwd=web; jcrj%5Fauth=True; jcrj%5Fsession=jwc%5Fcheck%2Cauth%2Cuser%2Cpwd%2Cjwc%5Fcheck%2Ctymfg%2Csf%2C; jcrj%5Fjwc%5Fcheck=y; jcrj%5Ftymfg=%C0%B6%C9%AB%BA%A3%CC%B2; jcrj%5Fsf=%D1%A7%C9%FA"}
response=res.initReq("http://jiaowu.xxxxx.edu.cn/web/web/lanmu/jshi.asp",header=header)
res.getClassRoom(response)
构建GetClassRoom函数,从已经获取到html中获取教室信息:
def getClassRoom(html):
table = html.find_all("table")
tr = table[-2].find_all("tr")#find_all方法会遍历出所有的指定元素,返回一个列表
for i in tr[1:]:
roomInfo = i.find_all("td")
area = roomInfo[1].get_text()
category = roomInfo[4].get_text()
address = roomInfo[0].find("input")
address = address["value"]
link = roomInfo[-1].find("a")
link = link["href"]
population = roomInfo[3].get_text()
getRoomInfo(link=link, address=address, category=category, area=area, population=population)
def getTable(url):
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299",
"Referer": "http://jiaowu.xxxxx.edu.cn/web/web/lanmu/jshi.asp",
"Cookie": "Hm_lvt_20274609f261feba8dcea77ff3f7070c=1523195886; ASPSESSIONIDCCAADCDT=GPKEFCGBHNKNLHIJBDMHBMGD;jcrj%5Fuser=web; jcrj%5Fpwd=web; jcrj%5Fauth=True;jcrj%5Fsession=jwc%5Fcheck%2Cauth%2Cuser%2Cpwd%2Cjwc%5Fcheck%2Ctymfg%2Csf%2C;jcrj%5Fjwc%5Fcheck=y;jcrj%5Ftymfg=%C0%B6%C9%AB%BA%A3%CC%B2;jcrj%5Fsf=%D1%A7%C9%FA"}
result = initReq("http://jiaowu.xxxx.edu.cn/web/web/lanmu/" + url, headers)
return result
defTable是为了得到课程表
我先一步一步的对html进行解剖,然后提取相应的数据.
在这一步可以得到每一间教室的课程表连接,现在还需要一个获取课标信息的操作,也就是上面我们call的getRoomInfo函数.
def getRoomInfo(link, category, area, address, population):
result = getTable(link)
table = result.find_all("table")
table = table[2].find_all("table")
tr = table[2].find_all("tr")
# print(tr[1].find_all("tr"))
l = 0
tr = tr[1].find_all("tr")
for i in tr:
l += 1
tds = i.find_all("td")
w = 0
for td in tds[-7:]:
w += 1
result = getLeason(td)
leason = str(w) + "-" + str(l)
db.con(category=category, area=area, address=address, curLeasonInfo=result, population=population,
leason=leason)
现在提取出整张课程表,
从htm中可以看出tr的子节点td中就是我们需要的数据信息,我们可以通过find_all方法提出所有的tr,然后再遍历tr提取出其中td,最后使用get_text()方法得到节点中的数据
但是根据课表可以看出,tr中存在合并后的单元格,那么在提取数据时如果从右向左进行遍历肯定是不行的,我们需要截取出真正蕴含信息的部分.
在这里的时候我遇到了一个问题
这里存在一个空tr,这个空tr为啥会出现呢?有什么意义?我不知道,但是我知道他给我接下来的操作造成了麻烦.
我用find_all获取tr标记的时候发现出现了问题,最后数据提取出来全都是错乱的,课程时间完全不对,甚至一天多出来了十节课. 等我输出这个tr的时候发现, tr中原本应该只有七条的列表多出来好几条.然后我想到了这个多出来的tr标记.
这里看不清,放个大图
现在可以看到,这个tr中蕴含了我需要的所有tr. 接下来,我要验证我的猜想(教务网编写人员的失误----tr未结束):
这是原始html.
这是在火狐中开发者模式中的.
测试代码
from bs4 import BeautifulSoup as bs
text="""
<table>
<tbody>
<tr><td>a</td></tr> <tr> <tr><td>a</td></tr> <tr><td>a</td></tr> <tr><td>a</td></tr>
</tbody>
"""
html=bs(text,"html.parser")
print(html.find_all("tr"))
结果:
从结果中我们可以看到,这里我们原本未结束的tr在find_all执行的时候自动把它给补全了!!!!!而之前的浏览器开发者模式中显示的又是浏览器自动优化后的结果,这误导直接造成了我们最后的结果错误.
def getTr(html):
tr = []
j = 0
for i in html[1:]:
j += 1
k = 0
trTemp = i.find_all("tr")
if trTemp:
for z in trTemp:
if len(z) > 3: # 此处存在判定问题,因为教务网中一个tr错误,这里trtemp列表中会多出一条包含所有的剩余tr的项
k += 1
tr.append(z)
pass
pass
pass
return tr
上面给出的代码是改造好的代码. 我们可以tr列表中看到列表第二项就已经包含了所有信息,所以我们只需要得到第二项就可以了
def getLeason(td):
text = td.get_text()
mask = re.compile("[1-9]-[1-9]{2}")
week = mask.findall(text)
single = 0 # 是否单周有课
double = 0
if len(week) != 0:
if re.search(r"\(单\)", text):
temp = re.compile("\(单\)\n[1-9]-[0-9]{2}") # 编译寻找单周上课时间的上课周的正则
text1 = temp.search(text)
if text1:
result = mask.search(text1.group()) # 找出上课周
single = result.group() # 单周上课时间
pass
elif re.search(r"\(双\)", text):
double = 1
temp = re.compile("\(双\)\n[1-9]-[0-9]{2}") # 编译寻找单周上课时间的上课周的正则
text1 = temp.search(text)
if text1:
result = mask.search(text1.group()) # 找出上课周
double = result.group() # 双周上课时间
pass
else:
single = double = week[0] # 上课不分单双周
pass
else:
print("本堂没课")
pass
return {"single": single, "double": double}
使用re库提取处对应信息,这里需要区分单双周上课,所以正则表达式
\(单\)\n[1-9]-[0-9]{2}
这一步为了得到整个单周上课信息,然后再提出上课时间
[1-9]-[0-9]{2}
End