---
# System prepended metadata

title: 搶課爬蟲實戰 ⚙️
tags: [Python]

---

---
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://i.imgur.com/sy75X7B.png)

課綱頁面網址請至[**課程查詢**](https://onepiece.nchu.edu.tw/cofsys/plsql/crseqry_all)找到想選的課程後，點擊該課程的選課號碼後進入的便是課綱頁面。
:::spoiler {state=open}點擊展開查看指示圖片
1. ![](https://i.imgur.com/zJ4c9pD.png)
2. ![](https://i.imgur.com/ex6vbj6.png)
:::

# 加退選(待補)

## 直接加選
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()
```