2022-01-10|閱讀時間 ‧ 約 21 分鐘

學校體溫自動上傳爬蟲

動機

因為疫情,這學習開始學校要求大家每天都要在九點前到學校網站上傳體溫,我覺得非常麻煩。 動點,果沒傳要被記警告!? 這種麻煩的要求我當然是不會每天乖乖上傳的,於是我做了一個小爬蟲來幫我自動上傳體溫。 本來每天用的好好,但有一天我發現爬蟲沒有上傳成功,於是我只能手動上傳,但當我打開網站要上傳時,我看到了令我無比傻眼的畫面……….????????!
說實話我看到這個東東我有點驚訝又覺得很好笑??,於是我就開始想解決它。

製作

使用語言*:python 使用套件:
import requests 

from bs4 import BeautifulSoup, element
from selenium 
import webdriver
import selenium
import time
首先,我用requests和把網站爬下來,然後丟到BeautifulSoup裡剖析。
url=’https://webap1.kshs.kh.edu.tw/kshsSSO/publicWebAP/bodyTemp/index.aspx'
r=requests.get(url)
soup=BeautifulSoup(r.text,’lxml’)
再來,我的任務是讓電腦自動判斷驗證的數字,並勾相應的選項。
取得驗證數字有問題的方式
我把BeautifulSoup剖析後的網頁,用find$=(id=''ContentPlaceHolder1_lb)的方式找出html中顯示驗證數字的span(區域)並將tag_verification_num.string(驗證數字)存到tag_verification_num變數中。 但問題來了,當我把tag_verification_num這個變數print出來時,它的值是null。 解決方法 把BeautifulSoup剖析後的網頁,用find$=(id='ContentPlaceHolder1_lb')的方式找出html中顯示驗證數字的span(區域)並將tag_verification_num.string(驗證數字)存到tag_verification_num變數後,將它轉成str(字串型態),在這裡我發現這個span轉成字串後的第65個數字就是我要的驗證數字
於是我將其存到變數中,以便之後使用。
tag_verification_num=soup.find(id='ContentPlaceHolder1_lbN') 
num_verification=str(tag_verification_num) 
num_verification=num_verification[65]
現在我已經取得驗證數字了,接下來我需要取得每個驗證選項的數字。 因為驗證選項的html結構是用span標籤把input選項和顯示選項數字的label包起來。
所以我先用find.(id=’ContentPlaceHolder1_RadioButtonList1')找到input和label的父標籤span,存到tag_verification變數中,再用tag_verification.find_all(‘input’)找到所有span裡的input標籤。
tag_verification=soup.find(id=’ContentPlaceHolder1_RadioButtonList1')
tag_verification_input=tag_verification.find_all(‘input’)
錯誤取得選項值的方式
用tag_verification.find_all(‘input’)取得所有span裡的input標籤後,再用 for i in tag_verification_input:存取每個input標籤的value值,但是這邊我遇到跟前面很像的問題,就是當我用print(i.value)把它print出來的值是null。
解決方法
這裡也可以用跟上面一樣的方式,把input標籤轉成字串型態,在找到value的值是字串中的第幾個字元,這樣就可以取得驗證選項的數字。
tag_verification=soup.find(id=’ContentPlaceHolder1_RadioButtonList1')
tag_verification_input=tag_verification.find_all(‘input’)
for i in tag_verification_input:
   num_input=str(i)
   num_input[121]//驗證選項的數字
因為tag_verification_input裡面的input子標籤在for迴圈裡面是用i代替,所以以下的驗證選項input標籤,都用i代替。
現在我有了驗證的數字,也有了每個選項的數字,那我現在要做的是判斷哪一個選項的數字等於驗證數字。 我在for i in tag_verification_input:裡面加上判斷式,來判斷i轉成字串後的第121個字元(驗證選項的數字)是否等於num_verification(驗證數字),
for i in tag_verification_input:
num_input=str(i)
  if num_verification==num_input[121]:

  //填報體溫
我已經知道對應到驗證數字的input標籤了,現在我要知道這個input標籤的name屬性,才能用find_element_by_name()控制webdriver勾選正確選項。 如果用最直覺的方法num_input=i.name,那num_input的值一樣會是null,所以還是要把i轉成字串,然後num_input[57:99]存取第59~99之間字元,這個就是i的name。
得到input標籤的name之後,就可以控制webdriver勾選正確選項了~

Code

import requests  
from bs4 import BeautifulSoup, element 
from requests.models import Response 
from selenium 
import webdriver 
import selenium 
import time  
url='https://webap1.kshs.kh.edu.tw/kshsSSO/publicWebAP/bodyTemp/index.aspx'
r=requests.get(url) soup=BeautifulSoup(r.text,'lxml')  
driver=webdriver.Chrome("./chromedriver") 
driver.get(url)  
tag_verification_num=soup.find(id='ContentPlaceHolder1_lbN') 
num_verification=str(tag_verification_num) 
num_verification=num_verification[65] def enter_step1():     
  tag_input = driver.find_element_by_name("ctl00$ContentPlaceHolder1$txtId")     
  tag_input.send_keys('(身分證字號)')     
  driver.find_element_by_name("ctl00$ContentPlaceHolder1$btnId").click() 
def enter_step2():     
  driver.find_element_by_name('ctl00$ContentPlaceHolder1$rbType').click()     
  driver.find_elenent_by_xpath('//*[@id="ContentPlaceHolder1_ddl1"]/option[3]').click()     
  driver.find_element_by_xpath('//*[@id="ContentPlaceHolder1_ddl2"]/option[3]').click()     
  driver.find_element_by_xpath('//*[@id="ContentPlaceHolder1_ddl3"]/option[2]').click()     
  driver.find_element_by_name('ctl00$ContentPlaceHolder1$btnId0').click() 
  tag_verification=soup.find(id='ContentPlaceHolder1_RadioButtonList1') 
  tag_verification_input=tag_verification.find_all('input')   
for i in tag_verification_input:     
  num_input=str(i)     
  print('迴圈外',num_input[121])     
  if num_verification==num_input[121]:         
    print('要驗證的數字',num_verification)       
    print('要輸入的數字',num_input[121])         
    print('secess\n')         
    print(i)         
    print(type(i),'\n')         
    print(num_input[57:99])         
    print(type(num_input[57:99]))         
    print('選取的數字:',num_input[121])         
    driver.find_element_by_name(num_input[57:99]).click()         
    enter_step1()        
    enter_step2()     
  else:         
    print('fail') 
time.sleep(3) 
driver.quit()

改進

動機2.0
2021/4/21學校又再次做出了令我意外的改動.....
驗證換成了隨機的問題,學校這波操作再次讓我感到驚喜,建議下次驗證改成解微積分。

思路2.0

異想天開
一開始我心想,學校會放的題目因該都是大眾會到的基本知識,那麼維基百科也一定查得到,所我想用requests和 beautifulsoup把題目爬下來,然後再送到維基百科搜尋答案,最後判斷維基百科的答案和網站上選項爬下來的答 案是否相同。 問題:這次驗證方式改版後,我發現已經沒辦法用requests和beautifulsoup選項標籤的任何屬性。 解決方法:能力不足,無解。
暴力解法
後來我老師建議了我一個可以忽略所有標籤的值,和驗證題目的方法。 就是我發現驗證選項的每個input標籤的id屬性前面都是ContentPlaceHolder1_RadioButtonList1_, 而最後一個數字是選項的編號,比如說: 選項一:ContentPlaceHolder1_RadioButtonList1_0 選項二:ContentPlaceHolder1_RadioButtonList1_1 選項三:ContentPlaceHolder1_RadioButtonList1_2 這樣就只需要按照這個規則一個選項一個選項的試,就可以解決了。
function
為了方便,我把除了驗證之外的操作做成function。
  1. enter_step1()
  • 功能:輸入身分證字號和點擊開始填報按鈕。
def enter_step1():    
  print('step1')     
  driver.find_element_by_id('ContentPlaceHolder1_txtId').send_keys('(身分證字號)')     
  driver.find_element_by_id('ContentPlaceHolder1_btnId').click()
2. enter_step2()
  • 功能:填入體溫選項,和出席狀況選項。
def enter_step2():     
  print('step2')     
  driver.find_element_by_id('ContentPlaceHolder1_rbType_1').click()     
  driver.find_element_by_xpath('//*[@id="ContentPlaceHolder1_ddl1"]/option[3]').click()     
  driver.find_element_by_xpath('//*[@id="ContentPlaceHolder1_ddl2"]/option[4]').click()     
  driver.find_element_by_xpath('//*[@id="ContentPlaceHolder1_ddl3"]/option[2]').click()
3. submit()
  • 功能:點擊填報完成按鈕。
def sunmit():     
  print('submit')     
  driver.find_element_by_id('ContentPlaceHolder1_btnId0').click()
驗證 首先,我把所有裝有驗證選項的input標籤的id屬性,去掉代表邊好的最後一個數字,然後存到id變數中。
name='ContentPlaceHolder1_RadioButtonList1_'
因為選項的數量每天不一樣,但是經過我的觀察,選項最多只有4個。 所以我用for i in range(3),然後宣告一個變數等於name+iinput_id=name+str(i),就可以利用for迴圈取得每個input標籤的i屬性,再把這個變數放到find_element_by_id()中,每一圈都試一次。 然後因為如果驗證失敗還繼續執行填報體溫的程式碼的話,就會噴error,所以在for迴圈裡面用例外處理try和except,如果驗證失敗就直接去下一回圈。 因為按下開始填報後,網頁會跳出一個alert說“開始填報”,所以我要switch_to_alert().accept()來確認這個alert。
for i in range(3):     
  try:           
    input_id=name+str(i)         
    print(input_id)         
    driver.find_element_by_id(input_name).click()         
    enter_step1()         
    driver.switch_to_alert().accept()         
    print('success')         e
    nter_step2()         
    # sunmit() 
  except:         
    print('fail')           
    # driver.switch_to_alert().accept()
可能的問題: 這樣也不需要擔心,因為如果今天選項只有兩個,卻跑了4個for迴圈,因為如果只有兩個選項,那正確選項也一定在for回圈前兩圈中,程式只要跑到正確選項時,就會直接完成體溫填報。
結論 這樣不管遇到怎樣的驗證問題,都不會影響程式的執行~~ 開心。

Code

import requests 
from selenium import webdriver 
import time  
url='https://webap1.kshs.kh.edu.tw/kshsSSO/publicWebAP/bodyTemp/index.aspx'driver=webdriver.Chrome("./chromedriver") 
driver.get(url)  
def enter_step1():    
  print('step1')     
  driver.find_element_by_id('ContentPlaceHolder1_txtId').send_keys('(身分證字號)')     
  driver.find_element_by_id('ContentPlaceHolder1_btnId').click()      
def enter_step2():    
  print('step2')     
  driver.find_element_by_id('ContentPlaceHolder1_rbType_1').click()     
  driver.find_element_by_xpath('//[@id="ContentPlaceHolder1_ddl1"]/option[3]').click()     

  driver.find_element_by_xpath('//[@id="ContentPlaceHolder1_ddl2"]/option[4]').click()    
  driver.find_element_by_xpath('//[@id="ContentPlaceHolder1_ddl3"]/option[2]').click()      
def sunmit():    
  print('submit')     
  driver.find_element_by_id('ContentPlaceHolder1_btnId0').click()  
id='ContentPlaceHolder1_RadioButtonList1_' 
for i in range(3):     
  try:           
    input_id=name+str(i)         
    print(input_id)         
    driver.find_element_by_id(input_name).click()         
    enter_step1()         
    driver.switch_to_alert().accept()         
    print('success')         
    enter_step2()         
    sunmit()     
  except:         
    print('fail') 
time.sleep(5) 
driver.quit()
分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.