---
tags: Python
title: 搶課爬蟲實戰 ⚙️
description: 中興大學資訊社
image: https://i.imgur.com/MLHI4tb.png
slideOptions:
allottedMinutes: 50 # 預計整份簡報花幾分鐘
---
<style>
summary h2{display:inline;border-bottom:0!important}
section summary h2{display:none!important}
</style>
# [搶課爬蟲實戰](https://colab.research.google.com/drive/11DG2180NQWVVUCKtUfG_jJfVUdTp_Y_i#sandboxMode=true)
<!-- > ⚠️不定期更新中,請注意程式碼可能**出現缺漏**等情況 -->
:::spoiler {state=open}<h2>目錄</h2>
+ [VJ GitHub 存檔](https://github.com/twjmy/twjmy.github.io)
+ ++[去年110-1 Response存檔](https://reurl.cc/35dnvV)++
+ **[從HTML到MarkDown - 網頁基礎(完全新手上路)](/@NCHUIT/mdhtml)**
+ **[LINE Notify - Python 爬蟲基礎教學(完全新手上路)](/@NCHUIT/line-notify)**
[ToC]
:::
# 網頁分析範例:通識預選
目標: 獲得`want`指定的課堂資訊
```python=
from requests import Session
from bs4 import BeautifulSoup
headers={'User-Agent':''} # 瀏覽器資訊
rs=Session() # 瀏覽器
rs.cookies.set('cookie_psstud', '學號') # 請改自己的
rs.cookies.set('cookie_pwd', '伺服器密碼') # 請改自己的,獲取方法看下面
url='https://onepiece.nchu.edu.tw/cofsys/plsql/gned_bef1'
response=rs.get(url,headers=headers)
want=['0679','0664','0429','0432'] # 選課號碼
# print(response.text)
bs=BeautifulSoup(response.text,'lxml')
form=bs.find('form') # 找到定位點
tables=form.select('table') # 選到我們要的<table> *是list
for table in tables[1::2]: # 篩掉奇數的<table>
tr=table.select('tr') # 選到每一列,即所有<tr> *是list
course=tr[2:] # 篩掉標題列/欄位標題 *還是list
for prop in course:
tdlist=prop.select('td') # 選到每一欄,即所有<td> *是list
if tdlist[5].string in want: # 判斷是不是你要的課號,[5]:選課號碼是第5欄
print(tdlist[5]) # 得到選課號碼
print(tdlist[11]) # 得到開課人數
```
用途: 選擇法用於掛機判斷**可選餘額**人數,若需要請務必學起來
## 教務系統今年更新,使用 requests 套件需加 verify=False 才可正常送出
# 登入 `ACAD_PASSCHK`
```python=!
from requests import Session
plsql='https://onepiece.nchu.edu.tw/cofsys/plsql/' # 網域
headers={'User-Agent':''}
data={'v_emp':'學號','v_pwd':'入口密碼'} # 帳密
rs=Session() # 瀏覽器
rs.post(plsql+'ACAD_PASSCHK',headers=headers,data=data,verify=False)
print(rs.cookies.get_dict())
```
用 post 送帳密到`ACAD_PASSCHK`頁面
: 有出現`cookie_psstud`和`cookie_pwd`就代表你登入成功了,其中`cookie_pwd`存放在教務系統伺服器上。據觀察,它不會變動。
## 另一種方法
```python=!
rs.cookies.set('cookie_psstud', '學號')
rs.cookies.set('cookie_pwd', '你在伺服器上的cookie_pwd')
```
如此 `set` 之後,對其他教務系統頁面送請求時,**皆暢行無阻**。也就不需要登入這個步驟了。
# 課程代號 `v_class_nbr`
**課程代號**請於**課綱**網頁的網址尾端截取 "v_class_nbr=****" (四碼) 如下所示:

課綱頁面網址請至[**課程查詢**](https://onepiece.nchu.edu.tw/cofsys/plsql/crseqry_all)找到想選的課程後,點擊該課程的選課號碼後進入的便是課綱頁面。
:::spoiler {state=open}點擊展開查看指示圖片
1. 
2. 
:::
# 加退選(待補)
## 直接加選
1. **get** `enro_direct1_list` 以取得 `cookie_key`(也可能沒有)
2. **post** 10對 4碼 ==`V_WANT`==(**需補空**) 到 `enro_direct2_chk` 以取得14碼 `v_tick`
3. **post** 14碼 `v_tick` 到 `enro_direct3_dml`,完成選課
+ `V_WANT` 即++選課號碼++
## 退選
1. **get** `enro_del1_list` 以取得 `cookie_key` 及4碼 ==`v_del`== **找你要退的**
2. **post** 4碼 `v_del` 到 `enro_del2_check` 以取得4碼**完全一樣**的 `v_del`
3. **post** 4碼 `v_del` 到 `enro_del3_drop`,完成退課
## 流程控制邏輯
### 法1
```python
while True:
# 加選3步驟
```
### 法2
```python
while True:
# 加選3步驟,解析出衝堂、名稱相同訊息,餘額
if (衝堂 or 名稱相同) and 餘額>0:
重試=True
## 退選3步驟,錯誤訊息上會給有衝突的課號
else: 重試=False
if not 重試: break
```
# 體育課(待補)
1. **get** `enro_sport_list?v_action=sport` 以取得`cookie_key` 及14碼 ==`v_tick`==
2. **post** 14碼 `v_tick` 到 `enro_nomo2_check` 以取得14碼**完全一樣**的 `v_tick`
3. **post** 14碼 `v_tick` 到 `enro_nomo3_dml`,完成選課
+ 直接 `post` 第2步的14碼 `v_tick` 到 `enro_nomo3_dml` 也可以
# 通識課
1. **get** `gned_add2_list` 以取得 `cookie_key` 及4碼 ==`v_click`==
2. **post** 4碼 `v_click` 到 `gned_add3_check` 以取得7碼 `v_click`
3. **post** 7碼 `v_click` 到 `gned_add4_dml`,完成選課
+ 4碼 `v_click` 即++課程代號++
+ 去年只 `post` 4碼 `v_click` 到 `gned_add4_dml` 也可以,但被查起來的話就比較尷尬
## 1. 通識加選-選擇 `gned_add2_list`
```python=!
res=rs.get(plsql+'gned_add2_list',headers=headers)
```
伺服器會==set-cookie: `cookie_key`==
經驗
: 每次發送 `get` 請求到 `gned_add2_list` 頁面時 `cookie_key` 都會刷新。經測試,發送 `post` 請求到 `gned_add4_dml` 頁面時**必須用到**,沒有的話就**無法如預期選課**。所以就算要跳步,也請好好 `get` 它。
如果你已經找好[**課程代號**](#%E8%AA%B2%E7%A8%8B%E4%BB%A3%E8%99%9F-v_class_nbr)...
: 上面拿完 `cookie_key` 後可以直接跳到**第3步**,將代號4碼換7碼。
如果想偷懶直接4碼選課也可以直接跳到**第4步**,但仍建議7碼。
### 分析網頁
```python=!
from bs4 import BeautifulSoup
bs=BeautifulSoup(res.text),'lxml')
```
使用 bs4 套件來解析,因為教務處工程師寫的網頁會報錯,所以我們[`lxml`](https://www.crummy.com/software/BeautifulSoup/bs4/doc/#differences-between-parsers)解析器來排錯
```python=!
want=['0314','0315','0103','0418'] # 你要選的課 的「選課號碼」
v_click=list()
for i in want:
num_ele=bs.find(string=i) # 找到定位點,ele:element 元素
# print(num_ele) # 印出來看看
input_ele=num_ele.find_previous('input') # 直接找上一個<input>
# print(input_ele) # 印出來看看
value=input_ele['value'] # 從 tag 屬性中選出 value
# print(value) # 印出來看看
v_click.append(value) # 丟到 list 裡
print(v_click) # 印出來看看
# 簡化版
v_click=[bs.find(string=i).find_previous('input')['value'] for i in want]
print(v_click) # 印出來看看
```
依據你要選的課 的「選課號碼」,循序選出你想要選的課 的`v_click`,其實它就是課綱網址上的`v_class_nbr`
### 準備資料
```python=!
data={'v_click':v_click} # 資料格式要是 dict
print(data) # 印出來看看,會像是 {'v_click':['0327','0420',...]}
```
因為 Python 不支持多對多的字典(ex. `{'v_xx':'0327','v_xx':'0420'}`),
所以我們要在`dict`裡放`list`存成像是`{'v_xx':['0327','0420']}`的樣子
## 2. 通識加選-確認 `gned_add3_check`
```python=!
res=rs.post(plsql+'gned_add3_check ',headers=headers,data=data) # data送出去啦~
```
post `v_click` 到 `gned_add3_check` 頁面
### 分析網頁
```python=!
bs=BeautifulSoup(res.text,'lxml')
inputs=bs.select('input[name="v_click"]') # 找出所有<input ... name="v_click">
v_click=list() # 清空
for i in inputs:
v_click.append(i['value']) # 丟到 list 裡
print(v_click) # 印出來看看
# 簡化版
v_click=[i['value'] for i in bs.select('input[name="v_click"]')]
print(v_click) # 印出來看看
```
課程代號4碼換7碼
### 準備資料
```python=!
data={'v_click':v_click}
print(data) # 印出來看看
```
同第1步,覆蓋之前的變數`data`,如此一來就全是7碼的`v_click`了
## 3. 通識加選-結果 `gned_add4_dml`
```python=!
res=rs.post(plsql+'gned_add4_dml',headers=headers,data=data)
```
就只是送出去而已
### 檢視結果
```python=!
bs=BeautifulSoup(res.text,'lxml')
tables=bs.select('table') # 選到所有<table>
for table in tables:
for tr in table.select('tr'): # 選到所有<tr>
for td in tr.select('td'): # 選到所有<td>
print(td.text.strip(),end=' ')
print()
```
當然你想看看選得怎樣的話,可以解析一下印出來給自己看,上面有點醜,可自行美化。
## 簡易搶通識課爬蟲 (註解供選用第2,3步)
```python=
from requests import Session
from bs4 import BeautifulSoup
from IPython.display import clear_output #用來清畫面(colab)
import os #用來清畫面(本地)
## 登入
headers={'User-Agent':''}
rs=Session()
rs.cookies.set('cookie_psstud', '學號') # 請改自己的
rs.cookies.set('cookie_pwd', '伺服器密碼') # 請改自己的,獲取方法看上面
want=['0429','0433','0435','0434','0679'] # 選課號碼
v_class_nbr=['5308','5193','2521','2087','3417'] # 課程代號
plsql='https://onepiece.nchu.edu.tw/cofsys/plsql/'
## 選擇
res=rs.get(plsql+'gned_add2_list',headers=headers)
while res.text.rfind('本時段不開放此功能')!=-1:
clear_output(True) #清畫面(colab)
os.system('cls' if os.name=='nt' else 'clear') #清畫面(本地)
print('本時段不開放此功能')
res=rs.get(plsql+'gned_add2_list',headers=headers)
# bs=BeautifulSoup(res.text,'lxml')
# v_click=[bs.find(string=i).find_previous('input')['value'] for i in want]
# data={'v_click':v_click}
## 確認
# res=rs.post(plsql+'gned_add3_check ',headers=headers,data=data)
# bs=BeautifulSoup(res.text,'lxml')
# v_click=[i['value'] for i in bs.select('input[name="v_click"]')]
# data={'v_click':v_click}
## 選用課程代號
data={'v_click':v_class_nbr} # 請選用
## 結果
res=rs.post(plsql+'gned_add4_dml',headers=headers,data=data)
bs=BeautifulSoup(res.text,'lxml')
table=bs.select('table')[-2]
for tr in table.select('tr'):
for td in tr.select('td'):
print(td.text.strip(),end=' ')
print()
```